PHP并发查询MySQL的实例代码


Posted in PHP onAugust 09, 2017

最近在研究PHP,很喜欢,碰到PHP并发查询MySQL的问题,研究了一下,顺便留个笔记:

同步查询

这是我们最常的调用模式,客户端调用Query[函数],发起查询命令,等待结果返回,读取结果;再发送第二条查询命令,等待结果返回,读取结果。总耗时,会是两次查询的时间之和。简化一下过程,例如下图:

PHP并发查询MySQL的实例代码 

例图,由1.1到1.3为一个Query[函数]的调用,两次查询,就要串行经历1.1、1.2、1.3、2.1、2.2、2.3,尤其在1.2和2.2会阻塞等待,进程没法做其他事情。

同步调用的好处是,符合我们的直观思维,调用和处理都简单。缺点是进程阻塞在等待结果返回,增加额外的运行时间。
如果,有多条查询请求,或者进程还有其他的事情处理,那么能否把等待的时间也合理利用起来,提高进程的处理能力呢,显然是可以的。

拆分

现在,我们把Query[函数]打碎,客户端在1.1后,马上返回,客户端跳过1.2,在1.3有数据达到后再去读取数据。这样进程在原来的1.2阶段就解放了,可以做更多的事情,例如…再发起一条sql查询[2.1],是否看到了并发查询的雏形了。

并发查询

相对于同步查询的下一条查询的发起都在上一条完成后,并发查询,可以在上一条查询请求发起后,立刻发起下一条查询请求。简化一下过程,下图:

PHP并发查询MySQL的实例代码 

例图,在1.1.1成功发送完请求后,立马返回[1.1.2],最终查询结果的返回时在遥远的1.2 。但是在,1.1.1到1.2中间,还发起了另一个查询请求,这时间段内,就同时发起了两条查询请求,2.2先于1.2到达,那么两条查询的总耗时,只相当于第一条查询的时间。

并发查询的优点是,可以提高进程的使用率,避免阻塞等待服务器处理查询,缩短了多条查询的耗时。但缺点也很明显,发起N条并发查询,就需要建立N条数据库链接,对于有数据库连接池的应用来说,可以避免这种情况。

退化

理想情况下,我们希望并发N条查询,总耗时等于查询时间最长的一条查询。但也有可能并发查询会[退化]为[同步查询]。What?例图中,如果1.2在2.1.1前就返回了,那么并发查询就[退化]为[同步查询]了,但付出的代价却比同步查询要高。

多路复用

  • 发起query1
  • 发起query2
  • 发起query3
  • ………
  • 等待query1、query2、query3
  • 读取query2结果
  • 读取query1结果
  • 读取query3结果

那么,怎么等待知道什么时候查询结果返回了,又是哪个的查询结果返回呢?

对每个查询IO调用read?如果是遇上阻塞IO,这样就会阻塞在一个IO上,其他IO有结果返回了,也没法处理。那么,如果是非阻塞IO,那不用怕会阻塞在其中一个IO上了,确实是,但又会造成不断地轮询判断,浪费CPU资源。

对于这种情况可以使用多路复用轮询多个IO。

PHP实现并发查询MySQL

PHP的mysqli(mysqlnd驱动)提供多路复用轮询IO(mysqli_poll)和异步查询(MYSQLI_ASYNC、mysqli_reap_async_query),使用这两个特性实现并发查询,示例代码:

<?php
 $sqls = array(
  'SELECT * FROM `mz_table_1` LIMIT 1000,10',
  'SELECT * FROM `mz_table_1` LIMIT 1010,10',
  'SELECT * FROM `mz_table_1` LIMIT 1020,10',
  'SELECT * FROM `mz_table_1` LIMIT 10000,10',
  'SELECT * FROM `mz_table_2` LIMIT 1',
  'SELECT * FROM `mz_table_2` LIMIT 5,1'
 );
 $links = [];
 $tvs = microtime();
 $tv = explode(' ', $tvs);
 $start = $tv[1] * 1000 + (int)($tv[0] * 1000);
 // 链接数据库,并发起异步查询
 foreach ($sqls as $sql) { 
  $link = mysqli_connect('127.0.0.1', 'root', 'root', 'dbname', '3306');
  $link->query($sql, MYSQLI_ASYNC); // 发起异步查询,立即返回
  $links[$link->thread_id] = $link;
 }
 $llen = count($links);
 $process = 0;
 do {
  $r_array = $e_array = $reject = $links;
  // 多路复用轮询IO
  if(!($ret = mysqli_poll($r_array, $e_array, $reject, 2))) {
   continue;
  }
  // 读取有结果返回的查询,处理结果
  foreach ($r_array as $link) {
   if ($result = $link->reap_async_query()) {
    print_r($result->fetch_row());
    if (is_object($result))
     mysqli_free_result($result);
   } else {
   }
   // 操作完后,把当前数据链接从待轮询集合中删除
   unset($links[$link->thread_id]);
   $link->close();
   $process++;
  }
  foreach ($e_array as $link) {
   die;
  }
  foreach ($reject as $link) {
   die;
  }
 }while($process < $llen);
 $tvs = microtime();
 $tv = explode(' ', $tvs);
 $end = $tv[1] * 1000 + (int)($tv[0] * 1000);
 echo $end - $start,PHP_EOL;

