【发布时间】:2012-08-08 11:21:20
【问题描述】:
在优化我的 Apache + PHP 内存使用时,我偶然发现了一个奇怪的问题。基本上,当尝试绑定 MySQLi 查询的结果时,代码会出现错误消息“致命错误:允许的内存大小为 16777216 字节已用尽(尝试分配 50331646 字节)”。
相关表格有:
CREATE TABLE `note` (
`noteID` int(11) NOT NULL AUTO_INCREMENT,
`contentID` int(11) NOT NULL,
`text` mediumtext NOT NULL,
PRIMARY KEY (`noteID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `content` (
`contentID` int(11) NOT NULL AUTO_INCREMENT,
`text` varchar(2048) NOT NULL,
`datestamp` datetime NOT NULL,
PRIMARY KEY (`contentID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
而爆炸的查询是:
select content.contentID,
content.text,
content.datestamp,
note.noteID,
note.contentID, note.text
from basereality.content as content
inner join basereality.note as note
on content.contentID = note.contentID
where content.contentID = 1028 ;
在服务器上的 MySQL 中运行查询运行良好,返回的 'note' 大小小于一千字节。
一个奇怪的事情是它试图分配 50331646 的大小是十六进制的 0x2FFFFFE,这是一个可疑的整数。这几乎就像 PHP 试图分配一个足够大的缓冲区来容纳可能的最大的 mediumtext 字段,而不是实际为检索到的数据分配内存。
除了增加 PHP 中允许的最大内存之外,任何人都知道解决方法吗?
顺便说一句,其他人也有同样的问题,但似乎不知道如何解决。 Out of memory (allocated 50855936) (tried to allocate 50331646 bytes)
爆炸的确切线路是:
$statement->bind_result(
$content_contentID,
$content_text,
$content_datestamp,
$note_noteID,
$note_contentID,
$note_text);
将查询更改为选择“'A test string' as note”,而不是获取列不再显示大量内存使用情况。
顺便说一句,我确定我不是在尝试检索大数据。这显示了 note 表中 mediumtext 字段的实际长度:
select length(text) from basereality.note;
+--------------+
| length(text) |
+--------------+
| 938 |
| 141 |
| 1116 |
| 431 |
| 334 |
+--------------+
【问题讨论】:
-
您能显示出现此错误的 PHP 代码的确切行吗?
-
完成 - 不要认为它会有所帮助。
-
您是否 1000% 确定您要获取的所有数据库字段都不大于 50MB?
-
我进行了一个简短的测试来重现这一点:Windows 上的 PHP 5.3 启用
mysqlnd作为数据库底层连接器的替代品,没有错误或增加内存使用量。 Linux 上的 PHP 5.3 默认不启用它,并且观察到内存使用量增加(它没有崩溃,因为 memory_limit 远高于 16 MB,但内存使用量约为 17 MB)。阅读 PHP 源代码让我想到在$stmt->execute()之前尝试$stmt->attr_set(MYSQLI_STMT_ATTR_UPDATE_MAX_LENGTH, 1);,这使内存使用率恢复到正常水平。 -
DCoder - 我也检查了 MySQLi 源。该属性实际上是 long 而不是 bool bugs.mysql.com/bug.php?id=16144 但 PHP 显然不支持将其设置为 1 以外的任何值。 bugs.php.net/bug.php?id=62798