PHP mysql_stmt :: fetch()使PHP致命错误内存耗尽

时间:2013-07-07 05:22:56

标签: php mysql centos fetch

CentOS 6.4 PHP 5.3.3 MySQL 5.1.69 x86_64

mysql_stmt::fetch()

使用预准备语句执行fetch时,PHP会产生错误:PHP致命错误:允许的内存大小为134217728字节(尝试分配4294967296字节)。

当用于创建临时表的SELECT语句中包含的变量未设置时,无论是否在调用存储过程之前在环境中设置该变量,都会发生这种情况。必须在存储过程中设置变量。当SELECT语句用于将临时表中的数据返回给PHP,并且PHP使用mysql_stmt :: fetch()来访问数据时,PHP会生成上述致命错误。

MySQL代码:

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代码:

// 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";

1 个答案:

答案 0 :(得分:5)

@statusNULL或字符串时,您会发现

问题是双重的:

  1. local variables不同,MySQL user variables支持一组非常有限的数据类型:

      

    可以从一组有限的数据类型中为用户变量分配值:整数,十进制,浮点,二进制或非二进制字符串或NULL值。

    文档未提及所使用的actual datatypes分别为BIGINTDECIMAL(65,30)DOUBLELONGBLOBLONGTEXT和{{ 1}}。关于最后一本,手册至少解释了:

      

    如果引用尚未初始化的变量,则其值为NULL且字符串类型。

    前三种数据类型中的

    Storage(即整数,十进制和浮点值)分别需要8,30和8个字节。其他数据类型(即字符串和LONGBLOB值)需要(最多) 4千兆字节的存储空间。

  2. 由于您使用的是v5.4.0之前的PHP版本,因此默认的MySQL驱动程序为libmysql,在数据绑定时只有服务器可以使用列类型元数据 - 所以MySQLi尝试分配足够的内存来保存每个可能的值(即使最终不需要完整的缓冲区);因此NULL - 和字符串值的用户变量,最大可能的大小为4GiB,导致PHP超过其默认内存限制(自PHP v5.2.0起为128MiB)。

  3. 您的选择包括:

    • 覆盖表定义中的列数据类型:

      NULL
    • 将用户变量显式casting更具体的数据类型:

      DROP TEMPORARY TABLE IF EXISTS tmp_table;
      CREATE TEMPORARY TABLE tmp_table (
        status VARCHAR(2)
      ) SELECT @status AS status;
      
    • 使用使用显式数据类型声明的局部变量:

      DROP TEMPORARY TABLE IF EXISTS tmp_table;
      CREATE TEMPORARY TABLE tmp_table
        SELECT CAST(@status AS CHAR(2)) AS status;
      
    • 通过调用DECLARE status VARCHAR(2) DEFAULT @status; DROP TEMPORARY TABLE IF EXISTS tmp_table; CREATE TEMPORARY TABLE tmp_table SELECT status; before mysqli_stmt::store_result()解决此问题,导致结果集存储在libmysql中(超出PHP的内存限制),然后PHP将只分配获取记录时保存记录所需的实际内存:

      mysqli_stmt::bind_result()
    • 提升PHP memory limit以便它可以容纳4GiB缓冲区的分配(尽管应该知道这样做会对硬件资源产生影响) - 例如,完全删除内存限制(虽然要注意这样做的潜在负面影响,例如来自真正的内存泄漏):

      $stmt->execute();
      $stmt->store_result();
      $stmt->bind_result( $status );
      $stmt->fetch();
      
    • 重新编译PHP,配置为使用the native mysqlnd driver(自v5.3.0起包含在PHP中,但在PHP v5.4.0之前未配置为默认值)而不是libmysql:

      ini_set('memory_limit', '-1');
      
    • 升级到PHP v5.4.0或更高版本,以便默认使用mysqlnd。