mysql优化器经常使用错索引,这个时候就需要我们dba进行索引纠正。
看下面的sql
SELECT DISTINCT(p.products_id) FROM products AS p INNER JOIN products_description AS pd USING(products_id) LEFT JOIN products_to_categories AS pc ON pc.products_id=p.products_id WHERE language_id=1 AND products_status = 1 AND p.products_date_added >= '2014-02-17' AND pc.categories_id NOT IN (1098,1212,1824,1826,1828,1825,1850,1827,1849,1851,1852,2086,1218,1842,1843,1844,1845,1846,1847,1838,1994,1210,1217,1987,1854,1219,1822,1820,1841,1213,1817,1819,1818,1821,1823,2150,1211,1647,2009,2010,2045) ORDER BY p.products_date_added DESC LIMIT 0, 40;
获取20天新增的产品,并排除某些类别id进行展示。
在p.products_date_added 上有索引,我们理想的sql状态是利用p.products_date_added 索引;但是,因为categories_id也是索引,而且categories_id为整形,p.products_date_added 为时间类型,mysql优化器选择了categories_id,造成了products_to_categories全索引扫描.
explain
+----+-------------+-------+--------+-----------------------------+---------+---------+------------------------------------+--------+-----------------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+-----------------------------+---------+---------+------------------------------------+--------+-----------------------------------------------------------+ | 1 | SIMPLE | pc | index | PRIMARY,categories_id | PRIMARY | 8 | NULL | 102015 | Using where; Using index; Using temporary; Using filesort | | 1 | SIMPLE | pd | eq_ref | PRIMARY | PRIMARY | 8 | banggood_work.pc.products_id,const | 1 | Using index | | 1 | SIMPLE | p | eq_ref | PRIMARY,products_date_added | PRIMARY | 4 | banggood_work.pc.products_id | 1 | Using where | +----+-------------+-------+--------+-----------------------------+---------+---------+------------------------------------+--------+-----------------------------------------------------------+
profiling发现
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | +--------------------------------+----------+----------+------------+--------------+---------------+ | starting | 0.000027 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | | checking query cache for query | 0.000132 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000007 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000009 | 0.000000 | 0.000000 | 0 | 0 | | Opening tables | 0.000037 | 0.000000 | 0.000000 | 0 | 0 | | System lock | 0.000016 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000116 | 0.000000 | 0.000000 | 0 | 0 | | init | 0.000272 | 0.000000 | 0.000000 | 0 | 0 | | optimizing | 0.000033 | 0.000000 | 0.000000 | 0 | 0 | | statistics | 0.000517 | 0.000999 | 0.000000 | 0 | 0 | | preparing | 0.000449 | 0.001000 | 0.000000 | 0 | 0 | | Creating tmp table | 0.000042 | 0.000000 | 0.000000 | 0 | 0 | | executing | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | | Copying to tmp table | 0.749170 | 0.464930 | 0.283956 | 272 | 440 | | Sorting result | 0.000291 | 0.000000 | 0.000000 | 0 | 0 | | Sending data | 0.000019 | 0.000000 | 0.000000 | 0 | 0 | | end | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | removing tmp table | 0.000054 | 0.000000 | 0.000000 | 0 | 0 | | end | 0.000005 | 0.000000 | 0.000000 | 0 | 0 | | query end | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | closing tables | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000018 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000319 | 0.000999 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000007 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | storing result in query cache | 0.000005 | 0.000000 | 0.000000 | 0 | 0 | | logging slow query | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | logging slow query | 0.000029 | 0.000000 | 0.000000 | 0 | 0 | | cleaning up | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | +--------------------------------+----------+----------+------------+--------------+---------------+
执行时间为0.74s,其中Copying to tmp table一项占据了大量io和cpu。
我们可以使用force index强制告诉sql优化器使用哪个索引,更改后如下
EXPLAIN SELECT DISTINCT(p.products_id) FROM products AS p FORCE INDEX(products_date_added) -> INNER JOIN products_description AS pd USING(products_id) -> LEFT JOIN products_to_categories AS pc ON pc.products_id=p.products_id -> WHERE language_id=1 AND products_status = 1 AND p.products_date_added >= '2014-02-17' -> AND pc.categories_id NOT IN (1098,1212,1824,1826,1828,1825,1850,1827,1849,1851,1852,2086,1218,1842,1843,1844,1845,1846,1847,1838,1994,1210,1217,1987,1854,1219,1822,1820,1841,1213,1817,1819,1818,1821,1823,2150,1211,1647,2009,2010,2045) -> ORDER BY p.products_date_added DESC LIMIT 0, 40; +----+-------------+-------+--------+-----------------------+---------------------+---------+-----------------------------------+------+----------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+-----------------------+---------------------+---------+-----------------------------------+------+----------------------------------------------+ | 1 | SIMPLE | p | range | products_date_added | products_date_added | 8 | NULL | 1336 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | pd | eq_ref | PRIMARY | PRIMARY | 8 | banggood_work.p.products_id,const | 1 | Using index; Distinct | | 1 | SIMPLE | pc | ref | PRIMARY,categories_id | PRIMARY | 4 | banggood_work.p.products_id | 1020 | Using where; Using index; Distinct | +----+-------------+-------+--------+-----------------------+---------------------+---------+-----------------------------------+------+----------------------------------------------+
发现已经使用我们预期中的索引products_date_added,
profiling的结果呢?
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | +--------------------------------+----------+----------+------------+--------------+---------------+ | starting | 0.000017 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | checking query cache for query | 0.000080 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | checking permissions | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | Opening tables | 0.000018 | 0.000000 | 0.000000 | 0 | 0 | | System lock | 0.000008 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000026 | 0.000000 | 0.000000 | 0 | 0 | | init | 0.000130 | 0.000000 | 0.000000 | 0 | 0 | | optimizing | 0.000019 | 0.000000 | 0.000000 | 0 | 0 | | statistics | 0.000251 | 0.000000 | 0.000000 | 0 | 0 | | preparing | 0.000035 | 0.000000 | 0.000000 | 0 | 0 | | Creating tmp table | 0.000022 | 0.000000 | 0.000000 | 0 | 0 | | executing | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | Copying to tmp table | 0.019651 | 0.009999 | 0.009999 | 0 | 0 | | Sorting result | 0.000238 | 0.000000 | 0.000000 | 0 | 0 | | Sending data | 0.000016 | 0.000000 | 0.000000 | 0 | 0 | | end | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | removing tmp table | 0.000006 | 0.000000 | 0.000000 | 0 | 0 | | end | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | query end | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | closing tables | 0.000007 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000011 | 0.000000 | 0.000000 | 0 | 0 | | Waiting for query cache lock | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000013 | 0.000000 | 0.001000 | 0 | 0 | | Waiting for query cache lock | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | freeing items | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | storing result in query cache | 0.000005 | 0.000000 | 0.000000 | 0 | 0 | | logging slow query | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | | cleaning up | 0.000003 | 0.000000 | 0.000000 | 0 | 0 | +--------------------------------+----------+----------+------------+--------------+---------------+
执行时间变为0.02s,其中最好io和cpu的Copying to tmp table已经减少了上百倍!
因为这条语句执行频率非常高,所以优化后,性能体现非常明显。