一次MySQL查询的大致过程

如果想尽可能地优化 MySQL 的性能,那么必须对 MySQL 查询的全过程有个大体上的了解,为后续的优化工作打下基础。

第一步:发送 SQL 语句

客户端与 MySQL 服务器建立 TCP 连接后发送一条 SQL 语句给 MySQL 服务器。

客户端和 MySQL 服务器之间的通信协议(应用层协议)是“半双工”的,因此,同一个时刻,只能有一方在发送数据,同时,另一方要么完整地接受数据,要么粗暴地断开连接。

第二步:检查查询缓存

MySQL 服务器会先检查 SQL 语句是不是 SELECT 型,如果是,则会先去检查查询缓存,如果缓存命中,则立即返回存储在缓存中的结果。如果未命中,则执行下一步。(可以通过配置来关闭查询缓存)

第三步:生成执行计划

MySQL 服务器进行 SQL 解析、预处理,最后,优化器会先预测多个执行计划的成本,然后选择生成其中成本最小的一个。优化器根据一系列的统计信息得来:每个表或者索引的页数(page)、索引的基数(即索引中不同值的数量)、索引和数据行的长度、索引分布情况等,注意优化器并没有考虑到任何缓存,它假设所有读取都是读磁盘。

注意,解析和预处理的主要作用是检测 SQL语句是否有错误,而这里的执行计划是一颗“指令树”。

第四步:执行

MySQL 服务器遍历这棵“指令树”,并调用相关存储引擎 API 来执行查询。

存储引擎将数据返回给 MySQL 服务器后(一条一条),还有可能对返回的数据进行筛选、排序、分组等操作。

SQL各部分执行顺序

第五步:返回结果

MySQL 服务器将结果返回给客户端,这是一个增量、逐步返回的过程,服务器一旦得到了一条完整的记录,就开始向客户端逐步返回结果集了。也就是说,结果集中的每一行都会以 MySQL 客户端 / 服务器通信协议进行封包,然后再通过 TCP协议传输。

这种做法的好处是,MySQL 不需要存储太多的结果,节约了内存,同时,也能让客户端尽早地接收到结果。

可以通过使用 SQL_BUFFER_RESULT 来改变 MySQL 的这种行为,这个 hint 告诉优化器将查询结果先放入到一个临时表中,然后尽快地释放相关的锁资源。不过,带来的坏处是 MySQL服务器将需要更多的内存。

另外,多数连接 MySQL 的库函数(比如,PHP 中的 PDO),默认情况下,都是先将全部结果集缓存在内存中。

//  省略数据库连接部分
$stmt = $pdo->query("select * from address");
while ($obj = $stmt->fetchObject()) {
    //  Do Something...
}

当执行到 while 时,PDO 就已经将整个结果集缓存到内存中了,while 循环只是从应用缓存中逐行取出数据。

默认的这种行为的好处在于 MySQL 能尽快地完成这次查询,尽快释放相关资源。坏处在于花费了时间和内存来存储整个结果集,当结果集很大的时候,可能发生超出内存的现象。

可以更改 PDO的这种默认行为:

$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->query("select * from address");
while ($obj = $stmt->fetchObject()) {
    //  Do Something...
}

此后,PDO 将不会缓存结果集了,而是每次循环都从 MySQL服务器取回一条数据。这种做法会延迟了服务器释放各种资源比如锁、连接的时间。

猜你喜欢

转载自blog.csdn.net/stfphp/article/details/53308908