php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #62111 MySQL PDO memory leaks, when used own result row class
Submitted: 2012-05-22 20:05 UTC Modified: 2020-10-27 14:03 UTC
Votes:12
Avg. Score:4.8 ± 0.6
Reproduced:12 of 12 (100.0%)
Same Version:4 (33.3%)
Same OS:7 (58.3%)
From: hosiplan at gmail dot com Assigned: nikic (profile)
Status: Closed Package: PDO MySQL
PHP Version: 5.4.4RC1 OS: Linux
Private report: No CVE-ID: None
 [2012-05-22 20:05 UTC] hosiplan at gmail dot com
Description:
------------
When PDO is told to use my row class and pass PDOStatement into it, it creates 
cyclic reference, that prevents GC from deleting the row data, when not required 
anymore.

Test script:
---------------
$db = new PDO('mysql:host=127.0.0.1;dbname=information_schema', 'root', 'password');

class DbRow { public function __construct($stt = NULL) { } }
$begin = memory_get_usage();
for ($i=0; $i < 10 ;$i++) {
	$stt = $db->prepare("SELECT * FROM COLLATIONS");
	$stt->setFetchMode(PDO::FETCH_CLASS, 'DbRow');
	$stt->execute();
	$rows = $stt->fetchAll();

	echo number_format((memory_get_usage() - $begin) / 1000000, 2, '.', ' '), " MB\n";
}

Expected result:
----------------
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB
0.05 MB

Actual result:
--------------
0.00 MB
0.05 MB
0.10 MB
0.14 MB
0.19 MB
0.24 MB
0.29 MB
0.34 MB
0.38 MB
0.43 MB

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-05-22 20:10 UTC] hosiplan at gmail dot com
Sorry, I've coppied wrong code



Test script:
---------------

$db = new PDO('mysql:host=127.0.0.1;dbname=information_schema', 'root', 
'password');

