ran*_*awn 5 postgresql index optimization array postgresql-performance
我现在正在使用充满国际象棋游戏数据的 Postgres 数据库,其中每个游戏都是“记录”表中的一行。玩家的动作和这些动作的(可选)计算机评估都有自己的列并存储为数组。
我编写了一个查询来检索指定的开局动作序列的所有评估。(你可能认为计算机的评估会是一致的 - 但事实并非如此。)开局序列的长度是任意的 - 可以是一步,也可以是三十步。
下面是一个示例查询,它查找以相同的十步开局序列开始的所有游戏,然后对于每个带有评估的游戏,返回计算机对游戏中该点的评估 -
SELECT evaluation[10]
FROM records
WHERE moves[1:10]::text[] = ARRAY['b4', 'e5', 'Bb2', 'd6', 'Nf3', 'Nf6', 'g3', 'Bg4', 'Bg2', 'h5']::text[]
AND evaluation IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
我不确定它是否相关,但移动数据始终是 2-6 个字符的字母数字字符串,并且计算机评估大部分是小数(正数和负数),但确实包括偶尔的特殊字符(强制将死者有一个 octothorpe前缀)。
这是表描述的相关片段 -
Column | Type |
-----------------+--------------------------------+-
id | bigint |
moves | character varying(255)[] |
evaluation | character varying(255)[] |
"records_pkey" PRIMARY KEY, btree (id)
Access method: heap
Run Code Online (Sandbox Code Playgroud)
这是来自 EXPLAIN ANALYZE 的查询计划:
Gather (cost=1000.00..736354.70 rows=905 width=516) (actual time=28251.267..28253.139 rows=0 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on records (cost=0.00..735264.20 rows=377 width=516) (actual time=28243.233..28243.234 rows=0 loops=3)
Filter: ((evaluation IS NOT NULL) AND ((moves[1:10])::text[] = '{b4,e5,Bb2,d6,Nf3,Nf6,g3,Bg4,Bg2,h5}'::text[]))
Rows Removed by Filter: 971361
Planning Time: 8.275 ms
Execution Time: 28253.915 ms
Run Code Online (Sandbox Code Playgroud)
这太慢了,但我不知道如何优化它——就 Postgres 而言,我远非专家,而且我所有设置索引的尝试都没有对查询计划产生任何影响。
因为我的开局序列总是从游戏的开头开始——我可能想匹配第 1 到 3 步或第 1 到 30 步,但永远不会,比如说从第 7 步到第 15 步——我也考虑过将移动数据存储为以空格分隔的文本字符串,并与字符串的开头进行匹配。(我也不知道如何优化该查询,但也许这会更容易。)
虽然这些当前是字符串数组,但我可以将移动和评估表示为整数数组。(不是我想要的,但是优化这个查询更重要,如果有帮助,我会这样做。)
你怎么认为?我应该从哪里开始?
您需要索引支持才能快速。如果不重新设计你的桌子就很棘手。以下解决方案应该表现出色(微秒而不是秒),但需要一些技巧。系好安全带。
IMMUTABLE
函数的表达式索引只需取几个前导数组元素,比如 8。这应该已经是非常有选择性的了。更多只会使索引更大,几乎不需要额外的过滤。
转换为字符串。无分隔符。这允许误报,但不太可能产生影响。最终,我们还是会过滤出准确的结果。
索引表达式中只IMMUTABLE
允许使用函数。但array_to_string()
只是STABLE
,而不是IMMUTABLE
,因为它需要anyarray
并且某些元素类型没有不可变的文本表示形式。我们只处理text
(好吧,varchar(255)
没有充分的理由,但都是一样的),事实上,这是不可变的。但array_to_string()
不知道这一点。
所以我们可以“伪造”一个不可变的包装函数。对于任何可以创建函数的用户来说都是可能的:
CREATE OR REPLACE FUNCTION public.f_8moves(text[])
RETURNS text
LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT array_to_string($1[1:8], '')
$func$;
Run Code Online (Sandbox Code Playgroud)
或者,更好的是,直接基于底层 C 函数定义一个诚实的for only inputIMMUTABLE
变体。更快更干净。让我们称其为明确的。这需要超级用户权限:array_to_string()
text[]
array_to_string_immutable()
-- SET ROLE postgres; -- you must be superuser
CREATE OR REPLACE FUNCTION public.array_to_string_immutable(text[], text)
RETURNS text
LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'array_to_text';
Run Code Online (Sandbox Code Playgroud)
其余的工作无需超级用户权限。
CREATE OR REPLACE FUNCTION public.f_8moves(text[])
RETURNS text
LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT public.array_to_string_immutable($1[1:8], '')
$func$;
Run Code Online (Sandbox Code Playgroud)
有关的:
不管怎样,我们现在有一个public.f_8moves(text[])
可以在以下索引中使用的函数:
CREATE INDEX records_8moves_idx ON records (public.f_8moves(moves) COLLATE "C");
Run Code Online (Sandbox Code Playgroud)
COLLATE "C"
正是我们需要允许左锚定(您表达的要求)LIKE
表达式。看:
如果主要百分比的行具有evaluation IS NOT NULL
,则将该过滤器添加到索引中,使其成为顶部的部分索引:
CREATE INDEX records_8moves_idx ON records (public.f_8moves(moves) COLLATE "C")
WHERE evaluation IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
对于具有10 个或更多数组元素的查询:
SELECT evaluation[10]
FROM records
WHERE public.f_8moves(moves) = public.f_8moves('{b4,e5,Bb2,d6,Nf3,Nf6,g3,Bg4,Bg2,h5}') COLLATE "C"
AND moves[1:10] = '{b4,e5,Bb2,d6,Nf3,Nf6,g3,Bg4,Bg2,h5}'
AND evaluation IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
COLLATE "C"
需要匹配索引。
使用表达式public.f_8moves(moves)
来匹配功能索引。表达式的右侧可以直接作为字符串给出。但为了方便我使用相同的功能。
然后添加原始精确过滤器以将结果缩小到精确匹配:
AND moves[1:10] = '{b4,e5,Bb2,d6,Nf3,Nf6,g3,Bg4,Bg2,h5}'
Run Code Online (Sandbox Code Playgroud)
看起来多余,逻辑上也是多余,但是可以让索引发挥很大的作用。
对于少于8 个元素的数组(我们的索引引导),或者一般情况下,使用LIKE
左锚定模式:
SELECT evaluation[10]
FROM records
WHERE public.f_8moves(moves) LIKE (public.f_8moves('{b4,e5}') || '%') COLLATE "C" -- COLLATE "C" is optional for LIKE
AND moves[1:2] = '{b4,e5}'
AND evaluation IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
db<>在这里摆弄
类似,但有更多解释:
对表行进行物理排序通常有助于提高性能,以便每个查询可以从一个或几个数据页读取结果,而不是从各处获取结果。
使用CLUSTER
或 非阻塞替代方案pg_repack
或pg_squeeze
。更多内容在上面的链接中。
归档时间: |
|
查看次数: |
1086 次 |
最近记录: |