FROM : 通过协程实现mysql查询的异步化

前言

最近学习了赵海平的演讲,了解到facebook的mysql查询可以进行异步化,从而提高性能。由于facebook实现的比较早,他们不得不对php进行hack才得以实现。现在的php5.5,已经无需hack就可以实现了。
对于一个web网站的性能来说,瓶颈多半是来自于数据库。一般数据库查询会在某个请求的整体耗时中占很大比例。如果能提高数据库查询的效率,网站的整体响应时间会有很大的下降。如果能实现mysql查询的异步化,就可以实现多条sql语句同时执行。这样就可以大大缩短mysql查询的耗时。

异步为啥比同步快?

与异步查询相反的时同步查询。通常情况下mysql的query查询都是同步方式。下面我们对两种方式做下对比。对比的例子是,请求两次select sleep(1)。这条语句在mysql服务器端大概耗时1000ms。

同步方式的执行流程:

第一步,向mysql服务器端发送第一次查询请求。大概耗时 1ms
第二步,mysql服务器端返回第一次查询的结果。大概耗时 1000ms
第三步,向mysql服务器再次发送请求。大概耗时 1ms
第四步,mysql服务器端返回第二次查询的结果。大概耗时 1000ms
同步的方式执行两次select sleep(1),大概耗时 2002ms。

异步方式的执行流程:

第一步,向mysql服务器端发送第一次查询请求。大概耗时1ms
第二步,在等待第一次请求返回数据的同时,向服务器端发送第二次查询请求。大概耗时 1ms
第三步,接受mysql服务器端返回的两次查询请求。大概耗时 1000ms。

对比分析
异步查询比同步查询速度快,是因为多条查询语句在服务器端同时执行,大大缩短了服务器端的响应时间。并行一般情况下总比串行快嘛。sql语句执行时间越长,效果越明显。

如何实现mysql的异步查询?

要实现异步查询的关键是能把发送请求和接受返回数据分开。正好mysqlnd中提供了这个特性。
在mysqlnd中对应的方法是:
mysqlnd_async_query 发送查询请求
mysqlnd_reap_async_query 获取查询结果
mysqli扩展针对mysqlnd的这个特性做了封装,在调用query方法时,传入MYSQLI_ASYNC即可。

具体代码实现可以查看博文 php中mysql数据库异步查询实现

为啥使用协程?
查看了博文中的代码实现,是不是感觉写法和平时不一样?一般在项目当中,我们都是以function的形式去相互调用,function中包含了数据库查询。为了保持这个习惯,方便大家使用,因此引入了协程。在php5.5中正好提供了yield和generator,方便我们实现协程。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
function f1(){
    $db = new db();
    $obj = $db->async_query('select sleep(1)');
    echo "f1 async_query \n";
    yield $obj;
    $row = $db->fetch();
    echo "f1 fetch\n";
    yield $row;
}
 
function f2(){
    $db = new db();
    $obj = $db->async_query('select sleep(1)');
    echo "f2 async_query\n";
    yield $obj;
    $row = $db->fetch();
    echo "f2 fetch\n";
    yield $row;
}
 
$gen1 = f1();
$gen2 = f2();
 
$gen1->current();
$gen2->current();
$gen1->next();
$gen2->next();
 
$ret1 = $gen1->current();
$ret2 = $gen2->current();
 
var_dump($ret1);
var_dump($ret2);
 
class db{
    static $links;
    private $obj;
 
    function getConn(){
        $host = '127.0.0.1';
        $user = 'demo';
        $password = 'demo';
        $database = 'demo';
        $this->obj = new mysqli($host, $user, $password, $database);
        self::$links[spl_object_hash($this->obj)] = $this->obj;
        return self::$links[spl_object_hash($this->obj)];
    }
 
   function async_query($sql){
        $link = $this->getConn();
        $link->query($sql, MYSQLI_ASYNC);
        return $link;
    }
 
    function fetch(){
        for($i = 1; $i <= 5; $i++){
            $read = $errors = $reject = self::$links;
            $re = mysqli_poll($read, $errors, $reject, 1);
            foreach($read as $obj){
                if($this->obj === $obj){
                    $sql_result = $obj->reap_async_query();
                    $sql_result_array = $sql_result->fetch_array(MYSQLI_ASSOC);//只有一行
                    $sql_result->free();
                    return $sql_result_array;
                }
            }
        }
    }
 
}
?>

在终端命令行方式执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$time php ./async.php
f1 async_query
f2 async_query
f1 fetch
f2 fetch
array(1) {
  ["sleep(1)"]=>
  string(1) "0"
}
array(1) {
  ["sleep(1)"]=>
  string(1) "0"
}
 
