通常のCREATE INDEXが取得するロック

通常のCREATE INDEXは対象テーブルにShareLockを取得する。ShareLockINSERTUPDATEDELETEが取得するRowExclusiveLockと競合するため、インデックス作成が完了するまで書き込みがブロックされる。

BEGIN;
CREATE INDEX idx_accounts_balance ON accounts (balance);
SELECT locktype, relation::regclass, mode, granted
FROM pg_locks
WHERE relation = 'accounts'::regclass;
ROLLBACK;
 locktype | relation |   mode    | granted 
----------+----------+-----------+---------
 relation | accounts | ShareLock | t

数百万行あるテーブルに対して実行すると、インデックス作成に数十秒から数分かかることがあり、その間書き込みが止まってしまう。実際にShareLockを保持した状態で別セッションからINSERTを実行すると、ロックが解放されるまで待たされる。

-- セッションA
BEGIN;
LOCK TABLE accounts IN SHARE MODE;

-- セッションB
INSERT INTO accounts (name, balance) VALUES ('user', 1);
-- セッションAのロックが解放されるまで待たされる

CONCURRENTLYで取得するロック

CREATE INDEXCONCURRENTLYを付けると、取得するロックがShareUpdateExclusiveLockに変わる。ShareUpdateExclusiveLockRowExclusiveLockと競合しないため、インデックス作成中もINSERTUPDATEDELETEをブロックしない。

CREATE INDEX CONCURRENTLY idx_accounts_balance ON accounts (balance);

ShareUpdateExclusiveLockを保持した状態で別セッションからINSERTを実行しても、待たされずに完了する。

-- セッションA
BEGIN;
LOCK TABLE accounts IN SHARE UPDATE EXCLUSIVE MODE;

-- セッションB
INSERT INTO accounts (name, balance) VALUES ('user', 1);
-- 待たされずに完了する

書き込みをブロックしない代わりに、CONCURRENTLYはテーブルを2回スキャンするため、通常のCREATE INDEXよりも時間がかかる。

CONCURRENTLYの制約

CREATE INDEX CONCURRENTLYはトランザクションブロック内では実行できない。

BEGIN;
CREATE INDEX CONCURRENTLY idx_accounts_balance ON accounts (balance);
ROLLBACK;
ERROR:  CREATE INDEX CONCURRENTLY cannot run inside a transaction block

失敗時に残る無効なインデックス

CREATE INDEX CONCURRENTLYの実行中に接続が切れたりキャンセルされたりすると、作成途中のインデックスが無効な状態のまま残る。pg_indexindisvalid列で有効性を確認できる。

SELECT indexrelid::regclass, indisvalid FROM pg_index WHERE indrelid = 'accounts'::regclass;
      indexrelid      | indisvalid 
----------------------+------------
 accounts_pkey        | t
 idx_accounts_balance | t
 idx_accounts_name    | f

indisvalidfのインデックスは検索には使われず、プランナーからも無視される。このまま放置せず、DROP INDEX CONCURRENTLYで削除してから作り直す。

DROP INDEX CONCURRENTLY idx_accounts_name;

DROP INDEXCONCURRENTLYを付けることで、通常のDROP INDEXが取得するAccessExclusiveLockを避け、書き込みをブロックせずに削除できる。