品牌 资讯 搭配 材料 时尚 热点 行业 首饰 玉石 行情

让SQL起飞(优化)

2023-04-17 01:23:47 来源:腾讯云

最近博主看完了《SQL进阶教程》这本书,看完后给博主打开了SQL世界的新大门,对于 SQL 的理解不在局限于以前的常规用法。借用其他读者的评论,

读完醍醐灌顶,对SQL做到了知其然更能知其所以然。全书从头到尾强调了 SQL的内在逻辑是基于集合论和谓词逻辑,而着两条主线恰恰在使用SQL起到了至关重要的指导作用。


(资料图)

本文给大家总结如何让SQL起飞(优化)

一、SQL写法优化

在SQL中,很多时候不同的SQL代码能够得出相同结果。从理论上来说,我们认为得到相同结果的不同SQL之间应该有相同的性能,但遗憾的是,查询优化器生成的执行计划很大程度上受到SQL代码影响,有快有慢。因此如果想优化查询性能,我们必须知道如何写出更快的SQL,才能使优化器的执行效率更高。

1.1 子查询用EXISTS代替IN

当IN的参数是子查询时,数据库首先会执行子查询,然后将结果存储在一张临时的工作表里(内联视图),然后扫描整个视图。很多情况下这种做法都非常耗费资源。使用EXISTS的话,数据库不会生成临时的工作表。但是从代码的可读性上来看,IN要比EXISTS好。使用IN时的代码看起来更加一目了然,易于理解。因此,如果确信使用IN也能快速获取结果,就没有必要非得改成EXISTS了。

这里用Class_A表和Class_B举例,

我们试着从Class_A表中查出同时存在于Class_B表中的员工。下面两条SQL语句返回的结果是一样的,但是使用EXISTS的SQL语句更快一些。

--慢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);

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

如果连接列(id)上建立了索引,那么查询 tb_b 时不用查实际的表,只需查索引就可以了。(同样的IN也可以使用索引,这不是重要原因)如果使用EXISTS,那么只要查到一行数据满足条件就会终止查询,不用像使用IN时一样扫描全表。在这一点上NOT EXISTS也一样。

实际上,大部分情况在子查询数量较小的场景下EXISTS和IN的查询性能不相上下,由EXISTS查询更快第二点可知,子查询数量较大时使用EXISTS才会有明显优势。

1.2 避免排序并添加索引

在SQL语言中,除了ORDER BY子句会进行显示排序外,还有很多操作默认也会在暗中进行排序,如果排序字段没有添加索引,会导致查询性能很慢。SQL中会进行排序的代表性的运算有下面这些。

GROUP BY子句ORDER BY子句聚合函数(SUM、COUNT、AVG、MAX、MIN)DISTINCT集合运算符(UNION、INTERSECT、EXCEPT)窗口函数(RANK、ROW_NUMBER等)

如上列出的六种运算(除了集合运算符),它们后面跟随或者指定的字段都可以添加索引,这样可以加快排序。

实际上在DISTINCT关键字、GROUP BY子句、ORDER BY子句、聚合函数跟随的字段都添加索引,不仅能加速查询,还能加速排序。

1.3 用EXISTS代替DISTINCT

为了排除重复数据,我们可能会使用DISTINCT关键字。如1.2中所说,默认情况下,它也会进行暗中排序。如果需要对两张表的连接结果进行去重,可以考虑使用EXISTS代替DISTINCT,以避免排序。这里用Items表和SalesHistory表举例:

我们思考一下如何从上面的商品表Items中找出同时存在于销售记录表SalesHistory中的商品。简而言之,就是找出有销售记录的商品。

在一(Items)对多(SalesHistory)的场景下,我们需要对item_no去重,使用DISTINCT去重,因此SQL如下:

SELECT DISTINCT I.item_no  FROM Items I INNER JOIN SalesHistory SH    ON I. item_no = SH. item_no;item_no-------    10    20    30

使用EXISTS代替DISTINCT去重,SQL如下:

SELECT item_no  FROM Items I WHERE EXISTS          (SELECT *              FROM SalesHistory SH            WHERE I.item_no = SH.item_no);item_no-------    10    20    30

这条语句在执行过程中不会进行排序。而且使用EXISTS和使用连接一样高效。

1.4 集合运算ALL可选项

SQL中有UNION、INTERSECT、EXCEPT三个集合运算符。在默认的使用方式下,这些运算符会为了排除掉重复数据而进行排序。

MySQL还没有实现INTERSECT和EXCEPT运算

如果不在乎结果中是否有重复数据,或者事先知道不会有重复数据,请使用UNION ALL代替UNION。这样就不会进行排序了。

