@IT·互联网

从SQL Server到MySql(7) : 查询性能优化

2015-11-19  本文已影响221人  沪上最强亚巴顿

1. 查询的过程

2. 优化数据访问

2.1 是否向数据库请求了不需要的数据

2.2 Mysql 是否在扫描额外的记录

3. 查询的执行

3.1 客户端/服务器通信协议

3.2 查询缓存

3.3 查询优化处理

    * 本质上, 所有类型的查询都以此类方式运行. 
    * `但全外连接是个例外, 因为它无法通过嵌套循环和回溯的方式完成. 所以Mysql 并不执行全外连接.`
      * full join :表中数据=内连接+左边缺失数据+右边缺失数据.
  * 执行计划
    * Mysql 并不会生成查询字节码来执行查询, 而是生成查询的一颗指令树, 然后通过存储引擎执行完成这颗指令数并返回结果. 
    * 最终的执行计划, 包含了重构查询的所有信息.
  * 关联查询优化器
    * 由于Mysql 嵌套循环方式的关联查询执行方式, 所以关联顺序变得非常重要.
    * 关联优化器会尝试在所有的关联顺序中选择一个成本最小的执行.
      * 但是, 当管理表过多时, 只能使用"贪婪" 搜索方式查找最优值.
      * 当10个表进行关联时,. 一共有3628800种不同的关联顺序.
  * 排序优化
    * 当无法使用索引进行排序时, Mysql 需要进行排序, 成为文件排序(可能在内存,硬盘中进行,根据数据量大小).
      * 内存不够时, 将数据分块, 对每个块使用"快速排序"并将结果存放在磁盘上, 最后进行merge.
    * 两种排序算法.
      * 两次传输排序(旧版本)
        * 读取行指针和需要排序的字段, 对其进行排序. 然后再更加排序结果读取所需要的数据行.
      * 单次传输排序(新版本)
        * 先读取查询需要的所有列, 然后再根据给定列进行排序, 最后直接返回排序结果.
    * `Mysql 进行文件排序时, 所需要使用的临时存储空间可能很大. 因�其对每个排序记录都会分配一个足够长的定长空间存放.`
  * 查询执行引擎
    * 经过了解析和优化阶段, 会生成执行计划(数据结构而非字节码). 查询执行引擎则根据这个执行计划来完成整个查询.
    * 过程: 简单地根据执行计划给出的指令逐步执行.
  * 返回结果给客户端
    * 即使没有结果数据, 也会返回一些查询信息,如影响的行数.
    * 增量, 逐步返回的过程. �开始生成第一条结果时,就逐步地进行返回.
    * 好处: 服务器无需存储太多结果. 让客户端尽快的得到了结果.

## 4. Mysql 查询优化器的局限性
### 4.1 关联子查询
* Mysql 的子查询实现的非常糟糕.
  * 其中, 最差的是where 条件中包含IN() 的子查询语句.

select * from people
where city_id IN(
select city_id from country where province = 'zhejiang')

  * 直觉上最优的执行方式:

Step1 : select city_id from country where name = 'shanghai'

Result : 4,5,6,7,8,9
Step2: select * from people where city_id IN (4,5,6,7,8,9).

  * Mysql 的做法: 将相关的外层表压到子查询中.

select * from people
where Exists(
select city_id from country where province = 'zhejiang'
Ande people.city_id = country.city_id)

    * 这会造成Mysql 无法先执行子查询. 而是先对people 执行全表扫描, 然后根据返回的city 行集合, 逐行执行子查询.
  * 改造

select * from people
Inner join country Using(city_id)
where province = 'zhejiang')


#### 4.2 Union 的限制
* Mysql 的限制: 无法将限制条件(如limit) 从外层"下推"到内层, 使得原本能够限制部分返回结果的条件无法应用到内层查询的优化上.
` (select * from t1) Union All (select * from t2) limit 20 `
* 执行过程: 首先将t1 和t2 所有符合条件的结果存放在临时表中, 让其年后从临时表中取出前20条.
` (select * from t1 limit 20) Union All (select * from t2 limit 20) limit 20.`
* 直接在筛选t1,t2 的结果时, 只取前20 条到临时表中.

#### 4.3 在同一表上查询和更新
* Mysql 的限制: 不允许对同一张表同时进行查询和更新. 
` Update tbl as outer_tbl set 
cnt = (select count(*) from tbl as inner_tbl where outer_tbl.type = inner_tbl.type);` 
  * 该条Sql 是无法执行的, 适用生成表的形式来绕过上面的限制:
` Update tbl Inner join 
( select type, count(*) as cnt from tbl group by type) as der Using(type) 
set tbl.cnt =der.cnt;`

#### 4.4 最大值和最小值优化
* Mysql 对Min() 和Max() 查询�并没有做很好的优化.
` select Min(id) from people where name ='haha' `
  * 由于name 字段上并没有索引, 因此会进行一次全表扫描.
  * 其实, 进行一次主键扫描, 当找到第一个name为haha 的记录其实就是正确的最小值.
` select id from people use Index(primary) where name = 'haha' limit 1`
  * 缺点是通过SQL, 并不能看出其本意是取最小值.

#### 4.5 松散索引扫描
* 类似于Oracle 中的skip index scan.
* Mysql 并不支持松散索引扫描, 也就无法按照不连续的方式扫描一个索引.
  * 索引扫描需要先定义个起点和终点.
* 例如` where b between 2 and 3;` 如果索引的前导字段是列a. 那么无法被使用, 只能全表扫描.
* 而根据索引的存储特性, 其实可以使用跳跃的方式来进行查询
  * 先扫描a列的第一个值对应的b列的范围, 然后再跳到a列第二个不同值来扫描对应的b列的范围.
* 可以**给前列的加上可能的常数值**的方式来绕过该限制.
* 在5.6 版本后, 使用**索引条件下推**的方式, 可以解决松散索引扫描的一些限制.
上一篇 下一篇

猜你喜欢

热点阅读