優化查詢匹配數組的前 N 項
我現在正在使用一個充滿國際象棋遊戲數據的 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;
我不確定它是否相關,但移動數據始終是 2-6 個字元的字母數字字元串,並且電腦評估主要是小數(正數和負數),但確實包括偶爾的特殊字元(強制將死有一個 octothorpe前綴)。
這是表格描述的相關片段 -
Column | Type | -----------------+--------------------------------+- id | bigint | moves | character varying(255)[] | evaluation | character varying(255)[] | "records_pkey" PRIMARY KEY, btree (id) Access method: heap
這是來自 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
這太慢了,但我不知道如何優化它——當談到 Postgres 時,我遠非專家,我所有建立索引的嘗試都沒有改變查詢計劃。
一些額外的想法
由於我的開局序列總是從遊戲開始時開始——我可能想在第 1 步到第 3 步或第 1 到 30 步時匹配,但從不從第 7 步到第 15 步進行匹配——我也考慮過將移動數據儲存為以空格分隔的文本字元串,並匹配字元串的開頭。(我也不知道如何優化該查詢,但也許這會更容易。)
雖然這些目前是字元串數組,但我可以將移動和評估都表示為整數數組。(不是我想要的,但是優化這個查詢更重要,如果有幫助,我會做的。)
你怎麼看?我應該從哪裡開始?
您需要快速索引支持。不重新設計你的桌子就很棘手。以下解決方案應該表現出色(微秒而不是秒),但需要一些技巧。係好安全帶。
IMMUTABLE
函式表達式索引只取幾個前導數組元素,比如 8。那應該已經非常有選擇性了。更多只會使索引更大,幾乎不需要額外的過濾。
轉換為字元串。沒有分隔符。這允許誤報,但不太重要。最終,無論如何,我們都會篩選出準確的結果。
IMMUTABLE
索引表達式中只允許使用函式。Butarray_to_string()
is onlySTABLE
, notIMMUTABLE
,因為它需要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$;
或者,更好的是,直接基於底層的 C 函式定義for only input的誠實
IMMUTABLE
變體。更快更清潔。讓我們說清楚。這需要超級使用者權限: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';
其餘的工作沒有超級使用者權限。
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$;
有關的:
無論哪種方式,我們現在都有一個
public.f_8moves(text[])
要在以下索引中使用的函式:CREATE INDEX records_8moves_idx ON records (public.f_8moves(moves) COLLATE "C");
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;
詢問
對於具有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;
COLLATE "C"
需要匹配索引。使用表達式
public.f_8moves(moves)
來匹配功能索引。表達式的右側可以直接作為字元串給出。但為方便起見,我使用相同的功能。然後添加原始精確過濾器以將結果縮小到精確匹配:
AND moves[1:10] = '{b4,e5,Bb2,d6,Nf3,Nf6,g3,Bg4,Bg2,h5}'
看起來多餘,邏輯上是多餘的,但允許使用索引效果很好。
對於少於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;
db<>在這裡擺弄
類似,有更多解釋:
集群表行
對錶行進行物理排序通常有助於提高性能,這樣每個查詢都可以從一個或幾個數據頁中讀取結果,而不是從各處獲取結果。
使用
CLUSTER
或 非阻塞替代品pg_repack
orpg_squeeze
。更多在上面的連結。