PDA

View Full Version : ۷۰۰ میلیون رکورد دیتابیس را به راحتی آب خوردن با PHP پردازش کنید



pepsiphone
یک شنبه 21 آبان 1396, 12:42 عصر
تو جدیدترین پروژم باید تمام رکوردهای یک جدول دیتابیس MySQL رو پردازش میکردم (http://bestical.rocks/read-700-million-records-mysql-php/) که حدود ۷۰۰ میلیون رکورد داشت با کد زیر کار رو شروع کردم
$pdo = new PDO('mysql:dbname=db_name;host=127.0.0.1', 'root', '');
$stmt=$pdo->prepare('select * from table_name');
$result= $stmt->execute();
while($row=$stmt->fetch())
...do
که خیلی سریع ۸ گیگ رم سیستمم پر شد و کار انجام نشد بعد از بررسی متوجه شدم که وقتی کد به تابع execute میرسه رم پر میشه و دیگه به حلفه while نمیرسه که نتایج رو پردازش کنه، مشکل به این خاطر بود PDO به صورت پیش فرض از buffered queries استفاده میکنه بزارید buffered queries و unbuffered queries (http://php.net/manual/en/mysqlinfo.concepts.buffering.php)رو توضیح بدم.
تو buffered queries وقتی تابع execute اجرا میشه MySQL نتایج رو به PHP میفرسته و کل رکوردهای حاصل در حافظه نگه داری میشه که تو مرجله بعد میشه با fetch یا fetchAll اونها رو پردازش کرد به همین دلیل بود که کل ۸ گیگ رم سیستمم پر می شد حالا اگه از unbuffered queries استفاده کنیم (که میگم چجوری فعالش کنید) وقتی تابع execute اجرا بشه MySQL یک resource به PHP برمیگردونه و نه کل نتایج رو، بعد شما با استفاده از تابع fetch میتونید دونه دونه رکوردها رو پردازش کنید و یا با تابع fetchAll کلشون رو بارگزاری کنید (که میشه عین مورد اول که کل نتایج تو حافظه قرار میگیرن و رم دوباره پر میشه)
برای اینکه buffered queries رو غیر فعال کنید باید به کد زیر رو بزنید
$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

هر کدوم از این روش ها مزایا و معایب خودش رو داره


تو buffered queries میتونید از توابعی مثل rowCount استفاده کنید چون کل نتایج مشخص شده هست ولی تو unbuffered queries همچین چیزی وجود نداره چون تعداد نتایج مشخص نیست چون همشون بارگزاری نشدن و از معایبش هم همین که چون کل نتایج تو حافظه قرار میگیرن باعث میشه استفاده زیادی از رم بشه.
unbuffered queries هم مزیتش بهینه سازی حافظه هست و از معایبش هم اینکه دو تا کوئری رو همزمان نمیتونید اجرا کنید به این خاطر که وقتی کوئری اول اجرا میشه و MySQL یک ریسورس برمیگردونه حالا اگه یک کوئری دیگه اجرا کنید MySQL نمیتونه هندل کنه مگه اینکه با تابع closeCursor قبلی رو ببندید مثل کد زیر

$stmt->closeCursor();
نکته آخر اینکه ممکن هست درایور MySQL سیستمتون با توجه به تسخش از این قابلیت پشتبانی نکنه که اگه اینجوری باشه خطایی میده کاملن مشخص هست.

بعد از حل این مشکل، من یک کتابخونه به نام ridona (https://github.com/hosseinmousavi/ridona) نوشتم که هم میتونه دیتابیس های حجیم رو بخونه همفایهای متنی با حجم بالا (https://github.com/hosseinmousavi/ridona/blob/master/tests/FileTest.php) رو که اینجا میتونید نحوه استفاده از اون رو ببینید (https://github.com/hosseinmousavi/ridona/blob/master/tests/DatabaseTest.php).