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 28 PHP
PHP 命名空间实例说明
Jan 27 PHP
php获取通过http协议post提交过来xml数据及解析xml
Dec 16 PHP
数组与类使用PHP的可变变量名需要的注意的问题
Jun 20 PHP
php树型类实例
Dec 05 PHP
php批量删除cookie的简单实现方法
Jan 26 PHP
php实现用手机关闭计算机(电脑)的方法
Apr 22 PHP
编写PHP脚本清除WordPress头部冗余代码的方法讲解
Mar 01 PHP
thinkPHP5.0框架API优化后的友好性分析
Mar 17 PHP
PHP微信公众号开发之微信红包实现方法分析
Jul 14 PHP
彻底搞懂PHP 变量结构体
Oct 11 PHP
PHP标准库(PHP SPL)详解
Mar 16 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
深入Apache与Nginx的优缺点比较详解
2013/06/17 PHP
解析php中的fopen()函数用打开文件模式说明
2013/06/20 PHP
跟我学Laravel之请求与输入
2014/10/15 PHP
2017年最新PHP经典面试题目汇总(上篇)
2017/03/17 PHP
javascript Select标记中options操作方法集合
2008/10/22 Javascript
A标签中通过href和onclick传递的this对象实现思路
2013/04/19 Javascript
Javscript删除数组中指定元素并返回新数组
2014/03/06 Javascript
javascript闭包传参和事件的循环绑定示例探讨
2014/04/17 Javascript
JavaScript定义变量和变量优先级问题探讨
2014/10/11 Javascript
angularJS 中$scope方法使用指南
2015/02/09 Javascript
JavaScript实现DIV层拖动及动态增加新层的方法
2015/05/12 Javascript
原生js更改css样式的两种方式
2017/03/15 Javascript
AngularJS基于provider实现全局变量的读取和赋值方法
2017/06/28 Javascript
微信小程序商品详情页的底部弹出框效果
2020/11/16 Javascript
详解js中let与var声明变量的区别
2020/04/05 Javascript
javascript中的this作用域详解
2019/07/15 Javascript
Vue插槽_特殊特性slot,slot-scope与指令v-slot说明
2020/09/04 Javascript
Python爬虫框架Scrapy安装使用步骤
2014/04/01 Python
浅谈python的输入输出,注释,基本数据类型
2019/04/02 Python
Django之使用内置函数和celery发邮件的方法示例
2019/09/16 Python
TensorFlow tf.nn.softmax_cross_entropy_with_logits的用法
2020/04/19 Python
详解python polyscope库的安装和例程
2020/11/13 Python
matplotlib实现数据实时刷新的示例代码
2021/01/05 Python
澳大利亚最受欢迎的美发用品目的地:AMR
2019/08/28 全球购物
递归计算如下递归函数的值(斐波拉契)
2012/02/04 面试题
安全生产承诺书范文
2014/05/22 职场文书
财务管理专业毕业生求职信
2014/06/02 职场文书
动画设计系毕业生求职信
2014/07/15 职场文书
公司领导班子群众路线四风问题对照检查材料
2014/10/02 职场文书
2015年社区纪检工作总结
2015/04/21 职场文书
教师调动申请报告
2015/05/18 职场文书
公司考勤管理制度
2015/08/04 职场文书
外出培训学习心得体会
2016/01/18 职场文书
css实现文章分割线样式的多种方法总结
2021/04/21 HTML / CSS
前端实现滑动按钮AJAX与后端交互的示例代码
2022/02/24 Javascript
Vertica集成Apache Hudi重磅使用指南
2022/03/31 Servers