real    0m1.016s
user    0m0.007s

从结果上我们可以看出执行流程是,先发了两次mysql查询,然后在接受数据库的返回数据。正常情况下,至少需要2000ms才能执行完毕。但是,real 0m1.016s,说明两次查询的耗时只有1016ms。
tips:以上代码只是示例代码,还有一些需要完善的地方。

注意

需要注意的是,如果mysql服务器本身负载很大,这种并行执行的方式就不一定是好的解决方法。因为,mysql服务端会为每个链接创建一个单独的线程进行处理。如果创建的线程数过多,会给系统造成负担。

参考资料

Facebook Asynchronous MySQL How Facebook Queries Databases

[转]向facebook学习,通过协程实现mysql查询的异步化的更多相关文章

  1. swoole深入学习 8. 协程 转

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/yangyi2083334/article/ ...

  2. Python学习---线程/协程/进程学习 1220【all】

    Python学习---线程基础学习 Python学习---线程锁/信号量/条件变量同步1221 Python学习---同步条件event/队列queue1223 Python学习---进程 1225 ...

  3. python学习笔记 协程

    在学习异步IO模型前,先来了解协程 协程又叫做微线程,Coroutine 子程序或者成为函数,在所有语言中都是层级调用,比如a调用b,b调用c.c执行完毕返回,b执行完毕返回,最后a执行完毕返回 所以 ...

  4. python学习之-- 协程

    协程(coroutine)也叫:微线程,是一种用户态的轻量级线程,就是在单线程下实现并发的效果.优点:1:无需线程上下文切换的开销.(就是函数之间来回切换)2:无需原子操作锁定及同步的开销.(如改一个 ...

  5. Python学习之协程

    8.8 协程 ​ 我们都知道线程间的任务切换是由操作系统来控制的,而协程的出现,就是为了减少操作系统的开销,由协程来自己控制任务的切换 ​ 协程本质上就是线程.既然能够切换任务,所以线程有两个最基本的 ...

  6. Python学习笔记--协程asyncio

    协程的主要功能是单线程并发运行 假设有3个耗时不一样的任务.看看协程的效果. 先来看没有使用协程情况: #!/usr/bin/python3 # -*- coding:utf-8 -*- import ...

  7. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

  8. [Sw] 使用 Swoole Server task/协程 处理大数据量异步任务时注意

    关于 Buffered Query 和 Unbuffered Query:http://www.php.net/manual/zh/mysqlinfo.concepts.buffering.php 对 ...

  9. python 协程(单线程中的异步调用)(转廖雪峰老师python教程)

    协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在 ...

随机推荐

  1. DOS 下 mysql 导入.SQL

  2. 在类库中引用WebService的注意事件

    在VS中 添加引用服务之后 会在 类库中生成一个app.config的文件 把里面的配置节点 添加到web项目中的 web.config中 即可.不然会报 错误  

  3. struts2 action配置时 method 省略不写 默认执行方法是父类ActionSuppot中的execute()方法

    struts2 action配置时 method 省略不写 默认执行方法是父类ActionSuppot中的execute()方法

  4. android ProGuard 代码混淆实现

    1 修改project.properties,添加ProGuard配置项 proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt: ...

  5. layout 布局、手风琴accordion、选项卡tabs

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. 在Hekaton里,正确选择哈希存储桶数

    今天我使用2048的桶数的哈希索引,往Hakaton里插入100万的记录,测试下在哈希桶数里,哈希冲突(Hash Collision)是如何影响Hekaton的工作量——结果非常非常有意思.首先我想介 ...

  7. oracle学习笔记系列------oracle操作例子的专用表

    CREATE TABLE dept( deptno ), dname ) , loc ) ) ; CREATE TABLE emp( empno ), ename ), job ), mgr ), h ...

  8. GacUI学习(二)

    GacUI学习(一)之高仿系统记事本(二) 转载请注明来源:http://www.cnblogs.com/lyfh/p/6107614.html 上篇<GacUI学习(一)之高仿系统记事本(一) ...

  9. 安装DELL R430服务器的过程记录

    序: 本次需要安装一个ineedle设备给客户用,貌似客户是华三.这次领导比较重视,下血本买了一台DELL服务器,似乎花费了1万多大洋.当然配置是比较高了,CPU:Intel(R) Xeon(R) C ...

  10. oracle函数--trunc

    作用:截取 语法:trunc(date,[fmt])   TRUNC函数,ORA-01898 精度说明符过多 TRUNC(SYSDATE)即可默认当前日期(年月日),---写到这一步就好了 TRUNC ...