mysqli_poll源码:

#ifndef PHP_WIN32
#define php_select(m, r, w, e, t) select(m, r, w, e, t)
#else
#include "win32/select.h"
#endif
/* {{{ mysqlnd_poll */
PHPAPI enum_func_status
mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, int * desc_num)
{
 struct timeval tv;
 struct timeval *tv_p = NULL;
 fd_set   rfds, wfds, efds;
 php_socket_t max_fd = 0;
 int    retval, sets = 0;
 int    set_count, max_set_count = 0;
 DBG_ENTER("_mysqlnd_poll");
 if (sec < 0 || usec < 0) {
  php_error_docref(NULL, E_WARNING, "Negative values passed for sec and/or usec");
  DBG_RETURN(FAIL);
 }
 FD_ZERO(&rfds);
 FD_ZERO(&wfds);
 FD_ZERO(&efds);
 // 从所有mysqli链接中获取socket链接描述符
 if (r_array != NULL) {
  *dont_poll = mysqlnd_stream_array_check_for_readiness(r_array);
  set_count = mysqlnd_stream_array_to_fd_set(r_array, &rfds, &max_fd);
  if (set_count > max_set_count) {
   max_set_count = set_count;
  }
  sets += set_count;
 }
 // 从所有mysqli链接中获取socket链接描述符
 if (e_array != NULL) {
  set_count = mysqlnd_stream_array_to_fd_set(e_array, &efds, &max_fd);
  if (set_count > max_set_count) {
   max_set_count = set_count;
  }
  sets += set_count;
 }
 if (!sets) {
  php_error_docref(NULL, E_WARNING, *dont_poll ? "All arrays passed are clear":"No stream arrays were passed");
  DBG_ERR_FMT(*dont_poll ? "All arrays passed are clear":"No stream arrays were passed");
  DBG_RETURN(FAIL);
 }
 PHP_SAFE_MAX_FD(max_fd, max_set_count);
 // select轮询阻塞时间
 if (usec > 999999) {
  tv.tv_sec = sec + (usec / 1000000);
  tv.tv_usec = usec % 1000000;
 } else {
  tv.tv_sec = sec;
  tv.tv_usec = usec;
 }
 tv_p = &tv;
 // 轮询,等待多个IO可读,php_select是select的宏定义
 retval = php_select(max_fd + 1, &rfds, &wfds, &efds, tv_p);
 if (retval == -1) {
  php_error_docref(NULL, E_WARNING, "unable to select [%d]: %s (max_fd=%d)",
      errno, strerror(errno), max_fd);
  DBG_RETURN(FAIL);
 }
 if (r_array != NULL) {
  mysqlnd_stream_array_from_fd_set(r_array, &rfds);
 }
 if (e_array != NULL) {
  mysqlnd_stream_array_from_fd_set(e_array, &efds);
 }
 // 返回可操作的IO数量
 *desc_num = retval;
 DBG_RETURN(PASS);
}

并发查询操作结果

为了更直观地看效果,我找了一个1.3亿数据量并且没有优化过的表进行操作。

并发查询的结果:

PHP并发查询MySQL的实例代码 

同步查询的结果:

PHP并发查询MySQL的实例代码 

从结果来看,同步查询的总耗时是所有查询的时间的累加;而并发查询的总耗时在这里其实是查询时间最长的那一条(同步查询的第四条,耗时是10几秒,符合并发查询的总耗时),而且并发查询的查询顺序和结果到达的顺序是不一样的。

多条耗时较短的查询对比

使用多条查询时间较短的sql进行对比一下

并发查询的测试1结果(数据库链接时间也统计进去):

PHP并发查询MySQL的实例代码 

同步查询的结果(数据库链接时间也统计进去):

PHP并发查询MySQL的实例代码 

并发查询的测试2结果(不统计数据库链接时间):

PHP并发查询MySQL的实例代码 

