postgresql中like和ilike语句的性能提升
2020-10-23 08:58 abce 阅读(4701) 评论(0) 编辑 收藏 举报LIKE和ILIKE是SQL的两个基本功能。人们在他们的应用程序中到处使用这些东西,因此从性能的角度来看这个主题是有意义的。PostgreSQL可以采取哪些措施来加快这些操作的速度,通常可以采取哪些措施要首先了解问题,其次才能获得更好的PostgreSQL数据库性能。
创建示例数据
在本文中,你将学到关于Gist和GIN索引的大部分知识。这两种索引类型都可以处理LIKE和ILIKE。这些索引类型的效率不一样,因此深入研究主题并找出最佳的索引类型是有意义的。
1 2 3 4 5 | test=# CREATE TABLE t_hash AS SELECT id, md5(id::text) FROM generate_series(1, 50000000) AS id; SELECT 50000000 test=# VACUUM ANALYZE; VACUUM |
让我们看一下数据。我们这里有5000万个id和它们的哈希表。下面的清单显示了数据的一般情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | test=# SELECT * FROM t_hash LIMIT 10; id | md5 ----+---------------------------------- 1 | c4ca4238a0b923820dcc509a6f75849b 2 | c81e728d9d4c2f636f067f89cc14862c 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 4 | a87ff679a2f3e71d9181a67b7542122c 5 | e4da3b7fbbce2345d7772b0674a318d5 6 | 1679091c5a880faf6fb5e6087eb1b2dc 7 | 8f14e45fceea167a5a36dedd4bea2543 8 | c9f0f895fb98ab9159f51fd0297e236d 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 10 | d3d9446802a44259755d38e6d163e820 (10 rows ) |
运行简单的like查询
1 2 3 4 5 6 7 8 | test=# \timing Timing is on . test=# SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; id | md5 ----------+---------------------------------- 37211731 | dadb4b54e2345679a8861ab52e4128ea (1 row) Time : 4767.415 ms (00:04.767) |
在我的iMac上,查询需要4.7秒才能完成。在90%以上的应用程序中,这已经太长了。用户体验已经受到煎熬,长期运行的查询很有可能已经大大增加了服务器的负载。
1 2 3 4 5 6 7 8 9 | test=# explain SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; QUERY PLAN ------------------------------------------------------------------------------ Gather (cost=1000.00..678583.88 rows =5000 width=37) Workers Planned: 2 -> Parallel Seq Scan on t_hash (cost=0.00..677083.88 rows =2083 width=37) Filter: (md5 ~~ '%e2345679a%' ::text) (4 rows ) Time : 11.531 ms |
由于表的大小,PostgreSQL查询优化器将进行并行查询。这基本上是一件好事,因为执行时间减少了一半。但是:这也意味着我们很容易牺牲两个CPU内核来回答仅返回一行的查询。
1 2 3 4 5 6 | test=# \dt+ List of relations Schema | Name | Type | Owner | Size | Description --------+--------+-------+-------+---------+------------- public | t_hash | table | hs | 3256 MB | (1 row) |
读取3.2 GB来获取单个数据现在已经非常高效了。
pg_trgm:高级索引
幸运的是,PostgreSQL提供了一个模块,它在模式匹配方面提供了很多技巧。pg_trgm扩展实现了“trigrams”,这是一种帮助模糊搜索的方法。该扩展是PostgreSQL contrib包的一部分,因此应该出现在绝大多数系统上:
1 2 3 | test=# CREATE EXTENSION pg_trgm; CREATE EXTENSION Time : 77.216 ms |
如你所见,启用扩展很容易。现在的问题是:什么是trigram?让我们一起来看看:
1 2 3 4 5 | test=# SELECT show_trgm( 'dadb4b54e2345679a8861ab52e4128ea' ); show_trgm --------------------------------------------------------------------------------------------------------------------------------------------- { " d" , " da" ,128,1ab,234,28e,2e4,345,412,456,4b5,4e2,52e,54e,567,61a,679,79a,861,886,8ea,9a8,a88,ab5,adb,b4b,b52,b54,dad,db4,e23,e41, "ea " } (1 row) |
你可以观察到trigram就像一个滑动的3个字符的窗口。所有这些标记都将显示在索引中,稍后你将看到。
为了支持like使用索引,pg_trgm模块支持两种PostgreSQL索引类型:Gist和GIN。两种都将被评估。
许多人在PostgreSQL中加速模糊搜索的方法是使用Gist索引。下面是如何部署这种类型的索引:
1 2 3 | test=# CREATE INDEX idx_gist ON t_hash USING gist (md5 gist_trgm_ops); CREATE INDEX Time : 2383678.930 ms (39:43.679) |
可以看到,构建索引需要相当长的时间。需要指出的重要一点是,即使设置更高的maintenance_work_mem也不会加快进程。即使有4GB的maintenance_work_mem,这个过程也需要40分钟。
1 2 3 4 5 6 | test=# \di+ List of relations Schema | Name | Type | Owner | Table | Size | Description --------+----------+-------+-------+--------+---------+------------- public | idx_gist | index | hs | t_hash | 8782 MB | (1 row) |
这个表只有3.5 GB——索引是表的2.5倍大。
1 2 3 4 5 6 | test=# SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; id | md5 ----------+---------------------------------- 37211731 | dadb4b54e2345679a8861ab52e4128ea (1 row) Time : 105506.064 ms (01:45.506) |
我们真的“优化”了查询?不是4.7秒,PostgreSQL几乎需要2分钟来完成这项工作。为什么会这样呢?让我们来看看执行计划是怎么说的:
1 2 3 4 5 6 7 8 9 | test=# explain SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; QUERY PLAN ---------------------------------------------------------------------------- Bitmap Heap Scan on t_hash (cost=495.30..18812.90 rows =5000 width=37) Recheck Cond: (md5 ~~ '%e2345679a%' ::text) -> Bitmap Index Scan on idx_gist (cost=0.00..494.05 rows =5000 width=0) Index Cond: (md5 ~~ '%e2345679a%' ::text) (4 rows ) Time : 13.433 ms |
PostgreSQL优化器决定进行“位图索引扫描”。所以直接索引扫描可能更好?
1 2 3 4 5 6 7 8 9 10 11 12 13 | test=# SET enable_bitmapscan TO off ; SET Time : 11.302 ms test=# explain analyze SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; QUERY PLAN ----------------------------------------------------------------------------- Index Scan using idx_gist on t_hash (cost=0.55..20428.04 rows =5000 width=37) (actual time =13750.850..99070.510 rows =1 loops=1) Index Cond: (md5 ~~ '%e2345679a%' ::text) Planning Time : 0.074 ms Execution Time : 99070.618 ms (4 rows ) Time : 99072.657 ms (01:39.073) |
实际上,查询仍然会显示糟糕的执行时间。
Gin
幸运的是,pg_trgm扩展提供了第二个操作符类来完成这项工作。GIN索引通常用于PostgreSQL全文搜索(FTS)。让我们测试看看是否也能加速like和ilike查询?在此之前,我们重置当前连接并删除旧索引:
1 2 3 4 5 6 | test=# DISCARD ALL ; DISCARD ALL Time : 12.000 ms test=# DROP INDEX idx_gist; DROP INDEX Time : 3123.336 ms (00:03.123) |
创建一个新索引:
1 2 3 | test=# CREATE INDEX idx_gin ON t_hash USING gin (md5 gin_trgm_ops); CREATE INDEX Time : 698063.038 ms (11:38.063) |
在我的机器上,它需要11分钟,这是很多,但实际上比创建Gist索引要快得多。但是,索引创建只发生一次,所以在这种情况下我们不应该太担心。通常更重要的是查询执行时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | test=# explain analyze SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; QUERY PLAN ------------------------------------------------------------------------------ Bitmap Heap Scan on t_hash (cost=2270.75..20588.36 rows =5000 width=37) (actual time =74.592..74.593 rows =1 loops=1) Recheck Cond: (md5 ~~ '%e2345679a%' ::text) Heap Blocks: exact=1 -> Bitmap Index Scan on idx_gin (cost=0.00..2269.50 rows =5000 width=0) (actual time =74.584..74.584 rows =1 loops=1) Index Cond: (md5 ~~ '%e2345679a%' ::text) Planning Time : 0.066 ms Execution Time : 74.665 ms (7 rows ) Time : 75.031 ms |
可以在75毫秒内运行查询,而不是4.7秒。这是一个重大的飞跃。一个索引的帮助——数据库性能的巨大飞跃。
1 2 3 4 5 6 7 | test=# SELECT * FROM t_hash WHERE md5 LIKE '%e2345679a%' ; id | md5 ----------+---------------------------------- 37211731 | dadb4b54e2345679a8861ab52e4128ea (1 row) Time : 74.487 ms |
到目前为止,你所看到的是GIN索引解决了这个问题。但是,这里可能仍然需要第二个索引。GIN不会加速“=”操作符。因此,如果你正在执行一个普通的查找,你将需要第二个索引,如下面的例子所示:
1 2 3 4 5 6 7 8 9 10 | test=# CREATE INDEX idx_btree ON t_hash (md5); CREATE INDEX Time : 274778.776 ms (04:34.779) test=# \di+ List of relations Schema | Name | Type | Owner | Table | Size | Description --------+-----------+-------+-------+--------+---------+------------- public | idx_btree | index | hs | t_hash | 2816 MB | public | idx_gist | index | hs | t_hash | 2807 MB | (2 rows ) |
需要一个btree来加速普通的比较。仅仅一个GIN索引是不够的:
1 2 3 4 5 6 7 | test=# SELECT * FROM t_hash WHERE md5 = 'dadb4b54e2345679a8861ab52e4128ea' ; id | md5 ----------+---------------------------------- 37211731 | dadb4b54e2345679a8861ab52e4128ea (1 row) Time : 0.379 ms |
最后
PostgreSQL提供了真正强大的索引策略。除了btree索引之外,还有很多东西需要发现。Gist和Gin也有它们的优点。GIN对于所有类型的全文操作特别有用,而Gist对于几何数据(GIS)非常理想。
如果你想了解更多关于Gin索引的信息,请查看我发布的关于 Gin索引的文章列表和vacuum的帖子(https://www.cybertec-postgresql.com/en/what-postgresql-full-text-search-has-to-do-with-vacuum/)。
https://www.cybertec-postgresql.com/en/postgresql-more-performance-for-like-and-ilike-statements/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)