在使用資料庫的時候都會面對到 SQL 執行速度不如預期的問題,有時候可能單純的 SQL 語法卻要花上好幾秒才能完成查詢,下面是一般常見幾種可能影響查詢速度的因素,此處主要會針對前兩者進行討論。
1. 正確的建立 Index
2. 對 Table 進行 Analyze 更新統計值
3. SQL 語法 優化
在各個不同資料庫調教 SQL 的方式基本大同小異,而在 PGSQL 中使用到 Explain 語法來查看執行計畫,下面會透過觀察執行計畫來觀察不同 Index 狀況對執行計畫的影響。
1. 多欄位Index使用
建立多欄位 Index 時需要注意,欄位的順序對於 Index 的使用是有差的,假設此處建立了一個 Index 依序包含 a, b, c 欄位,但在執行查詢的時候下的條件判斷沒有使用到首欄位,例如 select * from b = 500 因為需要先掃過整個 a 欄位才能掃 b 欄位,執行計畫可能會判斷使用 Index 成本較高,因此偏好使用 Seq Scan,在正確使用上必須要單獨幫 b 欄位另外補一個 Index 才能加速查詢。
下面建立一張測試表並產生測試資料來供後續測試使用,並且為此測試表建立 Index 欄位順序為 a, b, c
create table test_index (a int, b int, c int); create index on test_index (a, b, c); insert into test_index select generate_series(1, 10000000), generate_series(1, 10000000), generate_series(1, 10000000);
下面分別以 a 與 b 為條件做篩選,可以觀察到用 a 欄位作條件判斷有正確使用到 Index 但是使用到 b 欄位則沒有使用 Index。
edb=# explain select * from test_index where a = 5000; QUERY PLAN --------------------------------------------------------------------------------------------- Index Only Scan using test_index_a_b_c_idx on test_index (cost=0.43..8.45 rows=1 width=12) Index Cond: (a = 5000) (2 rows) edb=# explain select * from test_index where b = 5000; QUERY PLAN --------------------------------------------------------------------------------- Gather (cost=1000.00..107139.34 rows=1 width=12) Workers Planned: 2 -> Parallel Seq Scan on test_index (cost=0.00..106139.24 rows=1 width=12) Filter: (b = 5000) JIT: Functions: 2 Options: Inlining false, Optimization false, Expressions true, Deforming true (7 rows) edb=#
edb=# explain select * from test_index where a > 500; QUERY PLAN --------------------------------------------------------------------------------- Seq Scan on test_index (cost=0.00..179057.19 rows=9999673 width=12) Filter: (a > 500) JIT: Functions: 2 Options: Inlining false, Optimization false, Expressions true, Deforming true (5 rows) edb=# explain select * from test_index where a < 500; QUERY PLAN ------------------------------------------------------------------ Index Only Scan using test_index_a_b_c_idx on test_index (cost=0.43..893.83 rows=501 width=12) Index Cond: (a < 500) (2 rows) edb=#
2. Analyze Table
在效能調教上統計值有著巨大的影響,如果執行計畫產生器對於資料分布的狀況不了解,無法正確判讀可能的查詢結果,有可能會造成應該使用 Index 的地方卻選擇了 Seq Scan 導致執行查詢結果較慢。
以下產生測試資料
create table test_a (id text , str text, primary key(id, str)); create table test_b (id text primary key, str text); insert into test_a select generate_series(1, 10000), 'test_a'; insert into test_b select generate_series(1, 10000), 'test_b'; update test_b SET str = 'test_c' where id = 5000;
下面針對 join SQL 進行執行計畫的測試,由於 where 條件判斷預期查詢結果應該只有一筆資料,期望可以透過 Index Scan 來進行快速的查詢,但實際上發現都使用 Seq Scan。
analyze select * from test_a left outer join test_b on test_a.id = test_b.id where test_b.str = 'test_c'; QUERY PLAN ---------------------------------------------------------------------------------------------- Hash Join (cost=115.80..231.92 rows=24 width=128) (actual time=6.963..9.652 rows=1 loops=1) Hash Cond: (test_a.id = test_b.id) -> Seq Scan on test_a (cost=0.00..103.40 rows=4840 width=64) (actual time=0.023..2.779 ro ws=10000 loops=1) -> Hash (cost=115.50..115.50 rows=24 width=64) (actual time=4.141..4.143 rows=1 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB -> Seq Scan on test_b (cost=0.00..115.50 rows=24 width=64) (actual time=4.135..4.13 6 rows=1 loops=1) Filter: (str = 'test_c'::text) Rows Removed by Filter: 9999 Planning Time: 0.131 ms Execution Time: 9.675 ms (10 rows)
edb=# analyze test_a; ANALYZE edb=# analyze test_b; ANALYZE edb=# explain analyze select * from test_a left outer join test_b on test_a.id = test_b.id where test_b.str = 'test_c'; QUERY PLAN ---------------------------------------------------------------------------------------------- ----------------------------- Nested Loop (cost=0.29..188.30 rows=1 width=22) (actual time=3.402..3.404 rows=1 loops=1) -> Seq Scan on test_b (cost=0.00..180.00 rows=1 width=11) (actual time=3.381..3.382 rows= 1 loops=1) Filter: (str = 'test_c'::text) Rows Removed by Filter: 9999 -> Index Scan using test_a_pkey on test_a (cost=0.29..8.30 rows=1 width=11) (actual time= 0.015..0.015 rows=1 loops=1) Index Cond: (id = test_b.id) Planning Time: 0.395 ms Execution Time: 3.425 ms (8 rows) Time: 4.279 ms edb=#
藉由上面的測試,我們可以觀察到了解到 Index 以及統計值 Analyze 對執行計畫的影響,在調教 SQL 的時候,除了調整 SQL 語法以外,別忘了優先檢查相關 Index 的使用以及收集統計值。