原文:https://www.cdsy.xyz/computer/soft/database/postgresql/230425/cd43192.html
通过学习别人的优化思路,有助于在以后碰到类似问题的时候可以回忆这些内容,当然最佳学习和实践是看完本文把这些优化手段用到过去的SQL中进行优化。
本文的所有优化手段只针对Postgresql数据库。
PostgreSql 不只有btree索引,还有brin索引和Gin索引 :
BRIN索引:存储关于存储在表的连续物理块范围内的值的摘要,可以使用BRIN索引的特定操作符根据索引策略而变化
Gin索引:Gin索引是用来加快全文搜索的,适合做模糊查询和正则查询。
with的场景主要如下:
使用:在WITH子句中的每一个辅助语句可以是一个SELECT、INSERT、UPDATE或DELETE,并且WITH子句本身也可以被附加到一个主语句,主语句也可以是SELECT、INSERT、UPDATE或DELETE。
关联:[[【Postgresql】Update a PostgreSQL table using a WITH query]]
通过禁用顺序扫描强制走索引的方式提速:
SET enable_seqscan = OFF;
从结果来看,查询几乎全部走了索引,效率和性能也有了明显提升:
sql 中用到了表连接,group by ,count以及sum 函数,这些十分消耗数据库性能的操作占用绝大多数的查询时间,但是根据作者描述,数据会不断有增量,到了200万的时候查询时间到了40S。
因为是从Mysql转到Postgresql 使用,字段使用的都是Btree索引,这也是大部分情况下比较常见的选择,当然也是大部分人想得到的优化(包括我)。
CREATE INDEX indexname ON tablename (code)CREATE INDEX indexname ON tablename (code)
具体分析之前先看下优化的SQL对比,整个SQL优化前后的写法差距还是挺大的,建议拿对比工具左右对比两个SQL的差别:
优化前的代码
select
count(retrival.id) retrivalNum,
retrival.index_code,
retrival.camera_name,
province."name" place,
sum(end_time-start_time) retrivalDuration,
retrival.province_code
from
retrival
left join
resource
on
retrival.index_code = resource.index_code
left join
province
on
resource.province_code = province.code
group by
retrival.index_code,resource.province_code,retrival.camera_name
WITH tm AS (
SELECT
retrival.index_code,
COUNT
( retrival.index_code ) retrivalNum,
SUM ( end_time - start_time ) retrivalDuration
FROM
retrival
JOIN province ON retrival.province_code = province.code
WHERE
retrival.start_time >= ''
AND retrival.end_time <= ''
AND POSITION ( '100000' IN province.PATH ) > 0
GROUP BY
retrival.index_code
ORDER BY retrival.index_code
LIMIT 25 OFFSET 0
)
SELECT
distinct retrival.index_code,
retrival.province_code ,
retrival.camera_name,
province."name" place,
t.retrivalNum, t.retrivalDuration
FROM retrival
JOIN tm t ON retrival.index_code = t.index_code
JOIN province ON retrival.province_code = province.code
ORDER BY retrival.index_code
LIMIT 25 OFFSET 0
首先是简化表关联,resouce表后续被关联字段替代,从改动后结构猜测应该是在表retrival当中。
select
count(retrival.id) retrivalNum,
retrival.index_code,
retrival.camera_name,
province."name" place,
sum(end_time-start_time) retrivalDuration,
retrival.province_code
from
retrival
left join
province
on
retrival.province_code = province.code
group by
retrival.index_code,retrival.province_code,retrival.camera_name,province.name
这部分实践起来比较简单,属于比较容易想到的优化手段,这里就不过多分析了。
胡乱加索引可能会有反效果(就像本文作者自己的捣鼓),所以加完索引之后不一定会改善。查询时间还是30s左右
对数据进行分页也是没有效果的,利用业务代码处理一部分SQL逻辑,这种处理方式同样不一定会提升效率,反而因为更多的查询拖慢业务处理速度。
create extension pg_trgm;
create extension btree_gin; (必须管理员权限执行)
create index indexname on tablename using gin (...)
create index indexname on tablename using brin (...) with (pages_per_range=1)
个人顺带做了一下删除已有索引和添加grin索引的笔记:
DROP INDEX if exists "index_date" ;
CREATE INDEX if not exists index_date ON table USING brin (date);
索引分类和调整查询顺序之后,查询时间在15s左右。但是发现下面部分的查询依然很耗时:
group by
retrival.index_code,retrival.province_code,retrival.camera_name,province.name
在with 中只根据count里面的字段进行优化,消耗数据库性能的操作放到with中完成,其他字段根据with生成的“临时表”查询即可,最终通过合理的索引设计和查询字段顺序,实现分页查询执行时间只需要几百毫秒。
WITH tm AS (
SELECT
retrival.index_code,
COUNT
( retrival.index_code ) retrivalNum,
SUM ( end_time - start_time ) retrivalDuration
FROM
retrival
JOIN province ON retrival.province_code = province.code
WHERE
retrival.start_time >= ''
AND retrival.end_time <= ''
-- AND retrival.camera_name LIKE'%研%'
AND POSITION ( '100000' IN province.PATH ) > 0
GROUP BY
retrival.index_code
ORDER BY retrival.index_code
LIMIT 25 OFFSET 0
)
SELECT
distinct retrival.index_code,
retrival.province_code ,
retrival.camera_name,
province."name" place,
t.retrivalNum, t.retrivalDuration
FROM retrival
JOIN tm t ON retrival.index_code = t.index_code
JOIN province ON retrival.province_code = province.code
ORDER BY retrival.index_code
LIMIT 25 OFFSET 0
作者的原话:
导师提出了pgsql 的with 语句,此处,将group by 拆分,在with 中只根据count里面的字段进行统计,此处with 相当于将结果放入内存作为一个临时表。然后再从内存中关联其他需要的字段。这样优化之后分页查询执行时间只需要几百毫秒。
这里的思路挺好理解的,就是把所有设计函数的操作和计算放到with当中执行,为了提高处理速度同时也加了分页操作。即把一个一次性统计变为每次统计一小部分,最后合并统计结果的思路。
然而对比SQL会发现这里的group by去掉了三个条件:
retrival.province_code,retrival.camera_name,province.name
对应了“将group by 拆分,在with 中只根据count里面的字段进行统计” 这句话,少了三个列的group,去重操作用distinct替代,实际上这种写法在有索引的情况下并没有区别,分页查询数据使用distinct去重的开销也可以接受。
with之后的结果只有很少的一部分数据,查询也根据with的结果作为主表查询,最终结果同样进行分页,这时候优化其他字段就简单很多了(也就是开头作者所说的where字段添加索引即可),此外发现调整SQL之后分页也确实有了显著的提升查询速度的效果。
虽然加了with语句,但是整个语句可读性并没有复杂多少,还是比较好理解的。大部分SQL内容变化不大就不再介绍了。
大致的结论是:
在语义相同,有索引的情况下: group by和distinct都能使用索引,效率相同。
在语义相同,无索引的情况下:distinct效率高于group by。原因是distinct 和 group by都会进行分组操作,但group by可能会进行排序,触发filesort,导致sql执行效率低下。
总结:
收获:
Postgresql 的优化案例并不多,在加上Postgresql国内确实实践的少,中文文档也基本是机翻,难顶。