5

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

1 回答 1

6

您会发现只有@statusisNULL或字符串时才会发生这种情况。

问题是双重的:

  1. 局部变量不同,MySQL用户变量支持一组非常有限的数据类型:

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

    文档没有提到实际使用的数据类型分别是BIGINTDECIMAL(65,30)DOUBLELONGBLOB和。关于最后一个,手册至少解释了:LONGTEXTLONGBLOB

    如果你引用一个没有被初始化的变量,它的值是 NULL 和一个字符串类型。

    前三种数据类型(即整数、十进制和浮点值)的存储分别需要 8、30 和 8 个字节其他数据类型(即字符串和NULL值)需要(最多)4 GB的存储空间。

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

您的选择包括:

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

    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;
    
  • 通过调用mysqli_stmt::store_result() before mysqli_stmt::bind_result()来解决这个问题,这会导致结果集存储在 libmysql 中(超出 PHP 的内存限制),然后 PHP 在获取记录时只会分配保存记录所需的实际内存:

    $stmt->execute();
    $stmt->store_result();
    $stmt->bind_result( $status );
    $stmt->fetch();
    
  • 提高 PHP 的内存限制,以便它可以适应 4GiB 缓冲区的分配(尽管应该意识到这样做对硬件资源的影响)——例如,完全消除内存限制(尽管要注意潜在的负面副作用从这样做,例如从真正的内存泄漏):

    ini_set('memory_limit', '-1');
    
  • 重新编译 PHP,配置为使用本地 mysqlnd 驱动程序(自 v5.3.0 起包含在 PHP 中,但直到 PHP v5.4.0 才配置为默认驱动程序)而不是 libmysql:

    ./configure --with-mysqli=mysqlnd
    
  • 升级到 PHP v5.4.0 或更高版本,以便默认使用 mysqlnd。

于 2013-07-07T06:49:44.100 回答