从结果上看,并发查询测试1并没有讨到好处。从同步查询上看,每条查询耗时大概3-4ms左右。但如果不把数据库链接时间统计进去(同步查询只有一次数据库链接),并发查询的优势又能体现出来了。

结语

这里探讨了一下PHP实现并发查询MySQL,从实验上结果直观地认识了并发查询的优缺点。建立数据库连接的时间在一条优化了的sql查询上,占得比重还是很大。#没有连接池,要你何用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
如何使用PHP中的字符串函数
Nov 24 PHP
兼容PHP5的PHP目录管理函数库
Jul 10 PHP
用穿越火线快速入门php面向对象
Feb 22 PHP
通过PHP的内置函数,通过DES算法对数据加密和解密
Jun 21 PHP
php动态实现表格跨行跨列实现代码
Nov 06 PHP
使用php计算排列组合的方法
Nov 13 PHP
php分页函数完整实例代码
Sep 22 PHP
php上传文件问题汇总
Jan 30 PHP
php微信公众账号开发之前五个坑(一)
Sep 18 PHP
Thinkphp 中 distinct 的用法解析
Dec 14 PHP
Laravel框架执行原生SQL语句及使用paginate分页的方法
Aug 17 PHP
PHP xpath()函数讲解
Feb 11 PHP
Yii2框架中使用PHPExcel导出Excel文件的示例
Aug 09 #PHP
PHP实现更改hosts文件的方法示例
Aug 08 #PHP
PHP编程实现阳历转换为阴历的方法实例
Aug 08 #PHP
PHP数据分析引擎计算余弦相似度算法示例
Aug 08 #PHP
Eclipse PHPEclipse 配置的具体步骤
Aug 08 #PHP
PHP 文件锁与进程锁的使用示例
Aug 07 #PHP
PHP实现找出有序数组中绝对值最小的数算法分析
Aug 07 #PHP
You might like
咖啡的种类和口感
2021/03/03 新手入门
php 上传文件类型判断函数(避免上传漏洞 )
2010/06/08 PHP
Drupal7中常用的数据库操作实例
2014/03/02 PHP
php去掉文件前几行的方法
2015/07/29 PHP
PHP简单获取及判断提交来源的方法
2016/04/22 PHP
PHP微信开发之查询微信精选文章
2016/06/23 PHP
thinkphp中AJAX返回ajaxReturn()方法分析
2016/12/06 PHP
PHP封装的分页类与简单用法示例
2019/02/25 PHP
改版了网上的一个js操作userdata
2007/04/27 Javascript
javascript据option的value值快速设定初始的selected选项
2007/08/13 Javascript
jQuery.Validate 使用笔记(jQuery Validation范例 )
2010/06/25 Javascript
jqeury eval将字符串转换json的方法
2011/01/20 Javascript
经过绑定元素时会多次触发mouseover和mouseout事件
2014/02/28 Javascript
jQuery实现简单网页遮罩层/弹出层效果兼容IE6、IE7
2014/06/16 Javascript
使用不同的方法结合/合并两个JS数组
2014/09/18 Javascript
javascript使用smipleChart实现简单图表
2015/01/02 Javascript
JavaScript优化专题之Loading and Execution加载和运行
2016/01/20 Javascript
Ionic实现页面下拉刷新(ion-refresher)功能代码
2016/06/03 Javascript
jQuery实现的自定义弹出层效果实例详解
2016/09/04 Javascript
BootStrap与validator 使用笔记(JAVA SpringMVC实现)
2016/09/21 Javascript
微信小程序 确认框的实现(附代码)
2019/07/23 Javascript
通过实例了解Nodejs模块系统及require机制
2020/07/16 NodeJs
python格式化字符串实例总结
2014/09/28 Python
Python中的各种装饰器详解
2015/04/11 Python
在Python中操作时间之mktime()方法的使用教程
2015/05/22 Python
一个基于flask的web应用诞生 bootstrap框架美化(3)
2017/04/11 Python
python列表每个元素同增同减和列表元素去空格的实例
2019/07/20 Python
Python 序列化和反序列化库 MarshMallow 的用法实例代码
2020/02/25 Python
Django ORM 查询表中某列字段值的方法
2020/04/30 Python
css3.0 图形构成实例练习二
2013/03/19 HTML / CSS
CSS3 实现的加载动画
2020/12/07 HTML / CSS
教师现实表现材料
2014/02/14 职场文书
化验室岗位职责
2015/02/14 职场文书
党支部书记岗位职责
2015/02/15 职场文书
MySQL外键约束(FOREIGN KEY)案例讲解
2021/08/23 MySQL
nginx 配置指令之location使用详解
2022/05/25 Servers