SQL优化技巧及相关注意事项

创建索引

字段选取

  • 选择准则

    避免建在大量重复值的字段上,在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停用该索引
    
    
        -- 反例
        SELECTFROM  DEPARTMENT  WHERE  DEPT_CODE IS NOT NULL; 
    
    
        -- 正例
        SELECTFROM  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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值