groupby多个字段性能_SQL性能优化
SQL 的性能优化是数据库⼯程师在实际⼯作中必须⾯对的课题之⼀。对于某些数据库⼯程师来说,它⼏乎是唯⼀的课题。实际上,在像Web 服务这样需要快速响应的应⽤场景中,SQL 的性能直接决定了系统是否可以使⽤。在 SQL 中,很多时候不同代码能够得出相同结果。从理论上来说,得到相同结果
的不同代码应该有相同的性能,但遗憾的是,查询优化器⽣成的执⾏计划很⼤程度上要受到代码外部结构的影响。因此如果想优化查询性能,必须知道如何写代码才能使优化器的执⾏效率更⾼。
⼀、使⽤⾼效查询
1.1 参数是⼦查询时,使⽤ EXISTS 代替 IN
在⼤多时候,[NOT] IN 和 [NOT] EXISTS 返回的结果是相同的。但是两者⽤于⼦查询时,EXISTS 的速度会更快⼀些。
如下两张表中包含了选课程 A 和课程 B 的学⽣。
Table: class_A
Table: class_B
从 class_A 表中出同时选了课程 B 的学⽣。
-- slow
使⽤ EXISTS 时更快的原因有以下两个。
如果连接列 (id) 上建⽴了索引,那么查询 class_B 时不⽤查实际的表,只需查索引就可以了。
如果使⽤EXISTS,那么只要查到⼀⾏数据满⾜条件就会终⽌查询,不⽤像使⽤ IN 时⼀样扫描全表。在这⼀点上 NOT EXISTS 也⼀样。
当 IN 的参数是⼦查询时,数据库⾸先会执⾏⼦查询,然后将结果存储在⼀张临时的⼯作表⾥(内联视图),然后扫描整个视图。很多情况下
从代码的可读性上来看,IN ⽐ EXISTS 好。使⽤这种做法都⾮常耗费资源。使⽤ EXISTS 的话,数据库不会⽣成临时的⼯作表。 但是从代码的可读性上来看,IN ⽐ EXISTS 好
IN 时的代码看起来更加⼀⽬了然,易于理解。因此,如果确信使⽤ IN 也能快速获取结果,就没有必要⾮得改成 EXISTS 了。
1.2 参数是⼦查询时,使⽤联结代替 IN
针对上述的问题,也可以改⽤联结:
SELECT
这种写法⾄少能⽤到⼀张表的 "id" 列上的索引。⽽且,因为没有⼦查询,所以数据库也不会⽣成中间表。
⼆、避免排序
在 SQL 中会进⾏排序的代表型运算有:
GROUP BY ⼦句
ORDER BY ⼦句
DISTINCT
聚合函数 (SUM, COUNT, AVG, MAX, MIN)
集合运算符 (UNION, INTERSECT, EXCEPT)
窗⼝函数 (RANK, DENSE_RANK ROW_NUMBER)
2.1 灵活使⽤集合运算符的 ALL 可选项sql优化的几种方式
SQL 中有 UNION, INTERSECT 和 EXCEPT 三个集合运算符。在默认的使⽤⽅式下,这些运算符会为了排除掉重复数据⽽进⾏排序。SELECT
UNION 查询结果 -- ⽆重复项
如果不在乎结果中是否有重复数据,或者事先知道不会有重复数据,尽量使⽤ UNION ALL 代替 UNION。这样就不会进⾏排序了。SELECT
UNION ALL 查询结果 -- 有重复项
对于 INTERSECT 和 EXCEPT 也是⼀样的,加上 ALL 可选项后就不会进⾏排序了。加上 ALL 可选项是优化性能的⼀个⾮常有效的⼿段,但在 MySQL 中 不⽀持 INTERCEPT ALL 和 EXCEPT ALL。
2.2 使⽤ EXISTS 代替 DISTINCT
为了排除重复数据,DISTINCT 也会进⾏排序。如果需要对两张表的联结结果进⾏去重,可以考虑使⽤ EXISTS 代替 DISTINCT,以避免排序。
如下 Items 表中包含某公司的各商品编号及名称;SalesHistory 表中包含各商品的销售情况。
Table: Item
Table: SalesHistory
从上⾯的商品表 Items 中出同时存在于销售记录 表 SalesHistory 中的商品
-- slow
2.3 在极值函数中使⽤索引
SQL 语⾔⾥有 MAX 和 MIN 两个极值函数。使⽤这两个函数时都会进⾏排序。但是如果参数字段上建有索引,则 只需要扫描索引,不需要扫描整张表。以刚才的表 Items 为例来说,SQL 语句可以像下⾯这样写:
-- slow
因为 item_no 是表 Items 的唯⼀索引,所以效果更好。对于联合索引,只要查询条件是联合索引的第⼀个字段,索引就是有效的,所以也可以对表 SalesHistory 的 sale_date 字段使⽤极值函数。这种⽅法并不是去掉了排序这⼀过程,⽽是优化了排序前的查速度,从⽽减弱排序对整体性能的影响。
2.4 能写在 WHERE ⼦句⾥的条件不要写在 HAVING ⼦句⾥
例如,下⾯两条 SQL 语句返回的结果是⼀样的。
-- slow
但是从性能上来看,第⼆条语句写法效率更⾼。原因通常有两个。
在使⽤ GROUP BY⼦句聚合时会进⾏排序,如果事先通过 WHERE ⼦句筛选出⼀部分⾏,就能够减轻排序的负担。
很多时候聚合后的视图都没有继在 WHERE ⼦句的条件⾥可以使⽤索引。HAVING ⼦句是针对聚合后⽣成的视图进⾏筛选的, 但是很多时候聚合后的视图都没有继承原表的索引结构。
承原表的索引结构
2.5 在 GROUP BY ⼦句和 ORDER BY ⼦句中使⽤索引
⼀般来说,GROUP BY ⼦句和 ORDER BY ⼦句都会进⾏排序,来对⾏进⾏排列和替换。不过,通过指定带索引的列作为 GROUP BY 和ORDER BY 的列,可以实现⾼速查询。特别是,在⼀些数据库中,如果操作对象的列上建⽴的是唯⼀索引,那么排序过程本⾝都会被省略掉。
三、使⽤索引
3.1 尽量避免在索引字段上进⾏运算
假设我们在⼀个叫作 col_1 的列上建⽴了索引,然后来看⼀看下⾯这条 SQL 语句。这条 SQL 语句本来是想使⽤索引,但实际上执⾏时却进⾏了全表扫描。
SELECT
像下⾯这样把运算的表达式放到查询条件的右侧,就能⽤到索引了。
SELECT
同样,在查询条件的左侧使⽤函数时,也不能⽤到索引。
SELECT
使⽤索引时,条件表达式的左侧应该是原始字段。
3.2 不要对索引使⽤ IS NULL 谓词
索引字段通常是不存在 NULL 的,所以指定 IS NULL 和 IS NOT NULL 的话会使索引⽆法使⽤,进⽽导致查询性能低下。
SELECT
如果需要使⽤类似 IS NOT NULL 的功能,⼜想⽤到索引,那么可以使⽤下⾯的⽅法,假设“col_1”列的最⼩值是 1。
-- IS NOT NULL 的替代⽅案
3.3 尽量避免否定形式
下⾯这⼏种否定形式不能⽤到索引。
<>
!=
NOT IN
因此,下⾯的 SQL 语句也会进⾏全表扫描。
SELECT
3.4 尽量避免使⽤ OR
在 col_1 和 col_2 上分别建⽴了不同的索引,或者建⽴了 (col_1, col_2) 这样的联合索引时,如果使⽤ OR 连接条件,那么要么⽤不到索引,要么⽤到了但是效率⽐ AND 要差很多。
3.5 使⽤联合索引时 注意列的顺序
假设存在这样顺序的⼀个联合索引“col_1, col_2, col_3”。 这时,指定条件的顺序就很重要。联合索引中的第⼀列 (col_1) 必须写在查询条件的开头,⽽且索引中列的顺序不能颠倒。有些数据库⾥顺序颠倒
后也能使⽤索引,但是性能还是⽐顺序正确时差⼀些。
-- fast
3.6 使⽤ LIKE 谓词前⽅⼀致
使⽤ LIKE 谓词时,只有前⽅⼀致的匹配才能⽤到索引。
-- fast
四、减少中间表
频繁使⽤中间表会带来两个问题,⼀是展开数据需要耗费内存资源, ⼆是原始表中的索引不容易使⽤到(特别是聚合时)。因此,尽量减少中间表的使⽤也是提升性能的⼀个重要⽅法。
4.1 灵活使⽤ HAVING ⼦句
HAVING ⼦句和聚合操作是同时执⾏的,所以⽐起⽣成中间表后再执⾏的 WHERE ⼦句,效率会更⾼⼀些,⽽且代码看起来也更简洁。
-- slow
4.2 需要对多个字段使⽤ IN 谓词时,将他们汇总到⼀处
这种⽅法与前⾯的连接字段的⽅法相⽐有两个优点。⼀是不⽤担⼼连 接字段时出现的类型转换问题,⼆是这种⽅法不会对字段进⾏加⼯,因此 可以使⽤索引。
-- slow
4.3 先联结再聚合
先联结可以避免产⽣中间表。因为从集合运算的⾓度来看,联结做的是“乘法运算”。 联结表双⽅是⼀对⼀、⼀对多的关系时,联结运算后数据的⾏数不会增加。 ⽽且,因为在很多设计中多对多的关系都可以分解成两个⼀对多的关系, 因此这个技巧在⼤部分情况下都可以使⽤。
4.4 合理使⽤视图
在视图的定义语句中包含以下运算的时候,SQL 会⾮常低效,执⾏速度也会变得⾮常慢。
聚合函数 (SUM, COUNT, AVG, MAX, MIN)
集合运算符 (UNION, INTERSECT, EXCEPT)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论