mysqli_fetch_assoc() 性能 PHP5.4 与 PHP7.0
Posted
技术标签:
【中文标题】mysqli_fetch_assoc() 性能 PHP5.4 与 PHP7.0【英文标题】:mysqli_fetch_assoc() performance PHP5.4 vs PHP7.0 【发布时间】:2016-12-01 14:00:16 【问题描述】:我有大型 mysql 查询(180 万行,25 列),我需要从中创建二维数组(基于主键的内存表)。
代码按预期工作,但在 php7.0 中创建 $table 需要很长时间。
PHP7.0性能差这么多的原因是什么?我的主要兴趣是 mysqli。
感谢您提供任何见解 - 如果我可以修复性能,PHP7 会为我节省大量内存。
mysqli代码sn-p
$start = microtime(true);
$vysledek = cluster::query("SELECT * FROM `table` WHERE 1");
$query_time = (microtime(true) - $start);
$start_fetch = microtime(true);
while($zaznam = mysqli_fetch_assoc ( $vysledek ))
$fetch_time+= (microtime(true) - $start_fetch);
$start_assign = microtime(true);
$table[$zaznam['prikey']] = $zaznam;
$assign_time+= (microtime(true) - $start_assign);
$start_fetch = microtime(true);
$total_time+= (microtime(true) - $start);
echo round($assign_time, 2).' seconds to set the array values\n';
echo round($query_time, 2).' seconds to execute the query\n';
echo round($fetch_time, 2).' seconds to fetch data\n';
echo round($total_time, 2).' seconds to execute whole script\n';
echo "Peak Memory Usage:".round(memory_get_peak_usage(true)/(1024 * 1024), 2)." MB\n";
mysqli 结果
Deb 7 PHP 5.4 mysqlnd 5.0.10 1.8 秒设置数组值 8.37 秒执行查询 13.49 秒获取数据 24.42 秒执行整个脚本 峰值内存使用:8426.75 MB
Deb 8 PHP 5.6 mysqlnd 5.0.11-dev 1.7 秒设置数组值 8.58 秒执行查询 12.55 秒获取数据 23.6 秒执行整个脚本 峰值内存使用:8426.75 MB
Deb 8 PHP 7.0 mysqlnd 5.0.12-dev 0.73 秒设置数组值 8.63 秒执行查询126.71 秒获取数据 136.46 秒执行整个脚本 峰值内存使用:7394.27 MB
Deb 8 PHP 7.0 mysqlnd 5.0.12-dev 扩展基准测试
我已经扩展了部分获取的基准测试,以每 100k 行报告一次,结果如下:
在 1.87 秒内获取 100000 行 行在 5.24 秒内获取 300000 行在 10.97 秒内获取 500000 行数在 19.17 秒内获取 700000 行数在 29.96 秒内获取 900000 行在 43.03 秒内获取 1100000 行在 58.48 秒内获取 1300000 行在 76.47 秒内获取 1500000 行数在 96.73 秒内获取 1700000 在 107.78 秒内获取 1800000 行
DEB8 PHP7.1.0-dev libclient 5.5.50
1.56 秒设置数组值 8.38 秒执行查询456.52 秒获取数据 467.68 秒执行整个脚本 峰值内存使用量:8916 MB
DEB8 PHP7.1.0-dev libclient 5.5.50 扩展基准测试
在 2.72 秒内获取 100000 行 行在 15.7 秒内获取 300000 行在 38.7 秒内获取 500000 行在 71.69 秒内获取 700000 行数在 114.8 秒内获取 900000 在 168.18 秒内获取 1100000 行 行数在 231.69 秒内获取 1300000 行在 305.36 秒内获取 1500000 在 389.05 秒内获取 1700000 行 行数在 434.71 秒内获取 1800000
DEB8 PHP7.1.0-dev mysqlnd 5.0.12-dev
1.51 秒设置数组值 9.16 秒执行查询261.72 秒获取数据 273.61 秒执行整个脚本 峰值内存使用:8984.27 MB
DEB8 PHP7.1.0-dev mysqlnd 5.0.12-dev 扩展基准测试
在 3.3 秒内获取 100000 行 在 13.63 秒内获取 300000 行 行数在 29.02 秒内获取 500000 行数在 49.21 秒内获取 700000 行在 74.56 秒内获取 900000 行在 104.97 秒内获取 1100000 在 140.03 秒内获取 1300000 行 行在 180.42 秒内获取 1500000 行数在 225.72 秒内获取 1700000 行在 250.01 秒内获取 1800000
PDO 代码 sn-p
$start = microtime(true);
$sql = "SELECT * FROM `table` WHERE 1";
$vysledek = $dbh->query($sql, PDO::FETCH_ASSOC);
$query_time = (microtime(true) - $start);
$start_fetch = microtime(true);
foreach($vysledek as $zaznam)
$fetch_time+= (microtime(true) - $start_fetch);
$start_assign = microtime(true);
$table[$zaznam['prikey']] = $zaznam;
$assign_time+= (microtime(true) - $start_assign);
$start_fetch = microtime(true);
$total_time+= (microtime(true) - $start);
echo round($assign_time, 2).' seconds to set the array values\n';
echo round($query_time, 2).' seconds to execute the query\n';
echo round($fetch_time, 2).' seconds to fetch data\n';
echo round($total_time, 2).' seconds to execute whole script\n';
echo "Peak Memory Usage:".round(memory_get_peak_usage(true)/(1024 * 1024), 2)." MB\n";
PDO 结果
Deb 7 PHP 5.4 mysqlnd 5.0.10 1.85 秒设置数组值 12.51 秒执行查询 16.75 秒获取数据 31.82 秒执行整个脚本 峰值内存使用量:11417.5 MB
Deb 8 PHP 5.6 mysqlnd 5.0.11-dev 1.75 秒设置数组值 12.16 秒执行查询 15.72 秒获取数据 30.39 秒执行整个脚本 峰值内存使用:11417.75 MB
Deb 8 PHP 7.0 mysqlnd 5.0.12-dev 0.71 秒设置数组值35.93 秒执行查询 114.16 秒获取数据 151.19 秒执行整个脚本 峰值内存使用量:6620.29 MB
基线比较代码
$start_query = microtime(true);
exec("mysql --user=foo --host=1.2.3.4 --password=bar -e'SELECT * FROM `profile`.`table`' > /tmp/out.csv");
$query_time = (microtime(true) - $start_query);
echo round($query_time, 2).' seconds to execute the query \n';
所有系统的执行时间都相似,都是 19 秒 +-1 秒的变化。
基于上述观察,我会说 PHP 5.X 是合理的,因为执行的工作比仅仅转储到文件要多。
所有 3 台服务器都在同一主机上(源服务器和两台测试服务器) 重复测试时保持一致Deb8机器降级到PHP5.6后问题消失,重装PHP7后恢复
在 php.net - ID 72736 报告了一个错误,因为我相信已经证明问题出在 PHP 而不是系统或任何其他配置中
编辑 1:添加 PDO 比较
编辑 2:添加基准标记,编辑 PDO 结果,因为存在基准错误
编辑 3:主要清理原始问题,重建代码片段以更好地指示错误
Edit 4 : PHP 降级和升级的补充点
编辑 5:为 DEB8 PHP7.0 添加了扩展基准测试
编辑 6:包含 php7 配置
Edit 7:使用两个库对 PHP 7.1 开发人员进行性能测量 - 使用 bishop 的配置编译,删除了我的 php-config
编辑 8:添加了与 CLI 命令的比较,小幅清理
【问题讨论】:
只是好奇:你用 PDO 也试过了吗? 实际上它非常高效,在 PHP5.4 中,您可以在 40 - 60 秒内比较来自 2 个不同数据库的 2 个这样的表。同时对数据库的操作是非阻塞的,您可以进行单独的插入/删除/更新,原始应用程序可以一直在这些表之上运行。另一点是您可以在不同的服务器上执行此操作,因此即使耗尽内存也不会杀死目标数据库。php.ini
文件中的所有 mysqli 设置在 PHP7 中是否与在 PHP5 中相同?您是否为两者都安装了 x86 或 x64?如果将 PHP5 放在 Debian8 上,是否存在相同的性能差异?您是否在两台服务器上使用相同的 MySQL 驱动程序?
@MonkeyZeus php.ini 是相同的,服务器是(x64)并且只是克隆和升级,所以我可以使用 apt-get 来轻松安装 PHP7 ,其中 1 只是为了显示它的完整扫描- 可以省略,对查询没有影响
您使用的是libmysqlclient
还是mysqlnd
? (来自php -i
的configure
命令会有所帮助。)引擎代码会根据此选择通过不同的路径。
【参考方案1】:
由于问题出现在 fetch(不是数组创建)中,而且我们知道驱动程序正在运行 mysqlnd
(这是由 PHP 团队独立编写的驱动程序库,不是由 MySQL AB aka Oracle 提供的) ,然后使用libmysqlclient
(这是 MySQL AB aka Oracle 提供的接口)重新编译 PHP 可能会改善这种情况(或至少缩小问题空间)。
我建议的第一件事是编写一个可以从 CLI 运行的小脚本来演示问题。这将有助于消除任何其他变量(Web 服务器模块、opcache 等)。
然后,我建议使用libmysqlclient
重新构建 PHP,看看性能是否有所提高。重建 PHP 的快速指南(适用于技术熟练的人):
-
下载所需 PHP 版本的源代码
解压进入PHP代码目录
运行
./buildconf
运行./configure --prefix=/usr --with-config-file-path=/etc/php5/apache2 --with-config-file-scan-dir=/etc/php5/apache2/conf.d --build=x86_64-linux-gnu --host=x86_64-linux-gnu --sysconfdir=/etc --localstatedir=/var --mandir=/usr/share/man --enable-debug --disable-static --with-pic --with-layout=GNU --with-pear=/usr/share/php --with-libxml-dir=/usr --with-mysql-sock=/var/run/mysqld/mysqld.sock --enable-dtrace --without-mm --with-mysql=shared,/usr --with-mysqli=shared,/usr/bin/mysql_config --enable-pdo=shared --without-pdo-dblib --with-pdo-mysql=shared,/usr CFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -O2 -Wall -fsigned-char -fno-strict-aliasing -g" LDFLAGS="-Wl,-z,relro" CPPFLAGS="-D_FORTIFY_SOURCE=2" CXXFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security"
运行make && make test
走开
运行sapi/cli/php -i
并确认libmysqlclient的版本和存在
再次运行测试。有更好的吗?
【讨论】:
原帖已更新,您的编译指南中缺少很多步骤,但最终还是奏效了。我相信已经证明问题出在 mysqli_fetch_assoc() warper 函数中,而不是在 mysql 驱动程序中,libmysqlclient 的性能明显更差,而且它之前确实运行得更快。清理旧的 cmets,因为它们不相关 不管怎样,mysqlnd 是由 Ulf Wendel 共同开发的,他是一位在 MySQL AB 工作的工程师,现在在 Oracle 工作。 blog.ulf-wendel.de/2007/… 好的,现在作为基线,如果您使用命令行mysql
客户端执行相同的提取,需要多长时间?
目前我无法测试它,因为相关表中的数据现在已经消失(与此问题无关的原因)我在不同的表上进行了测试(稍微小一点)作为中间步骤,两者的性能相同主机。它的 cca 比 PHP5.X 版本快 30%。一旦恢复了主要测试数据(可能明天),当我可以使用相同的数据样本时,我将更新原始帖子。【参考方案2】:
供交叉参考:随着 2016 年 12 月 1 日 PHP 7.1 的发布,这个问题应该得到解决(在 PHP 7.1 中)。
PHP 7.0:即使在票证上写着 PHP-7.0 已被修补,我还没有在最近的更改日志(7.0.13 on 10 Nov 2016,自修补程序合并日期起)中看到这是当前 PHP 7.0.x 版本的一部分。也许在下一个版本中。
在上游跟踪错误(感谢 OP 的报告):Bug #72736 - Slow performance when fetching large dataset with mysqli / PDO(bugs.php.net;2016 年 8 月)。
【讨论】:
以上是关于mysqli_fetch_assoc() 性能 PHP5.4 与 PHP7.0的主要内容,如果未能解决你的问题,请参考以下文章
mysqli_fetch_assoc (& PDO fetch assoc) 将数字存储为字符串
mysqli_fetch_assoc() 期望参数/调用成员函数 bind_param() 错误。如何获得实际的mysql错误并修复它?
mysqli_fetch_assoc() 期望参数/调用成员函数 bind_param() 错误。如何获得实际的mysql错误并修复它?
mysqli_fetch_assoc() 期望参数/调用成员函数 bind_param() 错误。如何获得实际的mysql错误并修复它?
mysqli_fetch_assoc() 期望参数/调用成员函数 bind_param() 错误。如何获得实际的mysql错误并修复它?
mysqli_fetch_assoc() 期望参数/调用成员函数 bind_param() 错误。如何获得实际的mysql错误并修复它?