CentOS 6.4 PHP 5.3.3 MySQL 5.1.69 x86_64
mysql_stmt::fetch()
When executing fetch using a prepared statement, PHP yields error: PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4294967296 bytes).
This occurs when a variable included in a SELECT statement used to create a temporary table is unset, whether or not the variable is otherwise set in the environment before the stored procedure is called. The variable must be set within the stored procedure. When a SELECT statement is used to return data in the temporary table to PHP, and PHP uses mysql_stmt::fetch() to access the data, PHP generates the above fatal error.
MySQL Code:
DELIMITER $$
CREATE PROCEDURE test_sp()
BEGIN
# uncomment below line, and PHP call to mysqli_stmt::fetch() works
# SET @status = 1;
# remove tmp table
DROP TABLE IF EXISTS tmp_table;
# CREATE TEMPORARY TABLE
CREATE TEMPORARY TABLE tmp_table
SELECT @status AS status;
SELECT * FROM tmp_table;
END $$
DELIMITER ;
PHP Code:
// obtain MySQL login info
require_once(MYSQLOBJ);
// initialize status
$status = "";
$db = new mysqli(
DB_HOST,
DB_USER,
DB_PASSWORD,
DB_NAME
);
$query = "CALL test_sp";
$stmt = $db->prepare($query);
$stmt->execute();
$stmt->bind_result( $status );
$stmt->store_result();
$stmt->fetch(); // PHP FATAL ERROR OCCURS HERE
$stmt->free_result();
$db->close();
print "<p>status = $status</p>\n";
You will find that this is occurring only when @status
is NULL
or a string.
The problem is twofold:
Unlike local variables, MySQL user variables support a very limited set of datatypes:
User variables can be assigned a value from a limited set of data types: integer, decimal, floating-point, binary or nonbinary string, or
NULL
value.
The documentation fails to mention that the actual datatypes used are respectively BIGINT
, DECIMAL(65,30)
, DOUBLE
, LONGBLOB
, LONGTEXT
and LONGBLOB
. Regarding the last one, the manual does at least explain:
If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
Storage of the first three of these datatypes (i.e. for integer, decimal and floating-point values) require 8, 30 and 8 bytes respectively. The other datatypes (i.e. for string and NULL
values) require (up to) 4 gigabytes of storage.
Since you are using a version of PHP prior to v5.4.0, the default MySQL driver is libmysql, with which only column type metadata is available from the server upon data binding—so MySQLi attempts to allocate sufficient memory to hold every possible value (even if the full buffer is not ultimately required); thus NULL
- and string-valued user variables, which have a maximum possible size of 4GiB, cause PHP to exceed its default memory limit (of 128MiB since PHP v5.2.0).
Your options include:
Overriding the column datatype in the table definition:
DROP TEMPORARY TABLE IF EXISTS tmp_table;
CREATE TEMPORARY TABLE tmp_table (
status VARCHAR(2)
) SELECT @status AS status;
Explicitly casting the user variable to a more specific datatype:
DROP TEMPORARY TABLE IF EXISTS tmp_table;
CREATE TEMPORARY TABLE tmp_table
SELECT CAST(@status AS CHAR(2)) AS status;
Using local variables, which are declared with an explicit datatype:
DECLARE status VARCHAR(2) DEFAULT @status;
DROP TEMPORARY TABLE IF EXISTS tmp_table;
CREATE TEMPORARY TABLE tmp_table
SELECT status;
Working around the issue by calling mysqli_stmt::store_result()
before mysqli_stmt::bind_result()
, which causes the resultset to be stored in libmysql (outside of PHP's memory limits) and then PHP will only allocate the actual memory required to hold the record upon fetching it:
$stmt->execute();
$stmt->store_result();
$stmt->bind_result( $status );
$stmt->fetch();
Raising PHP's memory limit so that it can accomodate the allocation of 4GiB buffers (although one should be aware of the implications on hardware resources from doing so)—for example, to remove the memory constraints entirely (although be aware of potential negative side-effects from doing this, e.g. from genuine memory leaks):
ini_set('memory_limit', '-1');
Recompiling PHP, configured to use the native mysqlnd driver (included with PHP since v5.3.0, but not configured as the default until PHP v5.4.0) instead of libmysql:
./configure --with-mysqli=mysqlnd
Upgrading to PHP v5.4.0 or later so that mysqlnd is used by default.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With