PDO ile 2 milyon satır: prepared statement cache sıfırlanınca ne oluyor

这篇内容暂时只有 Türkçe 版本,以下是原文。

Geçen ay bir içe aktarma işini devraldım: PHP 8.2, MySQL 8.0, PDO mysql sürücüsü, tek bir worker. Girdi 2.1 milyon satırlık düz metin, satır başına bir UPDATE. İlk hali satır başına connect açıyordu; 40 dakika sonra hâlâ ilk 300 bin satırdaydı ve SHOW PROCESSLIST sürekli “Waiting for table metadata lock” gösteriyordu. Tablo MyISAM’dan InnoDB’ye taşınmıştı ama kod hâlâ autocommit’te tek tek yazıyordu.

Önce bağlantıyı dışarı aldım ve prepared statement’ı bir kez hazırladım. Sonra işi 20 bin satırlık parçalara böldüm:

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('UPDATE kayit SET durum = ?, guncel = ? WHERE id = ?');

foreach (array_chunk($satirlar, 20000) as $chunk) {
    $pdo->beginTransaction();
    foreach ($chunk as [$id, $durum]) {
        $stmt->execute([$durum, time(), $id]);
    }
    $pdo->commit();
}

Süre 38 dakikadan 4 dakikaya indi. Ama işin ilginç kısmı bu değil: ikinci denemede 1.2 milyonuncu satırda süreç aniden yavaşladı ve SHOW GLOBAL STATUS LIKE 'Com_stmt_prepare' sayacı zıplamaya başladı. Sebep, MySQL’in max_prepared_stmt_count sınırına yaklaşmamızdı; bağlantı havuzundaki her yeni bağlantı kendi statement’ını hazırlıyor, kapanmayanlar da sayaçta kalıyordu. ATTR_EMULATE_PREPARES => false bunu görünür yaptı; emülasyon açıkken aynı sorun sessizce istemci tarafında birikiyor ve sadece bellek şişmesi olarak kendini gösteriyor.

Bu yüzden parça boyutunu 20 binden 5 bine çektim ve her parça sonunda $stmt->closeCursor() çağırdım. Ek maliyet: 420 commit yerine ~4200 commit, yani daha fazla fsync. Karşılığında statement sayacı sabit kaldı ve iş tekrar çalıştırılabilir hale geldi. InnoDB’de innodb_flush_log_at_trx_commit = 2 ile bu maliyeti biraz kıstım ama elektrik kesintisinde son saniyeleri kaybetmeyi kabul ettim; bunu üretimde yapmadan önce iki kez düşünün.

Nerede işe yaramaz: tablo üzerinde eşzamanlı yazma varsa uzun transaction’lar lock beklemesini büyütüyor ve parçalama ters etki yapıyor. Ayrıca PDO’nun persistent bağlantısı (ATTR_PERSISTENT => true) açıksa closeCursor() bile yetmiyor; bağlantı başka bir isteğe devredilirken hazırlıklı statement’lar hayalet gibi kalıyor. Bir keresinde CLI cron ile web isteğini aynı havuzda karıştırıp bunu iki gün boyunca aradım.

Sizin ortamınızda içe aktarma işini tek transaction’da mı yoksa parçalı mı çalıştırıyorsunuz, ve parça boyutunu neye göre seçtiniz?

0
赞同度 100% · 0 条评论 · 7 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。