create index的語法中,看到一個關鍵字concurrently。以下是對concurrently的解釋:
當使用這個選項時,PostgreSQL在構建索引時不加任何阻止對表的並發插入、更新或刪除的鎖;而標准的索引構建會鎖定表上的寫操作(而不是讀操作),直到完成。 在使用此選項時,有幾個注意事項—請參閱building indexes concurrently。(這句話很關鍵,自己當初看的時候並沒有特別留意!) 對於臨時表,CREATE INDEX始終是非並發的,因為其他會話不能訪問它們,而且非並發索引創建代價更低。
看完上面的解釋的時候,感覺這個功能很好用。直到遇到對一張表執行create index concurrently ... 被另外的表操作阻塞的時候,才感覺這個功能有點雞肋。
類似下面的場景,在對表b執行concurrently創建索引的時候,會話3被會話2阻塞了。
Sessio1: Begin; Lock table a; Session2: Begin; Insert into a values(xxx); Session3: Create index concurrently b_idx on b(t);
繼續翻看文檔
創建索引會影響數據庫的常規操作。通常,pg會對要創建索引的表加上鎖,防止對表的寫操作,並且通過對表的一次掃描完成索引的構建。其他事務可以繼續對表執行select操作,但是如果嘗試insert、update、或delete操作會被阻塞,直到索引創建完成。這對生產庫可能會有很大的影響。對大表創建索引,可能需要幾個小時;即使對小表,短時間鎖住可能也是生產環境不能接受的。
pg支持創建索引的時候不阻塞寫操作。通過在create index的時候指定關鍵字concurrently來實現完成。使用該選項后,pg必須對表執行兩次表掃描,此外它必須等待已經存在的、潛在的可能修改或使用索引的事務結束(這里就是坑的所在,pg並不能准確的判斷出哪些已存在事務可能會修改該表。比如上面的session 2,並不會涉及到表b) 。因此這種方式需要完成更多的工作、花費更多的時間,相比標准的索引創建。
並發構建索引,先在一個事務中將索引加入到system catalog中,然后在兩個或者更多的事務中對表執行兩次掃描。在每次表掃描之前,創建索引的會話必須等待已存在的對表執行修改的事務完成結束。在第二次掃描之后,索引創建必須等待在第二次掃描開始之前已存在數據快照的任意事務結束(注意:這里是任意事務,而不是涉及到該表的事務,這也是坑的所在)。最后,索引才被標記為可用,create index命令結束。即使到這一步,創建的索引可能還不能立即被查詢使用;最差的情況是,直到在創建索引前的事務都結束才能被使用。
如果在掃描表的時候,遇到了問題,比如死鎖、唯一性沖突,create index會發生失敗,但是會遺留下一個無效的索引。這個無效索引不會被查詢使用,因為是不完整的;但是它仍然有被更新的開銷。\d命令會顯示該索引的無效:
postgres=# \d tab
Table "public.tab"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
col | integer | | |
Indexes:
"idx" btree (col) INVALID
這種場景,推薦的恢復方法是刪除后重新執行create index concurrently。(或者使用reindex重建,不過reindex不支持並發創建索引)
另一個注意事項就是,在並發創建唯一性索引時,唯一性約束已經在第二次掃描開始前強制執行了。這就意味着在索引被其他查詢使用時可能會報唯一性錯誤、甚至創建索引失敗。如果在第二次掃描時發生失敗,無效索引仍然會進行唯一性檢查。
並發創建表達式索引和部分索引是可以的。不支持在分區表上執行並發創建索引。你可以在每個分區上單獨執行並發索引創建,然后在常規地創建分區索引,這樣可以減少對分區表的鎖定時間,因為在這種場景下,創建分區索引只是元數據操作。
常規索引創建支持在相同的表上同時創建其他索引,但是同一時刻,在一張表上只能執行一個並發索引創建。另一個不同點是,常規對的create index可以在一個事務塊中執行,但是create index concurrently是不行的。
具體創建過程,可以閱讀這篇文章:並行創建索引探析 - Create Index Concurrently
