索引不與表繼承一起使用
我有一個帶有主表和 2 個子表的 PostgreSQL 9.0.12 數據庫。我的桌子:
CREATE TABLE test2 ( id serial PRIMARY KEY, coll character varying(15), ts timestamp without time zone ); CREATE INDEX ON test2(ts); CREATE TABLE test2_20150812 ( CHECK ( ts >= timestamp '2015-08-12' AND ts < timestamp '2015-08-13' ) ) INHERITS (test2); CREATE TABLE test2_20150811 ( CHECK ( ts >= timestamp '2015-08-11' AND ts < timestamp '2015-08-12' ) ) INHERITS (test2); CREATE INDEX ON test2_20150812(ts); CREATE INDEX ON test2_20150811(ts); VACUUM FULL ANALYZE;
我的選擇查詢的解釋結果(數據庫中沒有任何行):
EXPLAIN (ANALYZE, BUFFERS) select * from test2 WHERE ts >= '2015-08-11' ORDER BY ts DESC; Sort (cost=89.87..92.09 rows=887 width=31) (actual time=0.245..0.245 rows=0 loops=1) Sort Key: public.test2.ts Sort Method: quicksort Memory: 17kB Buffers: shared read=2 -> Result (cost=0.00..46.44 rows=887 width=31) (actual time=0.087..0.087 rows=0 loops=1) Buffers: shared read=2 -> Append (cost=0.00..46.44 rows=887 width=31) (actual time=0.078..0.078 rows=0 loops=1) Buffers: shared read=2 -> Seq Scan on test2 (cost=0.00..0.00 rows=1 width=31) (actual time=0.007..0.007 rows=0 loops=1) Filter: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) -> Bitmap Heap Scan on test2_20150812 test2 (cost=7.68..23.22 rows=443 width=31) (actual time=0.024..0.024 rows= 0 loops=1) Recheck Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) Buffers: shared read=1 -> Bitmap Index Scan on test2_20150812_ts_idx (cost=0.00..7.57 rows=443 width=0) (actual time=0.016..0.016 rows=0 loops=1) Index Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) Buffers: shared read=1 -> Bitmap Heap Scan on test2_20150811 test2 (cost=7.68..23.22 rows=443 width=31) (actual time=0.033..0.033 rows= 0 loops=1) Recheck Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) Buffers: shared read=1 -> Bitmap Index Scan on test2_20150811_ts_idx (cost=0.00..7.57 rows=443 width=0) (actual time=0.026..0.026 rows=0 loops=1) Index Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) Buffers: shared read=1 Total runtime: 0.320 ms (23 rows)
但是,如果我將列
coll
從更改character varying(15)
為character varying(255)
,然後再次執行這些步驟;CREATE TABLE test2 ( id serial PRIMARY KEY, coll character varying(255), ts timestamp without time zone );
解釋輸出是(數據庫中沒有任何行):
EXPLAIN (ANALYZE, BUFFERS) select * from test2 WHERE ts >= '2015-08-11' ORDER BY ts DESC; Sort (cost=42.47..43.18 rows=287 width=157) (actual time=0.028..0.028 rows=0 loops=1) Sort Key: public.test2.ts Sort Method: quicksort Memory: 17kB -> Result (cost=0.00..30.75 rows=287 width=157) (actual time=0.020..0.020 rows=0 loops=1) -> Append (cost=0.00..30.75 rows=287 width=157) (actual time=0.015..0.015 rows=0 loops=1) -> Seq Scan on test2 (cost=0.00..0.00 rows=1 width=157) (actual time=0.003..0.003 rows=0 loops=1) Filter: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) -> Seq Scan on test2_20150812 test2 (cost=0.00..15.38 rows=143 width=157) (actual time=0.002..0.002 rows=0 loops =1) Filter: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) -> Seq Scan on test2_20150811 test2 (cost=0.00..15.38 rows=143 width=157) (actual time=0.002..0.002 rows=0 loops =1) Filter: (ts >= '2015-08-11 00:00:00'::timestamp without time zone) Total runtime: 0.063 ms (12 rows)
在這種新情況下,有什麼方法可以在子表上使用索引?
所有這些都與繼承和分區無關。它一般是關於索引和查詢計劃的。
第二次嘗試的行大小要大得多:
width=157
與width=46
. Postgres將更容易為更寬的行使用索引。意外順序掃描的可能原因包括:
- 正如規劃師估計的那樣,您的表中第二次測試的行數要少得多:
rows=143
與rows=357
. 只查找幾行進行排序的索引是不值得的。- 或者統計數據已經過時導致規劃師估計錯誤(Postgres 只認為行數會更少)。
- 作為重寫表的副作用,索引大小可能已經膨脹。
REINDEX
或者VACUUM FULL
會修復它。在所有涉及的表上執行
ANALYZE
並重試 - 所有表中的行數相同。您應該再次看到點陣圖索引掃描。如果現象持續存在,請提供 的輸出EXPLAIN (ANALYZE, BUFFERS)
,而不僅僅是EXPLAIN
。問題更新後
只要您閱讀整個表格,索引的用途就有限。如果您使用匹配索引查詢單個表,以便可以從索引中讀取已排序的行,並且 Postgres 可以完全跳過排序步驟,您將看到索引掃描。
當必須組合多個表時,這是不可能的。這個SQL fiddle每個子節點有 10k 行,並且有效統計資訊按預期顯示點陣圖索引掃描。在重複查詢幾次之後(一旦整個表被記憶體),Postgres 可能會跳過索引並切換到順序掃描,這已經變得更便宜了。
Postgres 顯然不夠聰明,無法理解相互排除的檢查約束,這將允許從每個表中按原樣附加易於排序的結果。您可以通過手動指示來強制執行此操作:
(SELECT * FROM test2_20150812 ORDER BY ts DESC) UNION ALL (SELECT * FROM test2_20150811 ORDER BY ts DESC);
但是,Postgres 應該足夠聰明,可以使用Merge Append(組合預排序集的廉價方法)。在我對 PostgreSQL 9.4的本地測試中,我實際上看到了每個分區上的索引掃描,並結合了Merge Append。該計劃更好,但並不比順序掃描快多少,因為記住!只要您讀取整個表,索引的使用是有限的。
'QUERY PLAN' 'Merge Append (cost=0.73..16866.41 rows=200001 width=45)' ' Sort Key: test.ts' ' -> Index Scan Backward using test_ts_idx on test (cost=0.13..8.14 rows=1 width=528)' ' Index Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone)' ' -> Index Scan Backward using test_20150811_ts_idx on test_20150811 (cost=0.29..6594.01 rows=100000 width=45)' ' Index Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone)' ' -> Index Scan Backward using test_20150812_ts_idx on test_20150812 (cost=0.29..6594.29 rows=100000 width=45)' ' Index Cond: (ts >= '2015-08-11 00:00:00'::timestamp without time zone)'
我沒有得到與 Postgres 9.3 相同的計劃(在 sqlfiddle 上測試)。必須是 pg 9.3 的限制。(?)
但是由於您使用的是過時的 9.0 版本,因此您無法使用這些版本。
將結果限制為幾行時,您會得到更有趣的結果。
varchar(15)
或者varchar(255)
對查詢計劃影響很小。更廣泛的類型更傾向於索引。關於在 SQL Fiddle 上測試索引: