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 台服务器都在同一主机上(源服务器和两台测试服务器) 重复测试时保持一致 内存中已经有类似的变量了,需要做对比去掉测试,与问题无关 CPU 一直处于 100% 的状态 两台服务器都有 32G RAM 和 swappiness 设置为 1,目标是将其作为内存操作执行 测试服务器是专用的,没有其他东西在运行 php.ini 在主要版本之间发生了变化,但与 mysqli/PDO 相关的所有选项似乎都相同

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 -iconfigure 命令会有所帮助。)引擎代码会根据此选择通过不同的路径。 【参考方案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错误并修复它?