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中排序无法显式的指定,但是请注意很多运算都会暗中进行排序
- 尽量减少使用没用的中间表
SQL进阶系列之11让SQL飞起来的更多相关文章
- SQL进阶系列之9用SQL处理数列
写在前面 关系模型的数据结构里,并没有顺序的概念,但SQL处理有序集合也有坚实的理论基础 生成连续编号 --生成连续编号 CREATE TABLE Digits (digit INTEGER PRIM ...
- SQL进阶系列之7用SQL进行集合运算
写在前面 集合论是SQL语言的根基,因为这种特性,SQL也被称为面向集合语言 导入篇:集合运算的几个注意事项 注意事项1:SQL能操作具有重复行的集合(multiset.bag),可以通过可选项ALL ...
- [SQL SERVER系列]读书笔记之SQL注入漏洞和SQL调优
最近读了程序员的SQL金典这本书,觉得里面的SQL注入漏洞和SQL调优总结得不错,下面简单讨论下SQL注入漏洞和SQL调优. 1. SQL注入漏洞 由于“'1'='1'”这个表达式永远返回 true, ...
- SQL进阶系列之1CASE表达式
配置环境: 下载地址:https://www.enterprisedb.com/downloads/postgres-postgresql-downloads#windows 使用数据库: C:\Po ...
- Linq To Sql进阶系列(六)用object的动态查询与保存log篇
动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动 ...
- SQL进阶系列之12SQL编程方法
写在前面 KISS -- keep it sweet and simple 表的设计 注意命名的意义 英文字母 + 阿拉伯数字 + 下划线"_" 属性和列 编程的方针 写注释 注意 ...
- SQL进阶系列之10HAVING子句又回来了
写在前面 HAVING子句的处理对象是集合而不是记录 各队,全队点名 --各队,全体点名! CREATE TABLE Teams (member CHAR(12) NOT NULL PRIMARY K ...
- SQL进阶系列之8EXISTS谓词的用法
写在前面 支撑SQL和关系数据库的基础理论:数学领域的集合论和逻辑学标准体系的谓词逻辑 理论篇 什么是谓词?谓词是返回值为真值(true false unknown)的函数 关系数据库里,每一个行数据 ...
- SQL进阶系列之6用关联子查询比较行与行
写在前面 使用SQL对同一行数据进行列间的比较很简单,只需要在WHERE子句里写上比较条件就可以了,对于不同行数据进行列间比较需要使用自关联子查询. 增长.减少.维持现状 需要用到行间比较的经典场景是 ...
随机推荐
- abort exit _exit return的区别
exit()函数导致子进程的正常退出,并且参数status&这个值将被返回给父进程.exit()应该是库函数.exit()函数其实是对_exit()函数的一种封装(库函数就是对系统调用的一种封 ...
- linux驱动开发学习二:创建一个阻塞型的字符设备
在Linux 驱动程序中,可以使用等待队列来实现阻塞进程的唤醒.等待队列的头部定义如下,是一个双向列表. struct list_head { struct list_head *next, *pre ...
- 【软件工具】ImageMagick
如何安装; 如何检查是否安装成功呢: 如何使用: https://imagemagick.org/index.php 参考 1. 官网: 完
- Prometheus监控教程——从入门到放弃
Prometheus的安装还是比较简单的 下载地址如下 https://prometheus.io/download/ 解压,修改配置,运行.默认监听9090端口 [root@localhost ~] ...
- DRF概述
目录 一. REST 1. 什么是编程? 2. 什么是REST? 二. 知识准备 1. CBV(class based view) 2. 类方法 classmethod和classonlymethod ...
- massif 图例
19.63^ ### | # | # :: | # : ::: | :::::::::# : : :: | : # : : : :: | : # : : : : ::: | : # : : : : : ...
- [Linux]虚拟机下安装ubuntu后root密码设置
转自:https://blog.csdn.net/zcyhappy1314/article/details/17448223 问题描述: 在虚拟机下安装了ubuntu中要输入用户名,一般情况下大家都会 ...
- Linux 就该这么学 CH02新手必须掌握的Linux命令
0 概述 本章内容如下 强大的shell. 帮助文档命令(1) 系统工作命令(10) 系统状态监测命令(8) 工作目录切换命令(3) 文本文件编辑命令(9) 文件目录管理命令(7) 打包压缩或搜索命令 ...
- B+树比B树更适合实际应用中操作系统的文件索引和数据库索引
B+树比B树更适合实际应用中操作系统的文件索引和数据库索引 为什么选择B+树作为数据库索引结构? 背景 首先,来谈谈B树.为什么要使用B树?我们需要明白以下两个事实: [事实1]不同容量的存储器, ...
- 修改kvm宿主机主机名后
修改了宿主机的主机名后, libvirtd错误日志如下 virNetSocketReadWire: : End of file while reading data: Input/output err ...