当需要过滤的字段上既没有索引也没有直方图时,优化器会根据MySQL代码中内置的默认规则估计过滤的比率,实际很大程度上是瞎猜,部分常用的默认规则如下:
过滤类型 | 过滤比率(%) |
= |
10 |
<>或≠ |
90 |
< 或 > |
33.33 |
between |
11.11 |
in |
字段数×10和50的最小值 |
下面是SQL语句的执行计划中默认过滤比率的几个例子,首先设置pager,使执行计划只显示过滤比率:
mysql> pager grep filteredPAGER set to 'grep filtered'
等于检索字段的默认过滤比率为10%:
mysql> explain select * from actor where first_name='lisa'\G filtered: 10.001 row in set, 1 warning (0.00 sec)
大于或小于检索字段的默认过滤比率为三分之一:
mysql> explain select * from actor where first_name>'lisa'\G filtered: 33.331 row in set, 1 warning (0.00 sec)
不等于检索字段的默认过滤比率为90%:
mysql> explain select * from actor where first_name<>'lisa'\G filtered: 90.001 row in set, 1 warning (0.00 sec)
between的默认过滤比率为11.11%:
mysql> explain select * from actor where last_update between '2006-02-15' and '2006-02-16'\G filtered: 11.111 row in set, 1 warning (0.00 sec)
下面SQL中in的过滤比率为20%:
mysql> explain select * from actor where first_name in ('lisa','THORA')\G filtered: 20.001 row in set, 1 warning (0.00 sec)
使用默认规则估计过滤比率往往是不准的,因此很多时候会生成错误的执行计划,这个时候在字段上收集直方图统计信息可以解决这个问题。而且直方图即使不用于改变SQL语句的执行计划,也可以用于在执行计划中的filt列显示正确的过滤的比例。
编写一个简单的SQL语句,查询在payment表里面支付的金额大于10元的客户号,生成这个SQL的执行计划如下:
mysql> explain select customer_id from payment where amount>10\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: payment partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 16086 filtered: 33.33 Extra: Using where1 row in set, 1 warning (0.00 sec)
判断amount字段大于10的记录,由于这个字段上没有直方图的统计信息,优化器根据代码中内置的默认值估计有三分之一的记录属于这个范围。再判断amount大于100的记录的执行计划如下:
mysql> explain select customer_id from payment where amount>100\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: payment partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 16086 filtered: 33.33 Extra: Using where1 row in set, 1 warning (0.00 sec)
优化器仍然估计有三分之一的记录属于这个范围,显然优化器在瞎猜。为了解决这个问题,现在在amount字段上创建直方图的统计信息的命令和输出结果如下:
mysql> analyze table payment update histogram on amount with 256 buckets\G*************************** 1. row *************************** Table: sakila.payment Op: histogramMsg_type: statusMsg_text: Histogram statistics created for column 'amount'.1 row in set (0.31 sec)
再重新生成这个SQL语句的执行计划如下:
mysql> explain select customer_id from payment where amount>10\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: payment partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 16086 filtered: 0.71 Extra: Using where1 row in set, 1 warning (0.00 sec)
优化器根据直方图的统计信息估计符合这个条件的记录只占总数0.71%。
删除这个直方图的命令如下:
mysql> analyze table payment drop histogram on amount\G*************************** 1. row *************************** Table: sakila.payment Op: histogramMsg_type: statusMsg_text: Histogram statistics removed for column 'amount'.1 row in set (0.01 sec)
下面的SQL语句查询单词消费金额大于10元和在第一个店进行消费的顾客的姓名,在没有直方图时的生成的执行计划如下:
mysql> explain analyze select first_name,last_name from customer inner join payment using (customer_id) where amount>10 and store_id=1\G*************************** 1. row ***************************EXPLAIN: -> Nested loop inner join (cost=3100.48 rows=2918) (actual time=0.443..14.853 rows=68 loops=1) -> Index lookup on customer using idx_fk_store_id (store_id=1) (cost=36.35 rows=326) (actual time=0.310..0.707 rows=326 loops=1) -> Filter: (payment.amount > 10.00) (cost=6.72 rows=9) (actual time=0.042..0.043 rows=0 loops=326) -> Index lookup on payment using idx_fk_customer_id (customer_id=customer.customer_id) (cost=6.72 rows=27) (actual time=0.031..0.038 rows=27 loops=326) 1 row in set (0.01 sec)
可以看到优化器先对符合在第一个店进行消费的条件进行过滤,然后再过滤消费金额大于10元的条件。在字段amount上有直方图统计信息之后,再次生成这个SQL语句的执行计划如下:
mysql> explain analyze select first_name,last_name from customer inner join payment using (customer_id) where amount>10 and store_id=1\G *************************** 1. row *************************** EXPLAIN: -> Nested loop inner join (cost=1672.84 rows=62) (actual time=0.328..9.507 rows=68 loops=1) -> Filter: (payment.amount > 10.00) (cost=1632.85 rows=114) (actual time=0.224..8.421 rows=114 loops=1) -> Table scan on payment (cost=1632.85 rows=16086) (actual time=0.191..6.482 rows=16049 loops=1) -> Filter: (customer.store_id = 1) (cost=0.25 rows=1) (actual time=0.009..0.009 rows=1 loops=114) -> Single-row index lookup on customer using PRIMARY (customer_id=payment.customer_id) (cost=0.25 rows=1) (actual time=0.009..0.009 rows=1 loops=114) 1 row in set (0.02 sec)
可以看到优化器将这两个过滤条件的先后次序反转过来了,因为借助直方图统计信息,优化器知道消费金额大于10元这个条件的选择性更高。从估计成本和实际执行时间都可以看出,有直方图的执行计划效率要好很多!直方图在某些场景下可以帮助优化器生成更优的执行计划,那么在什么样的字段上考虑使用直方图呢,这里建议符合下面4个条件字段可以考虑建立直方图统计信息:
(1)值分布不均匀,优化器很难估计值的分布的字段。
(2)选择性差的字段,否则索引更适合。
(3)用于where子句中过滤的字段或用于连接的字段。
(4)字段值分布规律不随时间变化的字段。因为直方图统计信息不会自动收集,如果字段值分布规律发生大的变化,统计信息会失真。
实际工作中,可以使用explain analyze分析SQL语句的执行计划,如果估算的rows和实际的rows相差过大,可以考虑在过滤字段上创建直方图统计信息。