次の大まかなコードがあります(完全なコードは146行で、そのうち90行は文字列解析で、必要に応じて追加できます)。
_ini_set('memory_limit', '7G');
$db = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$db_ub = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$db_ub->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$stmt = $db->prepare('select columns from stats where timestamp between ? and ?');
$stmt->execute(array('2020-04-25', '2020-05-25'));
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo memory_get_usage() .PHP_EOL;
echo $row['id'] . PHP_EOL;
$stmt2 = $db_ub->prepare('select somedata from users limit 1');
$stmt2->execute();
$row2 = $stmt2->fetch(PDO::FETCH_ASSOC);
$type = !empty($row2['somedate']) ? 5 : 4;
$result = $db_ub->prepare('insert ignore into newtable (old, type) values (?, ?)');
$result->execute(array($row['id'], $type));
}
_
$stmt->execute(array('2020-04-25', '2020-05-25'));
中のメモリ消費量は_.34GB
_です(select
中の消費量を監視するためにps aux | grep 'php ' | awk '{$5=int(100 * $5/1024/1024)/100"GB";}{ print;}'
を使用し、_show full processlist
_ SQL側で確認します)。スクリプトがwhile
に入ると、+ 5 GBにジャンプします。
setattribute
のテスト
_var_dump($db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false));
_
影響を受けているようです:
_bool(true)
_
しかし、バッファリングとアンバッファリングを切り替えても、動作は変わりません。
_$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false)
_
そして
_$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true)
_
echo $db->getAttribute(constant('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'));
を使用すると、設定の変更も表示されます。
https://www.php.net/manual/en/ref.pdo-mysql.php のように設定を接続ではなくステートメントに移動しても機能しませんでした。
_$stmt = $db->prepare('select columns from stats where timestamp between ? and ?', array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
_
また、バッファ設定を影響のない接続に移動してみました。
_$db = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
_
2番目の接続を削除すると、バッファリングされていないクエリが意図したとおりに機能するようになります。
_ini_set('memory_limit', '1G');
$db = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
//$db_ub = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
//$db_ub->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$stmt = $db->prepare('select columns from stats where timestamp between ? and ?');
$stmt->execute(array('2019-01-25', '2019-11-25'));
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo memory_get_usage() .PHP_EOL;
echo $row['id'] . PHP_EOL;
/*
$stmt2 = $db_ub->prepare('select somedata from users limit 1');
$stmt2->execute();
$row2 = $stmt2->fetch(PDO::FETCH_ASSOC);
$type = !empty($row2['somedate']) ? 5 : 4;
$result = $db_ub->prepare('insert ignore into newtable (old, type) values (?, ?)');
$result->execute(array($row['id'], $type));
*/
}
_
この使用法では、_memory_get_usage
_は_379999
_を超えません。
2番目の接続のコメントを外してバッファリングも解除すると、次のようになります。
_Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
_
バッファリングされた2番目の接続は、最初に説明したように、実行時に大量のメモリを消費します。 _ini_set('memory_limit'
_が高い場合は機能し、低い場合はエラーになります。大きな_memory_limit
_を使用することは現実的な解決策ではありません。
(Red Hat Enterprise Linux Server release 7.3 (Maipo)
)を使用していました:
_php71u-pdo.x86_64 7.1.19-1.ius.centos7
_
スクリプトを新しいマシンに移動しました(Amazon Linux release 2 (Karoo)
):
_php73-pdo.x86_64 7.3.17-1.el7.ius
_
同じ動作をします。
PDO::ATTR_PERSISTENT
値はブール値ではありません。使用されている接続を識別し、複数の接続には一意の値を使用します。私の場合:
$db = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => 'unbuff', PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
$db_ub = new PDO("mysql:Host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => 'buff', PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true));
単一のクエリを実行するだけでほとんどのコードを取り除くことはできません。
INSERT IGNORE INTO newtable
SELECT ...,
IF(..., 5, 4)
FROM oldtable WHERE ...;
これで、7Gメモリの問題を取り除くことができます。
それが一度にやりすぎていることが判明した場合は、チャンクに分割します。ここのディスカッションを参照してください: http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks (これはDELETEs
について話しますが、次のような他のものに適応させることができますSELECT
。)
別のトピック:ループ内でselect somedata from users limit 1
が実行されるのはなぜですか?毎回同じデータを取得しているようです。また、ORDER BY
がないと、どのlimit 1
行が取得されるかを予測できません。