本文是《sql进阶教程》阅读笔记,感兴趣可以阅读该书对应章节,这本适合有一定sql基础的同学阅读。另外作者《sql基础教程》也值得一看
严格地优化查询性能时,必须要了解所使用数据库的功能特点。此外,查询速度慢并不只是因为 SQL 语句本身,还可能是因为内存分配不佳、文件结构不合理等其他原因。因此本节即将介绍的优化SQL 的方法未必能解决所有的性能问题,但是确实很多时候查询性能不好的原因还是 SQL 的写法不合理
一、使用高效的查询
参数是子查询时,使用 EXISTS
代替 IN
;使用 EXISTS
时更快的原因有以下两个。
-- 慢
SELECT * FROM Class_A
WHERE id IN (SELECT id FROM Class_B);
-- 快
SELECT * FROM Class_A A
WHERE EXISTS (SELECT * FROM Class_B B WHERE A.id = B.id);
如果连接列(id )上建立了索引,那么查询 Class_B 时不用查实际的表,只需查索引就可以了。
如果使用
EXISTS
,那么只要查到一行数据满足条件就会终止查询,不用像使用 IN 时一样扫描全表。在这一点上NOT EXISTS
也一样
当 IN 的参数是子查询时,数据库首先会执行子查询,然后将结果存储在一张临时的工作表里(内联视图),然后扫描整个视图。很多情况下这种做法都非常耗费资源。使用 EXISTS
的话,数据库不会生成临时的工作表
要想改善 IN 的性能,除了使用 EXISTS ,还可以使用连接
-- 使用连接代替IN
SELECT A.id, A.name
FROM Class_A A INNER JOIN Class_B B ON A.id = B.id;
其他方式思路
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
二、避免排序
会进行排序的代表性的运算有下面这些。
GROUP BY
子句ORDER BY
子句- 聚合函数(
SUM
、COUNT
、AVG
、MAX
、MIN
) DISTINCT
- 集合运算符(
UNION
、INTERSECT
、EXCEPT
) - 窗口函数(
RANK
、ROW_NUMBER
等)
排序如果只在内存中进行,那么还好;但是如果内存不足因而需要在硬盘上排序,那么伴随硬盘访问声,排序的性能也会急剧恶化; 尽量避免(或减少)无谓的排序
SQL 语言里有 MAX
和 MIN
两个极值函,使用这两个函数时都会进行排序。但是如果参数字段上建有索引,则只需要扫描索引,不需要扫描整张表
2.1 在 GROUP BY 子句和 ORDER BY 子句中使用索引
一般来说,GROUP BY
子句和 ORDER BY
子句都会进行排序,来对行进行排列和替换。不过,通过指定带索引的列作为 GROUP BY 和ORDER BY 的列,可以实现高速查询。特别是,在一些数据库中,如果操作对象的列上建立的是唯一索引,那么排序过程本身都会被省略掉。
三、真的用到索引了吗
3.1 索引的基本知识
-- 创建。单索引和组合索引
create index 索引名称 on 表 (字段)
create index 索引名称 on 表 (字段1,字段2,....);
-- 删除
drop index '索引名称'
-- 存储索引的表; 可以查询某一个具体的表的索引。
select * from pg_stat_user_indexes;
--所有的索引统计在 `pg_stat_user_indexes` 表中,可以通过表名查找到具体的索引。
--可以通过索引分析索引使用情况。从而定位索引是否被高效使用。
--EXPLAIN sql语句; -- 查询分析计划,通过执行计划可以迅速定位
- 使用索引时,条件表达式的左侧应该是原始字段
-- 1.在索引字段上进行运算
SELECT * FROM SomeTable
WHERE col_1 * 1.1 > 100;
-- 把运算的表达式放到查询条件的右侧,就能用到索引
-- WHERE col_1 > 100 / 1.1
--在查询条件的左侧使用函数时,也不能用到索引
SELECT * FROM SomeTable
WHERE SUBSTR(col_1, 1, 1) = 'a';
- 使用
IS NULL
谓词
索引字段是不存在 NULL
的,所以指定 IS NULL
和 IS NOT NULL
的话会使得索引无法使用,进而导致查询性能低下
SELECT * FROM SomeTable
WHERE col_1 IS NULL;
-- 然而,如果需要使用类似 IS NOT NULL 的功能,又想用到索引,
-- 那么可以使用下面的方法,假设“col_1”列的最小值是 1
--IS NOT NULL 的代替方案
SELECT *
FROM SomeTable
WHERE col_1 > 0;
-- 只要使用不等号并指定一个比最小值还小的数,就可以选出 col_1 中所有的值。
--因为 col_1 > NULL 的执行结果是unknown ,所以当“col_1”列的值为 NULL 的行不会被选择
- 下面这几种否定形式不能用到索引
<>
!=
NOT IN
-- 下面的 SQL 语句也会进行全表扫描。
SELECT *
FROM SomeTable
WHERE col_1 <> 100;
- 使用 OR
在 col_1 和 col_2 上分别建立了不同的索引,或者建立了(col_1,col_2 )这样的联合索引时,如果使用 OR 连接条件,那么要么用不到索引,要么用到了但是效率比 AND 要差很多
SELECT *
FROM SomeTable
WHERE col_1 > 100
OR col_2 = 'abc';
- 进行默认的类型转换
-- 默认的类型转换不仅会增加额外的性能开销,还会导致索引不可用,
--可以说是有百害而无一利;在需要类型转换时显式地进行类型转换
× SELECT * FROM SomeTable WHERE col_1 = 10;
○ SELECT * FROM SomeTable WHERE col_1 = '10';
○ SELECT * FROM SomeTable WHERE col_1 = CAST(10, AS CHAR(2));
四、减少中间表
在 SQL 中,子查询的结果会被看成一张新表,这张新表与原始表一样,可以通过代码进行操作。这种高度的相似性使得 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 sale_date, MAX(quantity)
FROM SalesHistory
GROUP BY sale_date
HAVING MAX(quantity) >= 10;
- 需要对多个字段使用 IN 谓词时,将它们汇总到一处
SQL-92 中加入了行与行比较的功能。这样一来,比较谓词 = 、< 、>
和 IN 谓词的参数就不能是标量值,而应是值列表
-- 这里对多个字段使用了 IN 谓词,“id”列是主键。
SELECT id, state, city
FROM Addresses1 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);
-- 这样一来,子查询不用考虑关联性,而且只执行一次就可以
-- 如果所用的数据库实现了行与行的比较,那么我们也可以像下面这样,
-- 在 IN 中写多个字段的组合。
SELECT *
FROM Addresses1 A1
WHERE (id, state, city)
IN (SELECT id, state, city
FROM Addresses2 A2);
合理地使用视图
视图是非常方便的工具,相信日常工作中很多人都在频繁地使用。但是,如果没有经过深入思考就定义复杂的视图,可能会带来巨大的性能问题。特别是视图的定义语句中包含以下运算的时候,SQL 会非常低效,执行速度也会变得非常慢。
- 聚合函数(AVG 、COUNT 、SUM 、MIN 、MAX )
- 集合运算符(UNION 、*NTERSECT 、EXCEPT 等)
要格外注意避免在视图中进行聚合操作后需要特别注意最近越来越多的数据库为了解决视图的这个缺点,实现了物化视图(materialized view)等技术; 当视图的定义变得复杂时,可以考虑使用一下。