1.5 WHERE条件不要写在HAVING字句

例如,这里继续用SalesHistory表举例,下面两条SQL语句返回的结果是一样的:

--聚合后使用HAVING子句过滤SELECT sale_date, SUM(quantity)  FROM SalesHistory GROUP BY sale_dateHAVING 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子句是针对聚合后生成的视图进行筛选的,但是很多时候聚合后的视图都没有继承原表的索引结构。

二、真的用到索引了吗

2.1 隐式的类型转换

如下,col_1字段是char类型:

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)); -- 走了索引

当查询条件左边和右边类型不一致时会导致索引失效。

2.2 在索引字段上进行运算

如下:

SELECT *  FROM SomeTable WHERE col_1 * 1.1 > 100;

在索引字段col_1上进行运算会导致索引不生效,把运算的表达式放到查询条件的右侧,就能用到索引了,像下面这样写就OK了。

WHERE col_1 > 100 / 1.1

如果无法避免在左侧进行运算,那么使用函数索引也是一种办法,但是不太推荐随意这么做。使用索引时,条件表达式的左侧应该是原始字段请牢记,这一点是在优化索引时首要关注的地方。

2.3 使用否定形式

下面这几种否定形式不能用到索引。

<>!=NOT

这个是跟具体数据库的优化器有关,如果优化器觉得即使走了索引,还是需要扫描很多很多行的哈,他可以选择直接不走索引。平时我们用!=、<>、not in的时候,要注意一下。

2.4 使用OR查询前后没有同时使用索引

例如下表:

CREATE TABLE test_tb ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(55) NOT NULLPRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

使用OR条件进行查询

SELECT * FROM test_tb WHERE id = 1 OR name = "tom"

这个SQL的执行条件下,很明显id字段查询会走索引,但是对于OR后面name字段的查询是需要进行全表扫描的。在这个场景下,优化器直接进行一遍全表扫描就完事了。

2.5 使用联合索引时,列的顺序错误

