PHP并发查询MySQL的实例代码

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

同步查询

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

例图,由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],是否看到了并发查询的雏形了。

并发查询

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

例图,在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亿数据量并且没有优化过的表进行操作。

并发查询的结果:

同步查询的结果:

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

多条耗时较短的查询对比

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

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

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

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

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

结语

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

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

(0)

相关推荐

  • php查询mysql数据库并将结果保存到数组的方法

    本文实例讲述了php查询mysql数据库并将结果保存到数组的方法.分享给大家供大家参考.具体分析如下: 这里主要用到了mysql_fetch_assoc函数 mysql_fetch_assoc语法如下: array mysql_fetch_assoc (resource $Result_Set) 范例代码如下: <?php $UserName = 'abc'; $Password = '1234'; $DbHandle = mysql_connect ('localhost', $UserNam

  • PHP将MySQL的查询结果转换为数组并用where拼接的示例

    mysql查询结果转换为PHP数组的几种方法的区别:  $result = mysql_fetch_row():这个函数返回的是数组,数组是以数字作为下标的,你只能通过$result[0],$Result[2]这样的形式来引用. $result = mysql_fetch_assoc():这个函数返回是以字段名为下标的数组,只能通过字段名来引用.$result['field1']. $result = mysql_fetch_array():这个函数返回的是一个混合的数组,既可以通过数字下标来引

  • php访问查询mysql数据的三种方法

    1. $row = mysql_fetch_row($result); 返回一个规则的数组$row,$row[0]是第一个元素,$row[1]是第二个元素,依次类推... mysql_num_fields($result) 返回结果的元素个数. 2. $row = mysql_fetch_array($result); 返回一个数组$row. 举例如下: 表结构如下: username | password ------------------------------------- bourbo

  • 关于php操作mysql执行数据库查询的一些常用操作汇总

    php操作mysql步骤:1.$connect=mysql_connect('localhost','root','123456') or die('数据库连接失败.'mysql_error());链接mysql.2.mysql_select_db('database',$connect)选择链接的数据库.3.mysql_query('Set names gb2312');$sql = "select * from blog_article";准备要查询的数据.4.$datas = m

  • php简单解析mysqli查询结果的方法(2种方法)

    本文实例讲述了php简单解析mysqli查询结果的方法.分享给大家供大家参考,具体如下: 可将查询结果放入对象或数组中: 1. 将查询结果放入对象: $sql="select name,brief from cars"; $result=mysqli->query($sql); while($row=$result->fetch_object()) { echo $row->name; echo $row->brief; } 2. 放入数组: $sql=&quo

  • php基础之连接mysql数据库和查询数据

    连接数据库,有三种方法 1. 常规方式: $con=mysql_connect($dbhostip,$username,$userpassword) or die("Unable to connect to the MySQL!"); $db = mysql_select_db($dbdatabasename,$con); //执行语句 $qres=mysql_query("SELECT id,GoodsName FROM user"); //提取一条数据 11 $

  • Php中用PDO查询Mysql来避免SQL注入风险的方法

    当我们使用传统的 mysql_connect .mysql_query方法来连接查询数据库时,如果过滤不严,就有SQL注入风险,导致网站被攻击,失去控制.虽然可以用mysql_real_escape_string()函数过滤用户提交的值,但是也有缺陷.而使用PHP的PDO扩展的 prepare 方法,就可以避免sql injection 风险. PDO(PHP Data Object) 是PHP5新加入的一个重大功能,因为在PHP 5以前的php4/php3都是一堆的数据库扩展来跟各个数据库的连

  • PHP并发查询MySQL的实例代码

    最近在研究PHP,很喜欢,碰到PHP并发查询MySQL的问题,研究了一下,顺便留个笔记: 同步查询 这是我们最常的调用模式,客户端调用Query[函数],发起查询命令,等待结果返回,读取结果:再发送第二条查询命令,等待结果返回,读取结果.总耗时,会是两次查询的时间之和.简化一下过程,例如下图: 例图,由1.1到1.3为一个Query[函数]的调用,两次查询,就要串行经历1.1.1.2.1.3.2.1.2.2.2.3,尤其在1.2和2.2会阻塞等待,进程没法做其他事情. 同步调用的好处是,符合我们

  • java实现连接mysql数据库单元测试查询数据的实例代码

    1.按照javaweb项目的要求逐步建立搭建起机构,具体的类包有:model .db.dao.test; 具体的架构详见下图: 2.根据搭建的项目架构新建数据库test和数据库表t_userinfo并且添加对应的测试数据; (这里我使用的是绿色版的数据库,具体的下载地址:http://pan.baidu.com/s/1mg88YAc) 具体的建立数据库操作详见下图: 3.编写包中的各种类代码,具体参考代码如下: UserInfo.java /** * FileName: UserInfo.jav

  • python自动结束mysql慢查询会话的实例代码

    生产环境的有些sql查询写得太复杂,或是表很大,对应索引未建立或建立不合理,或是查询未充分使用索引等,就有可能出现慢查询,一些慢查询需要修改程序,可能没那么快能解决,这时如果有个脚本能自动检测符合条件的慢查询会话并结束,那么是很方便的,当然运维人员也可顺便弄个检测慢查询并告警的脚本. 涉及知识点 mysql慢查询会话查询 schedule定时任务调度 pymysql执行sql 代码分解 mysql慢查询 #会话查询,只能查询所有会话,不能按条件过滤,不过比较好记 show PROCESSLIST

  • php mysql 封装类实例代码

    废话不多说了,具体代码如下所示: <?php class mysql { private $db_host; //数据库主机 private $db_user; //数据库用户名 private $db_pwd; //数据库用户名密码 private $db_database; //数据库名 private $conn; //数据库连接标识; private $result; //执行query命令的结果资源标识 private $sql; //sql执行语句 private $row; //返

  • Angularjs添加排序查询功能的实例代码

    废话不多说了,直接给大家贴代码了,具体代码如下所示: <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="angular-1.3.0.js"></script&g

  • 运用JSP+ajax实现分类查询功能的实例代码

    本文主要是介绍了运用JSP+ajax实现分类查询功能的实例代码,分享给大家供大家参考,具体如下: 这次是对小学期关于大学毕业生信息管理系统课程设计中遇到的一些问题和解决方法进行记录和分享. 题目要求:提供企业招聘信息(本年度或历年)查询功能: 首先建立一个jsp文件用来显示数据库信息,并在上面加输入条件的文本框和查询按钮,在这个jsp文件中需要完成一个js函数在用于完成查询功能. <%@ page language="java" pageEncoding="UTF-8&

  • mybatis interceptor 处理查询参数及查询结果的实例代码

    下面给大家介绍mybatis interceptor 处理查询参数及查询结果,具体代码如下所示: /** * Created by windwant on 2017/1/12. */ @Intercepts({ @Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}), @Signature(type=Executor.class,method="quer

  • Android Okhttp请求查询购物车的实例代码

    查询购物车的model层 public class SelectCarModel { private String url="http://120.27.23.105/product/getCarts"; private HashMap<String, String> map = new HashMap<>(); public void verifySelectCarInfo(int uid, final ISelectCarPresenter iSelectC

  • python通过elixir包操作mysql数据库实例代码

    本文研究的主要是python通过elixir包操作mysql数据库的相关实例,具体如下. python操作数据库有很多方法,下面介绍elixir来操作数据库.elixir是对sqlalchemy lib的一个封装,classes和tables是一一对应的,能够一步定义classes,tables和mappers,支持定义多个primary key. 定义model.py from elixir import sqlalchemy from elixir import * engine =sqla

  • PHP连接MYSQL数据库实例代码

    现在做的项目需要php连接mysql数据库,虽然之前学过,但是现在基本上都给忘了,之后通过查找相关资料找到了解决方法,下面小编把具体方法分享在我们平台供大家学习. 具体代码如下所示: <?php //设置字符集 header('Content-Type: text/html; charset=utf8'); //连接数据库 $link = mysql_connect("localhost","root","123456"); if(!$li

随机推荐