2013-07-22 39 views
4

我目前工作的一個複雜的排序問題的Postgres 9.2 你可以找到源代碼在這個問題(簡化版)使用此:http://sqlfiddle.com/#!12/9857e/11PostgreSQL的排序連接表與索引

我有一個巨大的( >> 20Mio行)包含各種不同類型列的表。

CREATE TABLE data_table 
(
    id bigserial PRIMARY KEY, 
    column_a character(1), 
    column_b integer 
    -- ~100 more columns 
); 

可以說,我想排序此表超過2列ASC)。 但我不想用簡單的Order By做到這一點,因爲稍後我可能需要在排序的輸出中插入行,用戶可能只希望看到100行(排序後的輸出) 。

爲了實現這些目標,我這樣做:

CREATE TABLE meta_table 
(
id bigserial PRIMARY KEY, 
id_data bigint NOT NULL -- refers to the data_table 
); 

--Function to get the Column A of the current row 
CREATE OR REPLACE FUNCTION get_column_a(bigint) 
RETURNS character AS 
'SELECT column_a FROM data_table WHERE id=$1' 
LANGUAGE sql IMMUTABLE STRICT; 

--Function to get the Column B of the current row 
CREATE OR REPLACE FUNCTION get_column_b(bigint) 
RETURNS integer AS 
'SELECT column_b FROM data_table WHERE id=$1' 
LANGUAGE sql IMMUTABLE STRICT; 

--Creating a index on expression: 
CREATE INDEX meta_sort_index 
ON meta_table 
USING btree 
(get_column_a(id_data), get_column_b(id_data), id_data); 

然後我的data_table的Id的複製到meta_table:

INSERT INTO meta_table(id_data) (SELECT id FROM data_table); 

後來我可以表增加新行一個類似的簡單插入。
要獲得行900000 - 900099(100行)我現在可以使用:

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
ORDER BY 1,2,3 OFFSET 900000 LIMIT 100; 

(帶一個附加內的data_table JOIN如果我想所有的數據)
生成的計劃是:

Limit (cost=498956.59..499012.03 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..554396.21 rows=1000000 width=8) 

這是一個非常有效的計劃(索引只掃描是Postgres 9.2中的新增功能)。
但是,如果我想獲得20'000'000 - 20'000'099(100行)的行嗎?同樣的計劃,更長的執行時間。那麼,爲了提高膠印性能(Improving OFFSET performance in PostgreSQL),我可以做以下事情(讓我們假設我每隔第100行將其保存到另一張表中)。

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE (get_column_a(id_data), get_column_b(id_data), id_data) >= (get_column_a(587857), get_column_b(587857), 587857) 
ORDER BY 1,2,3 LIMIT 100; 

這運行得更快。由此產生的計劃是:

Limit (cost=0.51..61.13 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.51..193379.65 rows=318954 width=8) 
Index Cond: (ROW((get_column_a(id_data)), (get_column_b(id_data)), id_data) >= ROW('Z'::bpchar, 27857, 587857)) 

到目前爲止一切正常,postgres做得很好!

我們假設我想將第二列的順序更改爲DESC
但後來我將不得不改變我的WHERE子句,因爲>運算符比較兩列的ASC。如上(ASC排序)同樣的查詢也可以寫成:

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE 
    (get_column_a(id_data) > get_column_a(587857)) 
OR (get_column_a(id_data) = get_column_a(587857) AND ((get_column_b(id_data) > get_column_b(587857)) 
OR (             (get_column_b(id_data) = get_column_b(587857)) AND (id_data >= 587857)))) 
ORDER BY 1,2,3 LIMIT 100; 

現在的計劃變更和查詢速度變慢:

Limit (cost=0.00..1095.94 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..1117877.41 rows=102002 width=8) 
Filter: (((get_column_a(id_data)) > 'Z'::bpchar) OR (((get_column_a(id_data)) = 'Z'::bpchar) AND (((get_column_b(id_data)) > 27857) OR (((get_column_b(id_data)) = 27857) AND (id_data >= 587857))))) 

我如何使用高效的舊方案與DESC-訂購?
你有什麼更好的想法如何解決這個問題?

(我已經嘗試聲明一個自己的類型與自己的操作符表,但是這太慢了)

+0

感謝http://stackoverflow.com/questions/1677538/advanced-indexing-involving-or-ed-conditions-pgsql我試過聯盟。這比上一個計劃好一點,但還不夠。 http://sqlfiddle.com/#!12/9857e/28/3 – Dreamcooled

回答

4

你需要重新考慮你的方法。從哪裏開始?這是一個明顯的例子,基本上是您在SQL中使用的那種功能方法的性能方面的限制。函數在很大程度上是不透明的,並且您爲data_table強制執行兩次不同的查找,因爲存儲過程的計劃不能合併在一起。

現在,更糟糕的是,您正在基於另一個表中的數據對一個表進行索引。這可能工作只追加工作量(插入允許,但沒有更新),但它會而不是工作,如果data_table可以應用更新。如果data_table中的數據發生更改,則將返回錯誤的結果。

在這些情況下,你是差不多總是會更好地在聯接中書寫顯式,並讓計劃者找出檢索數據的最佳方式。

現在你的問題是,當你改變你的第二列的順序時,你的索引變得沒那麼有用了(而且I/O方面更強大的磁盤)。另一方面,如果你在data_table上有兩個不同的索引並有明確的連接,PostgreSQL可以更容易地處理這個問題。