使用联合索引需要满足最左匹配原则,即最左优先。如果你建立一个(col_1, col_2, col_3)的联合索引,相当于建立了 (col_1)、(col_1,col_2)、(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 ;-- 没走索引SELECT * FROM SomeTable WHERE col_2 = 100 AND col_3 = 500 ;-- 没走索引SELECT * FROM SomeTable WHERE col_2 = 100 AND col_1 = 10 ;

联合索引中的第一列(col_1)必须写在查询条件的开头,而且索引中列的顺序不能颠倒。

2.6 使用LIKE查询

并不是用了like通配符,索引一定会失效,而是like查询是以%开头,才会导致索引失效。

-- 没走索引SELECT  *  FROM  SomeTable  WHERE  col_1  LIKE"%a";-- 没走索引SELECT  *  FROM  SomeTable  WHERE  col_1  LIKE"%a%";-- 走了索引SELECT  *  FROM  SomeTable  WHERE  col_1  LIKE"a%";

2.7 连接字段字符集编码不一致

如果两张表进行连接,关联字段编码不一致会导致关联字段上的索引失效,这是博主在线上经历一次SQL慢查询后的得到的结果,举例如下,有如下两表,它们的name字段都建有索引,但是编码不一致,user表的name字段编码是utf8mb4,user_job表的name字段编码是utf8,

CREATE TABLE `user` (  `id` int NOT NULL AUTO_INCREMENT,  `name` varchar(255) CHARACTER  SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,  `age` int NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;CREATE TABLE `user_job` (  `id` int NOT NULL,  `userId` int NOT NULL,  `job` varchar(255) DEFAULT NULL,  `name` varchar(255) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

进行SQL查询如下:

EXPLAINSELECT * from `user` u join user_job j on u.name = j.name

由结果可知,user表的查询没有走索引。想要user表也走索引,那就需要把user表name字段的编码改成utf8即可。

三、减少中间表

在SQL中,子查询的结果会被看成一张新表,这张新表与原始表一样,可以通过代码进行操作。这种高度的相似性使得SQL编程具有非常强的灵活性,但是如果不加限制地大量使用中间表,会导致查询性能下降。

频繁使用中间表会带来两个问题,一是展开数据需要耗费内存资源,二是原始表中的索引不容易使用到(特别是聚合时)。因此,尽量减少中间表的使用也是提升性能的一个重要方法。

3.1 使用HAVING子句

对聚合结果指定筛选条件时,使用HAVING子句是基本原则。不习惯使用HAVING子句的人可能会倾向于像下面这样先生成一张中间表,然后在WHERE子句中指定筛选条件。例如下面:

SELECT *   FROM (    SELECT sale_date, MAX(quantity) max_qty      FROM SalesHistory       GROUP BY sale_date     ) tmp WHERE max_qty >= 10

然而,对聚合结果指定筛选条件时不需要专门生成中间表,像下面这样使用HAVING子句就可以。

SELECT sale_date, MAX(quantity)  FROM SalesHistory GROUP BY sale_dateHAVING MAX(quantity) >= 10;

HAVING子句和聚合操作是同时执行的,所以比起生成中间表后再执行的WHERE子句,效率会更高一些,而且代码看起来也更简洁。

3.2 对多个字段使用IN

当我们需要对多个字段使用IN条件查询时,可以通过 || 操作将字段连接在一起变成一个字符串处理。

SELECT *  FROM Addresses1 A1 WHERE id || state || city    IN (SELECT id || state|| city          FROM Addresses2 A2);

这样一来,子查询不用考虑关联性,而且只执行一次就可以。

3.3 先进行连接再进行聚合

连接和聚合同时使用时,先进行连接操作可以避免产生中间表。原因是,从集合运算的角度来看,连接做的是“乘法运算”。连接表双方是一对一、一对多的关系时,连接运算后数据的行数不会增加。而且,因为在很多设计中多对多的关系都可以分解成两个一对多的关系,因此这个技巧在大部分情况下都可以使用。

到此本文讲解完毕,感谢大家阅读。

标签:

(责任编辑:new01)

相关文章

让SQL起飞(优化)

​最近博主看完了《SQL进阶教程》这本书,看完后给博主打开了SQL世界的新大门,对于SQL的理解不在局限于以前

2023-04-17 01:23:47

426家公司连续三年归母净利润同比增长 集中于生物医药等五大行业

​作为上市公司重要的财务指标之一,净利润是投资者阅读年报时的焦点。据同花顺数据显示,截至4月14日收盘,A

2023-04-16 21:44:37

环球观点:孕妇能吃西瓜吗孕晚期_孕妇能吃西瓜吗

​1、孕妇可以适量吃西瓜。2、西瓜富含葡萄糖、维生素A、B、C、D等。3、营养价值高,富含水、果糖、钾盐、苹

2023-04-16 19:53:41

偷了价值近10万元的香烟后 他们说,“没想到逃出了浙江还会被抓!”|当前速看

​“警察同志,我的香烟店昨晚被两个人偷了,他们太狠了,拿了我两箱价值近10万元的香烟……”4月12日上午9时

2023-04-16 18:15:23

【世界热闻】安徽省2022年调整企业退休人员养老金细则 定额调整上涨多少?

​江苏调整企业退休人员养老金方案2022~2023年今年退休人员养老金有上涨吗?每年快到养老金调整时间,很多人

2023-04-16 16:18:59

一尺等于多少米(一英尺等于多少米)

​1、“英寸”和“米”都是用来表示长度的长度计量单位,只不过英联邦和美国常用的是英寸,而米是国际通用的

2023-04-16 14:58:20

12306:给多人候补订票最好用多个账户下单_环球消息

​12306候补订票如何提高成功率?12306的工作人员表示,候补订单人数分为较多、中等、较少。候补人数较多并不

2023-04-16 14:34:37

焦点滚动:旬阳段家河的樱桃熟了!快去尝鲜!【959推荐】

​旬阳段家河的樱桃熟了!快去尝鲜!【959推荐】

2023-04-16 12:44:35

天天观天下!送50岁长辈男士礼物排行榜

​50岁的男士,可以送给他一些可以根据他的喜好来选择礼物,如果他喜欢运动,可以送他一些运动的运动产品,如

2023-04-16 12:20:08

【世界速看料】我国成功发射风云三号07星

​4月16日9时36分,我国在酒泉卫星发射中心使用长征四号乙运载火箭成功将风云三号07星发射升空,卫星顺利进入

2023-04-16 10:48:36

上海男篮惨了!遭篮协调查,球票卖不出去,或被取消8强资格!-天天快消息

​上海男篮惨了!遭篮协调查,球票卖不出去,或被取消8强资格!,李楠,篮协,李春江,江苏队,肯帝亚,上海男篮,深

2023-04-16 09:08:00

第133届广交会开幕:广州住宿机票预订量攀升|全球速讯

​每经AI快讯,广交会期间,来自全国各地的参展企业聚集在广州交流合作,相应的机票、酒店预订量都呈现上涨趋

2023-04-16 07:36:53

江苏徐州:红火烧烤,吃出幸福况味-当前观察

​光明日报记者 苏雁 光明日报通讯员 唐婧“老板,再来一斤羊肉串!”4月7日傍晚6点,江苏徐州一家烧烤店

2023-04-16 05:04:49

钙铁锌咀嚼片功效_钙铁锌

​1、钙铁锌是人身体内的微量元素。2、微量元素在人体内含量虽及其微小,但具有强大的生物科学作用。3、如果

2023-04-15 23:46:29

【世界时快讯】葡萄干不要直接吃很脏,教你正确清洗葡萄干方法,吃的干净又放心

​葡萄干酸酸甜甜的口感很好,但是我们平常吃的时候,就直接抓着吃了,但是这样的葡萄干不洗有多脏你知道吗?

2023-04-15 21:15:18

gsl是什么意思 gsl的中文是什么

​1 gsl是GlobalStarCraftⅡLeague的英文简写,翻译为全球星际争霸2联赛,每年的年底进行冠军角逐战。2 gsl是

2023-04-15 19:40:01

环球观焦点:大学生贫困申请书怎么写简短_大学生贫困申请书

​1、申请书我是XX中学X班的XX,我家住在一个偏僻的小山村里。2、家里有六口人,家中的劳动力只有父亲和母亲

2023-04-15 17:56:13

德泽尔比:三笘薰是球队最重要一员,喜欢他的积极进取_当前快讯

​北京时间今晚22:00,英超联赛第31轮布莱顿将赴客场迎战切尔西,据“进球网”报道,赛前布莱顿主帅德泽尔比

2023-04-15 16:10:31

焦点热议:保时捷公布新卡宴外观预热图 4月18日上海车展亮相

​【智车派新闻】不久前,保时捷正式宣布,将会在4月18日举办的上海车展上正式发布2024款卡宴新车。近日,保

2023-04-15 14:49:48

农夫与蛇的寓言故事是哪个国家的_农夫与蛇的寓言故事

​1、《农夫与蛇》的寓意是:在不清楚别人身份、不知道别人心底是否真诚的情况下,不要随意轻信别人,坏人不

2023-04-15 12:44:32

中职院校排名怎么收费 广东中职学校排行 环球报道

​今天,大学路小编为大家带来了中职院校排名怎么收费广东中职学校排行,希望能帮助到广大考生和家长,一起来

2023-04-15 11:22:26

Anna Teurnell,极简至尚,把自己活出高级感的女人! 全球独家

​嗨,大家好!我是跟大家分享怎么变美的南曦,希望我的文章会给大家带来一些时尚灵感,愿每个人都能活出自己

2023-04-15 09:45:07

申能股份:4月14日融券净卖出14.14万股,连续3日累计净卖出24.94万股_环球时讯

​4月14日,申能股份(600642)融资买入457 18万元,融资偿还1213 36万元,融资净卖出756 19万元,融资余额2 76亿元。

2023-04-15 08:29:32

秦刚同德国外长贝尔伯克举行第六轮中德外交与安全战略对话 全球微速讯

​每经AI快讯,4月14日,国务委员兼外长秦刚在北京同德国外长贝尔伯克举行第六轮中德外交与安全战略对话。秦

2023-04-15 06:30:34

华为、vivo、OPPO全亮相,今天机圈真热闹 世界热点

​今天手机圈真的很热闹,不信给大家盘点下:vivo官宣vivo正式官宣将在4月20日举行一场发布会,本次发布会的

2023-04-15 05:00:47

世界今头条!工行融e借贷款逾期了会怎样

​网贷逾期一般会上征信,有些借贷机构在用户逾期后一天后就会上报给征信机构,而有些借贷机构则是会在几天后

2023-04-14 23:47:20

药企高管频繁更迭!什么信号? 每日看点

​药企高管频繁更迭!什么信号?,药企,创新药,仿制药,生物药

2023-04-14 21:40:02

*ST中安股东户数下降4.06%,户均持股11.88万元 全球观天下

​*ST中安最新股东户数4 99万户,高于行业平均水平。公司户均持有流通股份4 57万股;户均流通市值11 88万元。

2023-04-14 20:19:54

第133届广交会静待开幕

​4月14日,第133届广交会展馆筹备就绪,静待开幕。  第133届中国进出口商品交易会(广交会)将于4月15日到

2023-04-14 19:10:10

青少年短视频沉迷问题难“根治”,需多方力量共同参与 焦点精选

​李梦怎么也没想到,她家幼儿园的孩子会沉迷在短视频里。李梦家的孩子自从上里幼儿园之后,好奇心也越来越重

2023-04-14 18:16:18