创建索引
字段选取
-
选择准则
避免建在大量重复值的字段上,在where、join、order by等字段上合理创建索引来提高性能
索引失效
负向查询
-
案例脚本
-- 反例 select name from user where id not in (1,3,4); -- 正例 select name from user where id in (2,5,6);
前导模糊查询
-
案例脚本
-- 反例 select name from user where name like '%zhangsan' -- 正例 select name from user where name like 'zhangsan%'
可以考虑使用 ElasticSearch 等全文索引工具来代替频繁的模糊查询。
数据区分不明显(数据不唯一)
-
案例脚本
-- 如user表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段
字段上进行函数运算
-
案例脚本
-- 反例 select name from user where FROM_UNIXTIME(create_time) < CURDATE(); -- 正例 select name from user where create_time < FROM_UNIXTIME(CURDATE());
最左前缀问题
-
案例脚本
-- 创建组合索引 create index index_name on user(username,pwd); -- 反例(不能命中索引) select username from user where pwd = 'axsedf1sd'; -- 正例(能命中索引) select username from user where username='zhangsan' and pwd ='axsedf1sd' select username from user where pwd ='axsedf1sd' and username='zhangsan' select username from user where username='zhangsan'
多表联查join字段类型不相同
-
案例脚本
-- join字段类型必须相同,否则不会命中索引
索引列上使用is null 和 is not null
-
案例脚本
-- 避免在索引中使用任何可以为空的列,oracle将无法使用该索引。 -- 1. 对于单列索引,如果列包含空值,索引中将不存在此记录。 -- 2. 对于复合索引,如果每个列都为空,索引中同样不存在此记录,如果至少有一个列不为空,则记录存在于索引中 -- 举例1:如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),Oracle将不接受下一条具有相同A,B值(123,null)的记录插入。 -- 举例2:如果所有索引列都为空,Oracle将认为整个键值为空,而空不等于空。因此你可以插入1000条具有相同键值的记录,当然它们都是空,因为空值不存在于索引列中,所以where子句中对索引列进行空值比较将使Oracle停用该索引 -- 反例 SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; -- 正例 SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;
提高查询效率
避免全表扫描
-
案例脚本
-- 反例(数据库做强制类型转换,会导致全表扫描) select username from user where telno = 18722222222 -- 正例 select username from user where telno = '18722222222'
明确知道只有一条记录返回
-
案例脚本
-- 正例(MySQL中关键字limit 1,可以让数据库停止游标移动) select name from user where username='zhangsan' limit 1
返回尽量少的字段
-
案例脚本
-- 反例(读取适当的记录及需要的字段,避免用*) select * from user;
分组统计禁止排序
-
案例脚本
-- 正例(MySQL默认情况下对所有group by的字段进行排序,如果查询包括group by 而又不需要排序操作可以禁止) select count(1),username from user group by username order by null;
使用join替代子查询
-
案例脚本
-- 反例(内部执行计划器是这样执行的:先查外表再匹配内表,而不是先查内表t2,当外表的数据很大时,查询速度会非常慢。) SELECT * FROM t1 WHERE id in (SELECT id FROM t2 WHERE name= 'hechunyang'); -- 正例(inner join内连接也叫做等值连接,left/right join是外链接) SELECT t1.* FROM t1 INNER JOIN t2 ON t1.id =t2.id;
使用(not) exists替代(not) in
-
案例脚本
-- 反例(NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.) SELECT * FROM EMP(基础表) WHERE EMPNO > 0 AND DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC = 'MELB') -- 正例 SELECT * FROM EMP(基础表) WHERE EMPNO > 0 AND EXISTS(SELECT 'X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = 'MELB')
用exists替换distinct
-
案例脚本
-- 反例 SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E WHERE D.DEPT_NO = E.DEPT_NO -- 正例 SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT 'X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);
创建表时选择合适字段属性
-
案例脚本
-- 建表时尽量让字段not null,这样数据库在执行查询时不用比较null值
减少使用联接列
-
案例脚本
-- 假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工 -- 反例(对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的) select * from employee where first_name||' '||last_name = 'Beill Cliton'; -- 正例 select * from employee where first_name ='Beill' and last_name ='Cliton';
用union替换or
-
案例脚本
--通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低. 在下面的例子中, LOC_ID 和REGION上都建有索引. -- 反例(如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面) SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = 'MELBOURNE' -- 正例 SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE LOC_ID = 10 UNION SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE REGION = 'MELBOURNE'
用union all 替换 union
-
案例脚本
-- SQL 语句需要UNION两个查询结果集合时,这两个结果集合会以UNION ALL的方式被合并, 然后在输出最终结果前进行排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这块内存的优化也是相当重要的 -- 反例 SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE ='31-DEC-95' UNION SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE ='31-DEC-95' -- 正例 SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95' UNION ALL SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95'
用where替换order by
-
案例脚本
-- order by 子句使用索引条件限制: -- 1. order by 中所有的列必须包含在相同的索引中并保持在索引中的排列顺序 -- 2. order by 中所有的列必须定义为非空 -- 3. WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列(这句话含义不是很懂)
优化group by
-
案例脚本
-- 将不需要的记录在GROUP BY 之前过滤掉 -- 反例 SELECT JOB, AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = 'PRESIDENT' OR JOB = 'MANAGER' -- 正例 SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = 'PRESIDENT' OR JOB = 'MANAGER' GROUP by JOB
使用with as
-
案例脚本
-- 一次分析,多次使用 -- 把一大堆重复用到的sql语句放在with as里面,取一个别名,后面的查询就可以用它,这样对于大批量的sql语句起到一个优化的作用,而且清楚明了 insert into table2 with s1 as (select rownum c1 from dual connect by rownum <= 10), s2 as (select rownum c2 from dual connect by rownum <= 10) select a.c1, b.c2 from s1 a, s2 b where...;
合理使用正则匹配函数
-
案例脚本
https://www.cnblogs.com/numen-fan/p/12059707.html
-- 要求将clob字段存在资讯内容中所有富文本标签替换成空返回 select INF_ID AS infId,INF_PDVDR_CD as infPvdrCd,regexp_replace(pblopn_txt_cntnt,'</?[^>]*>|nbsp;|&','') as pblOpnTxtCntnt from INTELLIGENT_INF; -- 执行效率非常堪忧,最后选择直接查询出结果,在代码中处理富文本标签
参考链接
https://www.cnblogs.com/gomysql/p/3632209.html
https://www.cnblogs.com/williamjie/p/9389530.html
其他
MySQL
unix_timestamp/from_unixtime函数使用
https://blog.csdn.net/qq_24309787/article/details/81095238
MySQL之SQL语句优化步骤
https://blog.csdn.net/DrDanger/article/details/79092808