介绍
MySQL原生驱动程序的内存管理方式与MySQL客户端库不同。这些库在内存分配和释放方式、读取MySQL结果时内存的块分配方式、存在的调试和开发选项以及从MySQL读取的结果与PHP用户变量的链接方式上有所差异。
以下备注旨在为有兴趣从C代码层面理解MySQL原生驱动的用户提供介绍和总结。
使用的内存管理函数
所有内存分配和释放都使用PHP内存管理函数完成。因此,可以使用PHP API调用(例如memory_get_usage())来跟踪mysqlnd的内存消耗。由于内存是使用PHP内存管理进行分配和释放的,因此更改可能不会立即在操作系统级别可见。PHP内存管理充当代理,可能会延迟向系统释放内存。因此,比较MySQL原生驱动程序和MySQL客户端库的内存使用情况比较困难。MySQL客户端库直接使用操作系统内存管理调用,因此其影响可以在操作系统级别立即观察到。
PHP强制执行的任何内存限制也影响MySQL原生驱动程序。当获取超过PHP提供的可用内存大小的大型结果集时,这可能会导致内存不足错误。由于MySQL客户端库不使用PHP内存管理函数,因此它不符合任何设置的PHP内存限制。如果使用MySQL客户端库,则根据部署模型,PHP进程的内存占用量可能会超过PHP内存限制。但是,PHP脚本也可能能够处理更大的结果集,因为分配用于保存结果集的部分内存不受PHP引擎的控制。
MySQL原生驱动程序通过轻量级包装器调用PHP内存管理函数。除其他外,包装器使调试更容易。
结果集的处理
各种MySQL服务器和各种客户端API区分缓冲和非缓冲结果集。非缓冲结果集在客户端迭代结果时逐行从MySQL传输到客户端。缓冲结果在传递给客户端之前由客户端库完整获取。
MySQL原生驱动程序使用PHP流与MySQL服务器进行网络通信。MySQL发送的结果从PHP流网络缓冲区提取到mysqlnd的结果缓冲区中。结果缓冲区由zvals组成。在第二步中,结果将提供给PHP脚本。从结果缓冲区到PHP变量的最终传输会影响内存消耗,并且在使用缓冲结果集时最为明显。
默认情况下,MySQL原生驱动程序尝试避免在内存中两次保存缓冲结果。结果仅保存在内部结果缓冲区及其zvals中。当PHP脚本将结果提取到PHP变量中时,这些变量将引用内部结果缓冲区。数据库查询结果不会被复制,并且只在内存中保留一次。如果用户修改保存数据库结果的变量的内容,则必须执行写时复制以避免更改引用的内部结果缓冲区。缓冲区的内容不得修改,因为用户可能会决定第二次读取结果集。写时复制机制是使用附加的引用管理列表和标准zval引用计数器实现的。如果用户将结果集读取到PHP变量中并在取消设置变量之前释放结果集,也必须执行写时复制。
一般来说,这种模式对于只读取一次结果集并且不修改保存结果的变量的脚本非常有效。其主要缺点是由附加引用管理引起的内存开销,这主要源于保存结果的用户变量在mysqlnd引用管理停止引用它们之前无法完全释放。当结果集被释放或执行写时复制时,MySQL原生驱动程序会删除对用户变量的引用。观察者将看到总内存消耗会增长,直到结果集被释放。使用统计信息检查脚本是否显式释放结果集,或者驱动程序是否隐式释放结果集,以及内存是否使用了比必要时间更长的时间。统计信息还有助于查看发生了多少次写时复制操作。
使用与while ($row = $res->fetch_assoc()) { ... }
相等或等效的代码片段读取缓冲结果集的许多小行的PHP脚本可以通过请求副本而不是引用来优化内存消耗。尽管请求副本意味着在内存中保留两次结果,但它允许PHP在迭代结果集并且在释放结果集本身之前释放$row
中包含的副本。在负载较大的服务器上,优化峰值内存使用量可能有助于提高整体系统性能,尽管对于单个脚本而言,由于额外的分配和内存复制操作,复制方法可能较慢。
监控和调试
有多种方法可以跟踪MySQL原生驱动程序的内存使用情况。如果目标是快速获得高级概述或验证PHP脚本的内存效率,则检查库收集的统计信息。例如,统计信息允许您捕获生成的结果多于PHP脚本处理的结果的SQL语句。
可以配置调试跟踪日志以记录内存管理调用。这有助于查看何时分配或释放内存。但是,请求的内存块的大小可能不会列出。
一些最新版本的MySQL原生驱动程序具有模拟随机内存不足情况的功能。此功能仅供库或mysqlnd 插件的C开发人员使用。请在源代码中搜索相应的PHP配置设置和更多详细信息。此功能被认为是私有的,可能会随时修改,恕不另行通知。