当前位置:嗨网首页>书籍在线阅读

19-优化ORDER BY语句

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

id: 1

优化ORDER BY语句之前,首先来了解一下MySQL中的排序方式。先看 customer表上的索引情况:

mysql> show index from customer\G

1. row

Table: customer

Non_unique: 0

Key_name: PRIMARY

Seq_in_index: 1

Column_name: customer_id

Collation: A

Cardinality: 541

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

2. row

Table: customer

Non_unique: 1

Key_name: idx_fk_store_id

Seq_in_index: 1

Column_name: store_id

Collation: A

Cardinality: 3

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

3. row

Table: customer

Non_unique: 1

Key_name: idx_fk_address_id

Seq_in_index: 1

Column_name: address_id

Collation: A

Cardinality: 541

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

4. row

Table: customer

Non_unique: 1

Key_name: idx_last_name

Seq_in_index: 1

Column_name: last_name

Collation: A

Cardinality: 541

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

4 rows in set (1.05 sec)

1.MySQL中有两种排序方式

第一种通过有序索引顺序扫描直接返回有序数据,这种方式在使用 explain 分析查询的时候显示为Using Index,不需要额外的排序,操作效率较高,例如:

mysql> explain select customer_id from customer order by store_id\G

1. row

id: 1

select_type: SIMPLE

table: customer

type: index

possible_keys: NULL

key: idx_fk_store_id

key_len: 1

ref: NULL

rows: 541

Extra: Using index

1 row in set (0.41 sec)

第二种是通过对返回数据进行排序,也就是通常说的 Filesort 排序,所有不是通过索引直接返回排序结果的排序都叫Filesort排序。Filesort并不代表通过磁盘文件进行排序,而只是说明进行了一个排序操作,至于排序操作是否使用了磁盘文件或临时表等,则取决于MySQL服务器对排序参数的设置和需要排序数据的大小。例如,按照商店store_id排序返回所有客户记录时,出现了对全表扫描的结果的排序:

mysql> explain select * from customer order by store_id\G

1. row

id: 1

select_type: SIMPLE

table: customer

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 541

Extra: Using filesort

1 row in set (0.68 sec)

又如,只需要获取商店store_id和顾客email信息时,对表customer的扫描就被对覆盖索引 idx_storeid_email 扫描替代,此时虽然只访问了索引就足够,但是在索引 idx_storeid_email上发生了一次排序操作,所以执行计划中仍然有Using Filesort。

mysql> alter table customer add index idx_storeid_email (store_id, email);

Query OK, 599 rows affected (1.17 sec)

Records: 599 Duplicates: 0 Warnings: 0

mysql> explain select store_id, email, customer_id from customer order by email\G

1. row

id: 1

select_type: SIMPLE

table: customer

type: index

possible_keys: NULL

key: idx_storeid_email

key_len: 154

ref: NULL

rows: 590

Extra: Using index; Using filesort

1 row in set (0.10 sec)

Filesort是通过相应的排序算法,将取得的数据在sort_buffer_size系统变量设置的内存排序区中进行排序,如果内存装载不下,它就会将磁盘上的数据进行分块,再对各个数据块进行排序,然后将各个块合并成有序的结果集。sort_buffer_size 设置的排序区是每个线程独占的,所以同一个时刻,MySQL中存在多个 sort buffer排序区。

了解了MySQL排序的方式,优化目标就清晰了:尽量减少额外的排序,通过索引直接返回有序数据。WHERE条件和ORDER BY使用相同的索引,并且ORDER BY的顺序和索引顺序相同,并且ORDER BY的字段都是升序或者都是降序。否则肯定需要额外的排序操作,这样就会出现Filesort。

例如,查询商店编号store_id为1,按照email逆序排序的记录主键customer_id时,优化器使用扫描索引idx_storeid_email直接返回排序完毕的记录:

mysql> explain select store_id, email, customer_id from customer where store_id = 1 order by email desc \G

1. row

id: 1

select_type: SIMPLE

table: customer

type: ref

possible_keys: idx_fk_store_id,idx_storeid_email

key: idx_storeid_email

key_len: 1

ref: const

rows: 295

Extra: Using where; Using index

1 row in set (1.93 sec)

而查询商店编号store_id大于等于1小于等于3,按照email排序的记录主键customer_id的时候,由于优化器评估使用索引idx_storeid_email进行范围扫描代价cost最低,所以最终是对索引扫描的结果,进行了额外的按照ename逆序排序操作:

mysql> explain select store_id, email, customer_id from customer where store_id >= 1 and store_id <= 3 order by email desc \G

1. row

select_type: SIMPLE

table: customer

type: range

possible_keys: idx_fk_store_id,idx_storeid_email

key: idx_storeid_email

key_len: 1

ref: NULL

rows: 295

Extra: Using where; Using index; Using filesort

1 row in set (1.53 sec)

总结,下列SQL可以使用索引:

SELECT * FROM tabname ORDER BY key_part1,key_part2,... ;

SELECT * FROM tabname WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;

SELECT * FROM tabname ORDER BY key_part1 DESC, key_part2 DESC;

但是在以下几种情况下则不使用索引:

SELECT * FROM tabname ORDER BY key_part1 DESC, key_part2 ASC;

--order by的字段混合ASC和DESC

SELECT * FROM tabname WHERE key2=constant ORDER BY key1;

--用于查询行的关键字与ORDER BY中所使用的不相同

SELECT * FROM tabname ORDER BY key1, key2;

--对不同的关键字使用ORDER BY:

2.Filesort的优化

通过创建合适的索引能够减少Filesort出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要想办法加快Filesort的操作。对于Filesort,MySQL有两种排序算法。

两次扫描算法(Two Passes):首先根据条件取出排序字段和行指针信息,之后在排序区 sort buffer中排序。如果排序区 sort buffer不够,则在临时表Temporary Table中存储排序结果。完成排序后根据行指针回表读取记录。该算法是MySQL 4.1之前采用的算法,需要两次访问数据,第一次获取排序字段和行指针信息,第二次根据行指针获取记录,尤其是第二次读取操作可能导致大量随机I/O操作;优点是排序的时候内存开销较少。

一次扫描算法(Single Pass):一次性取出满足条件的行的所有字段,然后在排序区sort buffer中排序后直接输出结果集。排序的时候内存开销比较大,但是排序效率比两次扫描算法要高。

MySQL通过比较系统变量max_length_for_sort_data的大小和Query语句取出的字段总大小来判断使用哪种排序算法。如果max_length_for_sort_data更大,那么使用第二种优化之后的算法;否则使用第一种算法。

适当加大系统变量 max_length_for_sort_data 的值,能够让 MySQL 选择更优化的 Filesort排序算法。当然,假如max_length_for_sort_data设置过大,会造成CPU利用率过低和磁盘I/O过高,CPU和I/O利用平衡就足够了。

适当加大sort_buffer_size排序区,尽量让排序在内存中完成,而不是通过创建临时表放在文件中进行;当然也不能无限制加大sort_buffer_size排序区,因为sort_buffer_size参数是每个线程独占的,设置过大,会导致服务器SWAP严重,要考虑数据库活动连接数和服务器内存的大小来适当设置排序区。

尽量只使用必要的字段,SELECT具体的字段名称,而不是SELECT *选择所有字段,这样可以减少排序区的使用,提高SQL性能。