class DbRow { public function __construct($stt = NULL) { } }
$begin = memory_get_usage();
for ($i=0; $i < 10 ;$i++) {
	$stt = $db->prepare("SELECT * FROM COLLATIONS");
	$stt->setFetchMode(PDO::FETCH_CLASS, 'DbRow', array($stt));
	$stt->execute();
	$rows = $stt->fetchAll();
	echo number_format((memory_get_usage() - $begin) / 1000000, 2, '.', ' 
'), " MB\n";
}
 [2012-05-22 20:10 UTC] hosiplan at gmail dot com
-Summary: GC not working, when used own row class +Summary: MySQL PDO memory leaks, when used own result row class -PHP Version: 5.4.3 +PHP Version: 5.4.0
 [2012-05-22 20:12 UTC] hosiplan at gmail dot com
affected version
 [2012-05-22 20:12 UTC] hosiplan at gmail dot com
-PHP Version: 5.4.0 +PHP Version: 5.4.4RC1
 [2012-05-22 20:39 UTC] juzna dot cz at gmail dot com
Leaks with mysql, no leaks with sqlite.

No need to fetchAll(); execute() is enough to get leaks
 [2012-06-11 13:48 UTC] juzna dot cz at gmail dot com
The same causes PHP to crash completely on Windows 32bit with MSSQL server (using 
sqlsrv driver). I guess the root cause will be related.
 [2012-06-12 13:33 UTC] uw@php.net
There is no leak with MySQL. Memory usage increases, that's it.

==6216== LEAK SUMMARY:
==6216==    definitely lost: 0 bytes in 0 blocks
==6216==    indirectly lost: 0 bytes in 0 blocks
==6216==      possibly lost: 0 bytes in 0 blocks
==6216==    still reachable: 54 bytes in 2 blocks
==6216==         suppressed: 0 bytes in 0 blocks
==6216== Rerun with --leak-check=full to see details of leaked memory
 [2012-06-12 16:18 UTC] hosiplan at gmail dot com
I don't really care what you name it. It's a real problem and its eating my 
memory and it shouldn't!
 [2012-06-13 15:10 UTC] uw@php.net
It matters a whole lot what the cause is. It is not a leak. First, it helps to have some clean test code, such as this:

class pdo_row {
	public function __construct($stmt) {
		$stmt = NULL;
	}
}

/* $pdo = new PDO("mysql:dbname=test;unix_socket=/var/run/mysql/mysql.sock", "root", ""); */
$pdo = new PDO("sqlite::memory:");

$pdo->exec("DROP TABLE test");
$pdo->exec("CREATE TABLE test(id INT, col VARCHAR(200))");
for ($i = 0; $i < 100; $i++) {
  $pdo->exec(sprintf("INSERT INTO test(id, col) VALUES (1, '012345678901234567890123456789012345678901234567890123456789-%d')", $i));
}

for ($i = 0; $i < 10; $i++) {
	$stmt = $pdo->prepare("SELECT col FROM test");
	$stmt->execute();
	$stmt->setFetchMode(PDO::FETCH_CLASS, 'pdo_row', array($stmt));
	$rows = $stmt->fetchAll();
	printf("emalloc %d kB, malloc %d kB\n",
		memory_get_usage() / 1024,
		memory_get_usage(true) / 1024);
}


Running the above for SQLlite gives me:

emalloc 205 kB, malloc 256 kB
emalloc 206 kB, malloc 512 kB
emalloc 207 kB, malloc 512 kB
emalloc 208 kB, malloc 512 kB
emalloc 209 kB, malloc 512 kB
emalloc 210 kB, malloc 512 kB
emalloc 211 kB, malloc 512 kB
emalloc 212 kB, malloc 512 kB
emalloc 213 kB, malloc 512 kB
emalloc 214 kB, malloc 512 kB


Running the above for MySQL gives me:



emalloc 221 kB, malloc 256 kB
emalloc 231 kB, malloc 512 kB
emalloc 240 kB, malloc 512 kB
emalloc 250 kB, malloc 512 kB
emalloc 259 kB, malloc 512 kB
emalloc 269 kB, malloc 512 kB
emalloc 278 kB, malloc 512 kB
emalloc 288 kB, malloc 512 kB
emalloc 297 kB, malloc 512 kB
emalloc 307 kB, malloc 512 kB


Second, it helps to understand the matter. In both cases emalloc() figures increase, which is to be expected. PHP objects are created, the extensions allocate memory using the PHP internal e*alloc() function family and the figures increase. And, in both cases malloc() figures are the same. 

No difference between MySQL and SQLite.
 [2012-12-15 20:35 UTC] enumag at gmail dot com
Try this one instead. It does not matter whether you use MySQL or SQLlite. The problem is that if you add some arguments for the constructor, it consumes more and more memory without any reason.

---------
class pdo_row {
	public function __construct() { }
}

$pdo = new PDO('mysql:host=127.0.0.1;dbname=information_schema', 'root', '');

$pdo->exec("DROP TABLE test");
$pdo->exec("CREATE TABLE test(id INT, col VARCHAR(200))");
for ($i = 0; $i < 100; $i++) {
  $pdo->exec(sprintf("INSERT INTO test(id, col) VALUES (1, '012345678901234567890123456789012345678901234567890123456789-%d')", $i));
}

printf("With ctor argument (memory usage increase):");
for ($i = 0; $i < 10; $i++) {
	$stmt = $pdo->prepare("SELECT col FROM test");
    $stmt->setFetchMode(PDO::FETCH_CLASS, 'pdo_row', array($stmt));
	$stmt->execute();
	$rows = $stmt->fetchAll();
	printf("emalloc %d kB, malloc %d kB\n",
		memory_get_usage() / 1024,
		memory_get_usage(true) / 1024);
}

printf("Without ctor argument (no memory usage increase):");
for ($i = 0; $i < 10; $i++) {
	$stmt = $pdo->prepare("SELECT col FROM test");
    $stmt->setFetchMode(PDO::FETCH_CLASS, 'pdo_row');
	$stmt->execute();
	$rows = $stmt->fetchAll();
	printf("emalloc %d kB, malloc %d kB\n",
		memory_get_usage() / 1024,
		memory_get_usage(true) / 1024);
}
 [2014-01-01 12:37 UTC] felipe@php.net
-Package: PDO related +Package: PDO MySQL
 [2020-03-10 15:26 UTC] cmb@php.net
-Status: Open +Status: Verified
 [2020-03-10 15:26 UTC] cmb@php.net
I can confirm that we're still leaking the pdo_stmt_t objects:

==32703== 3,680 bytes in 10 blocks are definitely lost in loss record 1 of 1
==32703==    at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==32703==    by 0x4D52B6: __zend_malloc (zend_alloc.c:2976)
==32703==    by 0x4D423A: _malloc_custom (zend_alloc.c:2417)
==32703==    by 0x4D4368: _emalloc (zend_alloc.c:2536)
==32703==    by 0x3291C1: zend_object_alloc (zend_objects_API.h:91)
==32703==    by 0x331151: pdo_dbstmt_new (pdo_stmt.c:2314)
==32703==    by 0x511762: _object_and_properties_init (zend_API.c:1417)
==32703==    by 0x5117D3: object_init_ex (zend_API.c:1431)
==32703==    by 0x323948: pdo_stmt_instantiate (pdo_dbh.c:412)
==32703==    by 0x324290: zim_PDO_prepare (pdo_dbh.c:522)
==32703==    by 0x5781A9: ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1730)
==32703==    by 0x5D6FF6: execute_ex (zend_vm_execute.h:53821)
 [2020-10-27 14:03 UTC] nikic@php.net
-Status: Verified +Status: Closed -Assigned To: +Assigned To: nikic
 [2020-10-27 14:03 UTC] nikic@php.net
No longer reproduces on 7.3 at least, don't have older versions to test. Various GC issues in PDO have been fixed in the meantime.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 16:01:28 2024 UTC