- is null 和 is not null
造成全表扫描,null不能进行索引构建
- 联接列
优化器不走索引,应改成xxx and xxx
- 带通配符(%)的like语句
通配符出现在首部,不走索引
- Order by语句
若order by出现非索引项或者表达式,会降低查询速度
- 使用大于和小于代替不等于号
速度更快
最左前缀原则
定义:最左前缀原则指的的是在sql where 子句中一些条件或表达式中出现的列的顺序要保持和多索引的一致或以多列索引顺序出现,只要 出现非顺序出现、断层都无法利用到多列索引。
举例说明:上面给出一个多列索引(username,password,last_login),当三列在where中出现的顺序如(username,password,last_login)、 (username,password)、(username)才能用到索引,如下面几个顺序(password,last_login)、(passwrod)、(last_login)—这三者不 从username开始,(username,last_login)—断层,少了password,都无法利用到索引。因为B+tree多列索引保存的顺序是按照索引创 建的顺序,检索索引时按照此顺序检索
最左前缀原则中where字句有or出现还是会遍历全表
Order by 优化
filesort优化算法:
在mysql version()<4.1之前,优化器采用的是filesort第一种优化算法,先提取键值和指针,排序后再去提取数据,前后要搜索数据 两次,第一次若能使用索引则使用,第二次是随机读(当然不同引擎也不同)。mysql version()>=4.1,更新了一个新算法,就是在第 一次读的时候也把selcet的列也读出来,然后在sort_buffer_size中排序(不够大则建临时表保存排序顺序),这算法只需要一次读 取数据。所以有这个广为人传的一个优化方法,那就是增大sort_buffer_size。Filesort第二种算法要用到更多空间, sort_buffer_size不够大反而会影响速度,所以mysql开发团队定了个变量max_length_for_sort_data,当算法中读出来的需要列 的数据的大小超过该变量的值才使用,所以一般性能分析的时候会尝试把max_length_for_sort_data改小。
单独order by 用不了索引,索引考虑加where 或加limit
先建一个索引(last_login),建的过程就不给出了
mysql> explain select * from one order by last_login desc;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows
| Extra |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| 1 | SIMPLE | one | ALL | NULL | NULL | NULL | NULL | 2046
3 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
1 row in set (0.00 sec)
mysql> explain select * from one order by last_login desc limit 10;
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------+
| 1 | SIMPLE | one | index | NULL | last_login | 4 | NULL
| 10 | |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------+
1 row in set (0.00 sec)
开始没limit查询是遍历表的,加了limit后,索引可以使用,看key_len 和key
where + orerby 类型,where满足最左前缀原则,且orderby的列和where子句用到的索引的列的子集。即是(a,b,c)索引, where满足最左前缀原则且order by中列a、b、c的任意组合
mysql> explain select * from one where username='abgvwfnt' and password ='123456
' and last_login='1338251001' order by password desc,last_login desc;
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+
| 1 | SIMPLE | one | ref | username | username | 83 | const,const,const | 1 | Using where |
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+
1 row in set (0.00 sec)
mysql> explain select * from one where username='abgvwfnt' and password ='123456
' and last_login='1338251001' order by password desc,level desc;
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref| rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+-----------------------------+
| 1 | SIMPLE | one | ref | username | username | 83 | const,const,const | 1 | Using where; Using filesort |
+----+-------------+-------+------+---------------+----------+---------+-------------------+------+-----------------------------+
1 row in set (0.00 sec)
上面两条语句明显的区别是多了一个非索引列level的排序,在extra这列对了Using filesort。 笔者测试结果:where满足最左前缀且order by中的列是该多列索引的子集时(也就是说orerby中没最左前缀原则限制),不管是否 有asc ,desc混合出现,都能用索引来满足order by。因为篇幅比较大,这里就不一一列出。
Ps:很优化博文都说order by中的列要where中出现的列(是索引)的顺序一致,笔者认为不够严谨。
where + orerby+limit
隔离列
隔离列是只查询语句中把索引列隔离出来,也就是说不能在语句中把列包含进表达式中,如id+1=2、inet_aton(‘210.38.196.138’)— ip转换成整数、convert(123,char(3))—数字转换成字符串、date函数等mysql内置的大多函数。
非隔离列影响性能很大甚至是致命的,这也就是赶集网石展的《三十六军规》中的一条,虽然他没说明是隔离列。 以下就测试一下:
首先建立一个索引(last_login ),这里就不给出建立的代码了,且把last_login改成整型(这里只是为了方便测试,并不是影响条件)
mysql> explain select * from one where last_login = 8388605;
+----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+
| 1 | SIMPLE | one | ref | last_login | last_login | 3 | const | 1 | Using where |
+----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+
1 row in set, 1 warning (0.00 sec)
容易看出建的索引已起效
mysql> explain select * from one where last_login +1= 8388606 ;
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | one | ALL | NULL | NULL | NULL | NULL | 2049
7 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
**last_login +1=8388608非隔离列的出现导致查找的列20197,说明是遍历整张表且索引不能使用。
这是因为这条语句要找出所有last_login的数据,然后+1再和20197比较,优化器在这方面比较差,性能很差。
所以要尽可能的把列隔离出来,如last_login +1= 8388606改成login_login=8388607,或者把计算、转换等操作先用php函数处理
过再传递给mysql服务器**
OR、IN、UNION ALL,可以尝试用UNION ALL
or会遍历表就算有索引
mysql> explain select * from one where username = 'abgvwfnt' or password='123456';
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows| Extra |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | one | ALL | username | NULL | NULL | NULL | 20259 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
对于in,这个是有争议的,网上很多优化方案中都提到尽量少用in,这不全面,其实在in里面如果是常量的话,可一大胆的用in, 这个也是赶集网石展、阿里hellodab的观点(笔者从微博中获知)。应用hellodab一句话“MySQL用IN效率不好,通常是指in中 嵌套一个子查询,因为MySQL的查询重写可能会产生一个不好的执行计划,而如果in里面是常量的话,我认为性能没有任何问题, 可以放心使用”———当然对于这个比较的话,没有实战数据的话很难辩解,就算有,影响性能的因素也很多,也许会每个 dba都有不同的测试结果.这也签名最左前缀中“补洞”一个方法
UNION All 直接返回并集,可以避免去重的开销。之所说“尝试”用UNION All 替代 OR来优化sql语句,因为这不是一直能优化的了, 这里只是作为一个方法去尝试。
(1) 选择最有效率的表名顺序(只在基于规则的优化器中有效):
ORACLE 的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表.
(2) WHERE子句中的连接顺序.:
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.
(3) SELECT子句中避免使用 ‘ * ‘:
ORACLE在解析的过程中, 会将’*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间
(4) 减少访问数据库的次数:
ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量 , 读数据块等;
(5) 在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200
(6) 使用DECODE函数来减少处理时间:
使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.
(7) 整合简单,无关联的数据库访问:
如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)
(8) 删除重复记录:
最高效的删除重复记录方法 ( 因为使用了ROWID)例子:
DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
(9) 用TRUNCATE替代DELETE:
当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息. 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短. (译者按: TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML)
(10) 尽量多使用COMMIT:
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:
COMMIT所释放的资源:
a. 回滚段上用于恢复数据的信息.
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
(11) 用Where子句替换HAVING子句:
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销. (非oracle中)on、where、having这三个都可以加条件的子句中,on是最先执行,where次之,having最后,因为on是先把不 符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,where也应该比having快点的,因为它过滤数据后 才进行sum,在两个表联接时才用on的,所以在一个表的时候,就剩下where跟having比较了。在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是where可以使用rushmore技术,而having就不能,在速度上后者要慢如果要涉及到计算的字 段,就表示在没计算之前,这个字段的值是不确定的,根据上篇写的工作流程,where的作用时间是在计算之前就完成的,而having就是在计算后才起作 用的,所以在这种情况下,两者的结果会不同。在多表联接查询时,on比where更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表 后,再由where进行过滤,然后再计算,计算完后再由having进行过滤。由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里
(12) 减少对表的查询:
在含有子查询的SQL语句中,要特别注意减少对表的查询.例子:
SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT
TAB_NAME,DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)
(13) 通过内部函数提高SQL效率.:
复杂的SQL往往牺牲了执行效率. 能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的
(14) 使用表的别名(Alias):
当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.
(15) 用EXISTS替代IN、用NOT EXISTS替代NOT IN:
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率. 在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.
mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。
如果查询的两个表大小相当,那么用in和exists差别不大。
如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:
例如:表A(小表),表B(大表)
not in 和not exists如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。
索引选择性
基数:不重复的索引值的行数
索引选择性=基数/数据行,基数可以通过 “show index from 表名”查看。高索引选择性的好处就是mysql查找匹配的时候可以过滤更多的行,唯一索引的选择性最佳,值为1。 那么对于非唯一索引或者说要被创建索引的列的数据内容很长,那就要选择索引前缀。这里就简单说明一下:
mysql> select count(distinct(username))/count(*) from one;
+------------------------------------+
| count(distinct(username))/count(*) |
+------------------------------------+
| 0.2047 |
+------------------------------------+
1 row in set (0.09 sec)
count(distinct(username))/count( )就是索引选择性,这里0.2太小了。假如username列数据很长,则可以通过 select count(distinct(concat(first_name, left(last_name, N))/count( ) from one;测试出接近1的索引选择性, 其中N是索引的长度,穷举法去找出N的值,然后再建索引。
重复或多余索引
很多phper开始都以为建索引相对多点性能就好点,压根没考虑到有些索引是重复的,比如建一个(username),(username,password), (username,password,last_login),很明显第一个索引是重复的,因为后两者都能满足其功能。要有个意识就是,在满足功能需求的 情况下建最少索引。对于INNODB引擎的索引来说,每次修改数据都要把主键索引,辅助索引中相应索引值修改,这可能会出现大量数据迁移,分页,以及碎片的出现。