zoukankan      html  css  js  c++  java
  • SQL进阶系列之11让SQL飞起来

    写在前面

    SQL的性能优化是数据库使用者必须面对的重要问题,本节侧重SQL写法上的优化,SQL的性能同时还受到具体数据库的功能特点影响,这些不在本节讨论范围之内

    使用高效的查询

    • 参数是子查询时,使用EXISTS代替IN
    -- 使用EXISTS替代IN的建表语句
    CREATE TABLE Class_A
    (id char(1), 
     name varchar(30), 
     PRIMARY KEY(id));
    
    CREATE TABLE Class_B
    (id   char(1), 
     name varchar(30), 
     PRIMARY KEY(id));
    
    INSERT INTO Class_A (id, name) VALUES('1', '田中');
    INSERT INTO Class_A (id, name) VALUES('2', '铃木');
    INSERT INTO Class_A (id, name) VALUES('3', '伊集院');
    
    INSERT INTO Class_B (id, name) VALUES('1', '田中');
    INSERT INTO Class_B (id, name) VALUES('2', '铃木');
    INSERT INTO Class_B (id, name) VALUES('4', '西园寺');
    
    -- 性能慢的写法
    SELECT * FROM Class_A WHERE id IN (SELECT id FROM Class_B);
    -- 性能快的写法
    SELECT * FROM Class_A WHERE EXISTS (SELECT * FROM Class_B WHERE Class_A.id = Class_B.id);
    

    使用EXISTS时更快的原因有一下两个

    • 如果连接列(id)上建立了索引,那么查询Class_B时不用查实际的表,只需要查索引就可以了

    • 如果使用EXISTS,那么只要查到一行数据满足条件就会终止查询,不用像使用IN一样扫描全表。在这一点上,NOT EXISTS也一样。

    • 参数是子查询时,使用连接替代IN

    -- 使用连接替代IN 
    SELECT Class_A.id,Class_A.name
    FROM Class_A INNER JOIN Class_B
    ON  Class_A.id = Class_B.id;
    

    避免排序

    与面向过程语言不同,SQL语言用户不能显式地命令数据库进行排序操作。但实际数据库暗中进行着各种各样的排序,会进行排序的代表性的运算有下面这些:

    • GROUP BY

    • ORDER BY

    • 聚合函数(SUM COUNT AVG MIN MAX)

    • DISTINCT

    • 集合运算符(UNION INTERSECT EXCEPT)

    • 窗口函数(RANK ROW_NUMBER)

    • 灵活使用集合运算符的ALL可选项

    -- 求所有的id和name
    SELECT * FROM Class_A
    UNION 
    SELECT * FROM Class_B;
    
    -- 如果不在话是否有重复值,则可以使用ALL选项
    SELECT * FROM Class_A
    UNION ALL
    SELECT * FROM Class_B;
    

    各数据库对ALL选项的支持情况如下表:

    Oracle DB2 SQL Sever PostgreSQL MySQL
    UNION
    INTERSECT × × -
    EXCEPT × × -
    • 使用EXISTS代替DISTINCT
    -- 使用EXISTS代替DISTINCT的建表语句
    CREATE TABLE Items
     (item_no INTEGER PRIMARY KEY,
      item    VARCHAR(32) NOT NULL);
    
    INSERT INTO Items VALUES(10, 'FD');
    INSERT INTO Items VALUES(20, 'CD-R');
    INSERT INTO Items VALUES(30, 'MO');
    INSERT INTO Items VALUES(40, 'DVD');
    
    CREATE TABLE SalesHistory
     (sale_date DATE NOT NULL,
      item_no   INTEGER NOT NULL,
      quantity  INTEGER NOT NULL,
      PRIMARY KEY(sale_date, item_no));
    
    INSERT INTO SalesHistory VALUES('2007-10-01',  10,  4);
    INSERT INTO SalesHistory VALUES('2007-10-01',  20, 10);
    INSERT INTO SalesHistory VALUES('2007-10-01',  30,  3);
    INSERT INTO SalesHistory VALUES('2007-10-03',  10, 32);
    INSERT INTO SalesHistory VALUES('2007-10-03',  30, 12);
    INSERT INTO SalesHistory VALUES('2007-10-04',  20, 22);
    INSERT INTO SalesHistory VALUES('2007-10-04',  30,  7);
    
    -- 查找有销售记录的商品
    SELECT Items.item_no
    FROM Items INNER JOIN SalesHistory
    ON Items.item_no = SalesHistory.item_no;
    
    -- 去重(慢)
    SELECT DISTINCT Items.item_no
    FROM Items INNER JOIN SalesHistory
    ON Items.item_no = SalesHistory.item_no;
    
    -- 去重(快)
    SELECT item_no FROM Items WHERE EXISTS (SELECT * FROM SalesHistory WHERE Items.item_no = SalesHistory.item_no);
    
    • 在极值函数中使用索引
    -- 这样写需要扫描全表
    SELECT MAX(item) FROM Items;
    -- 这样写可以用到索引
    SELECT MAX(item_no) FROM items;
    
    -- 这样写并不是渠道了排序过程,而是优化了排序前的查找速度
    
    • 能写在WHERE子句里的条件不要写在HAVING子句里
    -- 聚合后使用HAVING子句过滤
    SELECT sale_date,SUM(quantity) 
    FROM SalesHistory
    GROUP BY sale_date
    HAVING sale_date = '2007-10-01';
    -- 聚合前使用WHERE子句过滤
    SELECT sale_date,SUM(quantity)
    FROM SalesHistory
    WHERE sale_date = '2007-10-01'
    GROUP BY sale_date;
    
    -- 写法二效率更高的原因:GROUP BY聚合时会进行排序,如果事先通过WHERE子句筛选一部分,能够减轻排序的负担;WHERE子句的条件里可以使用索引,HAVING子句是针对聚合后生成的视图进行筛选的,但很多时候聚合后的视图并没有继承原表的索引结构
    
    • 在GROUP BY 子句和ORDER BY子句中使用索引

    真的用到索引了吗

    • 在索引字段上进行运算
    -- 没有使用到索引的情况
    SELECT * FROM SomeTable
    WHERE col_1 * 1.1 > 100; 
    -- 使用到索引的情况
    SELECT * FROM SomeTable
    WHERE col_1  > 100 / 1.1; 
    -- 左侧使用函数也用不到索引
    SELECT * FROM SomeTable
    WHERE SUBSTR(col_1,1,1) = 'a'; -- 使用索引时,条件表达式的左侧应该是原始字段
    
    • 使用IS NULL谓词

    通常索引字段是不存在NULL的,所以指定IS NULL和IS NOT NULL的话,会使得索引无法使用,进而导致查询性能低下。

    -- IS NULL没办法继续优化
    SELECT * FROM SomeTable WHERE col_1 IS NULL;
    
    -- IS NOT NULL时,修改成 > 一个比最小值还小的数
    SELECT * FROM SomeTable WHERE col_1 > 0; -- 假设col_1最小值是1
    
    • 使用否定形式

    "<>"/"!="/"NOT IN"使用不到索引

    -- 全表扫描
    SELECT * FROM SomeTable WEHRE col_1 <> 100;
    -- 否定形式
    SELECT * FROM SomeTable WHERE NOT (col_1 = 100);
    
    • 使用OR
    -- 用不到索引的情形
    SELECT * FROM SomeTable WEHRE col_1 > 100 OR col_2 = 'abc';
    
    • 使用联合索引时,列的顺序错误

    假设存在这样顺序的一个联合索引"col_1,col_2,col_3"

    SELECT * FROM SomeTable WHERE col_1 = 10 AND col_2 = 100 AND col_3 = 500; -- '●'
    SELECT * FROM SomeTable WHERE col_1 = 10 AND col_2 = 100                  -- '●'
    SELECT * FROM SomeTable WHERE col_1 = 10 AND col_3 = 500;                 -- 'x'
    SELECT * FROM SomeTable WHERE col_2 = 100 AND col_3 = 500;                -- 'x'
    SELECT * FROM SomeTable WHERE col_2 = 100 AND col_1 = 10;                 -- 'x'
    
    • 使用LIKE谓词进行后方一致或中间一致的匹配

    只有前方一直的匹配才能用到索引

    SELECT * FROM SomeTable WHERE col_1 LIKE '%a';  -- 'x'
    SELECT * FROM SomeTable WHERE col_1 LIKE '%a%'; -- 'x'
    SELECT * FROM SomeTable WHERE col_1 LIKE 'a%';  -- '●'
    
    • 进行默认的类型转换

    对CHAR类型的列'col_1'指定条件的示例

    SELECT * FROM SomeTable WHERE col_1 = 10;                  -- 'X'
    SELECT * FROM SomeTable WHERE col_1 = '10';                -- '●'
    SELECT * FROM SomeTable WHERE col_1 = CAST(10,AS CHAR(2)); -- '●'
    

    减少中间表

    在SQL中,子查询会被看成一张新表,如果不加限制地大量使用中间包,将会导致查询性能下降

    • 灵活地使用HAVING子句
    -- 无意义的中间表
    SELECT * FROM 
    (SELECT sale_date,MAX(quantity) AS max_qty FROM SalesHistory GROUP BY sale_date) TMP
    WHERE max_qty >= 10;
    -- HAVING
    SELECT * FROM SalesHistory GROUP BY sale_date HAVING MAX(quantity) >= 10;
    
    • 需要对多个字段使用IN谓词时,将它们汇总到一处
    -- 多个字段使用IN
    SELECT id,state,city FROM Address1 A1 WHERE state IN (SELECT state FROM Addresses2 A2 WHERE A1.id = A2.id) AND city IN (SELECT city FROM Addresses2 A2 WHERE A1.id = A2.id);
    
    -- 通过字段连接(但可能带来类型转换问题,无法使用索引)
    SELECT * FROM Addresses1 A1 WHERE id || state || city IN (SELECT id || state || city FROM Addresses2 A2);
    
    -- 优化版本
    SELECT * FROM Addresses1 A1 WHERE (id,state,city) IN (SELECT id,state,city FROM Addresses2 A2); 
    
    • 先进行连接再进行聚合
    • 合理地使用视图

    本节小结

    • 参数是子查询时,使用EXISTS代替IN
    • 使用索引时,条件表达式的左侧应该是原始字段
    • 在SQL中排序无法显式的指定,但是请注意很多运算都会暗中进行排序
    • 尽量减少使用没用的中间表
  • 相关阅读:
    第7.10节 Python类中的实例变量定义与使用
    第7.9节 案例详解:Python类封装
    第7.8节 Python中隐秘的类封装方法
    第7.7节 案例详解:Python类继承机制
    第7.6节 Python中类的继承机制详述
    第7.5节 揭开Python类中self的面纱
    第7.4节 Python中与众不同的类
    第7.3节 Python特色的面向对象设计:协议、多态及鸭子类型
    第7.2节 关于面向对象设计的一些思考
    (独孤九剑)--流程控制
  • 原文地址:https://www.cnblogs.com/evian-jeff/p/11609757.html
Copyright © 2011-2022 走看看