PostgreSQLスキルアップノート(自己啓発のための個人サイト)
行レベルロック・概要&検証
【一覧に戻る】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■■■ PostgreSQL スキルアップノート
■■■■
■◆■■ 行レベルロック・概要&検証
■■■■
■■■■
■■■■ 2013/03/03
■■■■ 使用環境:PostgreSQL9.1.7 (CentOS6.2)
(C) 2013 ohdb
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
行レベルのロックと影響を実際に確認する。
【マニュアル】
第 13章同時実行制御・明示的ロック・行レベルのロック→●[マニュアル]
SELECT文(FOR UPDATE/FOR SHARE句)→●[マニュアル]
第45章システムカタログ・pg_locks→●[マニュアル]
【その他】
−
■1■ 概要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
行レベルロックはテーブルレベルロックと比べて単純でわかりやすい。
・行レベルロックは参照に影響を与えることは一切ない。同じ行の更新削除だけを阻害する。
・種類は排他ロックと共有ロックのみ。
・ロックの保持期間はコミットまたはロールバックされるまで。
行の排他ロック
・更新・削除を行うと自動的に行が排他ロックされる。
・SELECT FOR UPDATEにより行レベルロック(排他)だけを獲得することができる。
・一旦ロックを獲得すれば自らでトランザクションを終えるまでは他から更新削除される心配がない。
(※1補足参照)
行の共有ロック
・SELECT FOR SHAREにより行う。
・獲得すると他の誰からも更新、削除、排他ロックの獲得はできない。
・ただ、複数の人が同時に同じ行に共有ロックをかけられる。
【補足】※1
マニュアルに更新削除される心配がないと記載されているのは、自分の更新が必ず
成功できるという意味ではない。
たとえばデッドロックによりエラーとなる場合もあれば、FOR UPDATEした後で
テーブルにSHAREロックをかけられてしまうことも起こる。(この検証別ページ)
■2■ 検証準備・実験用テーブル作成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【□】 実験用テーブル作成
drop table t1;
create table t1 (c1 int,c2 text);
insert into t1 values(1,'aa'),(2,'bb'),(3,'cc');
select * from t1;
c1 | c2
----+----
1 | aa
2 | bb
3 | cc
■3■ 検証準備・ロック状態確認用スクリプト
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ロック状態はpg_locksを参照して確認を行う。 (SELECT * FROM pg_locks;)
ここでは簡単に呼び出せるようにPREPAREを定義。
なお、PREPAREはそのセッション限り有効なのでそのターミナルを確認専用として切らずに使うこと。
このスクリプトは前章のテーブルレベルロックの確認用スクリプトから変更しています。
テーブルレベルと行レベル双方のロックを確認することができます。
【term-0 □】 確認用SQLを準備
DEALLOCATE show_pglock;
PREPARE show_pglock AS
SELECT l.pid,l.granted,d.datname,l.locktype,relation,relation::regclass,transactionid,l.mode
FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid
WHERE l.pid != pg_backend_pid()
ORDER BY l.pid;
---------------------------------------------------------------------
l.pid バックエンドプロセスのpid
l.granted ロックが待たされている場合 f
d.datname DB名
l.locktype ロック対象。今回はテーブルレベル'relation'のみ抽出
relation テーブル等oid
relation::regclass テーブル等名称
(pg_classへ結合の代わりにregclassキャストで名前を取得)
transactionid トランザクションID
l.mode ロックモード
l.pid != pg_backend_pid() このSQL自身のロックは結果から除外
---------------------------------------------------------------------
■4■ 検証1・同じテーブルの同一行に2多重でUPDATE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
同じ行を更新(UPDATE)した場合には、テーブルレベルロックは成功するが行ロックが原因で
阻まれることになる。
【term-1 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1;
【term-2 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1; ・・待ちが発生
【term-0 □】 EXECUTE show_pglock;
pid | granted | datname | locktype | relation | relation | transactionid | mode
-------+---------+-----------+---------------+----------+----------+---------------+------------------
16643 | t | | transactionid | | | ★ 19567 | ExclusiveLock
16643 | t | | virtualxid | | | | ExclusiveLock
16643 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
-------+---------+-----------+---------------+----------+----------+---------------+------------------
16655 | t | database1 | tuple | 19613 | t1 | | ExclusiveLock
16655 | t | | transactionid | | | 19568 | ExclusiveLock
16655 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
16655 | t | | virtualxid | | | | ExclusiveLock
16655 | f★ | | transactionid | | | ★ 19567 | ShareLock
(分かりやすいようにpidごとに横線を加えてあります)
以上よりわかることは、次項でまとめて記載。
■5■ 検証2・同じテーブルの同一行に4多重でUPDATE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4トランザクションの同一行更新なので3トランザクションで待ちが発生する。
【term-1 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1;
【term-2 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1; ・・待ちが発生
【term-3 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1; ・・待ちが発生
【term-4 □】 BEGIN; UPDATE t1 set c2='aaaaa' WHERE c1=1; ・・待ちが発生
【term-0 □】 EXECUTE show_pglock;
pid | granted | datname | locktype | relation | relation | transactionid | mode
-------+---------+-----------+---------------+----------+----------+---------------+------------------
term-1
16643 | t | | virtualxid | | | | ExclusiveLock
16643 | t | | transactionid | | | ● 19567 | ExclusiveLock
16643 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
-------+---------+-----------+---------------+----------+----------+---------------+------------------
term-2
16655 | f ★ | | transactionid | | | ● 19567 | ShareLock
16655 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
16655 | t | | transactionid | | | 19568 | ExclusiveLock
16655 | t | | virtualxid | | | | ExclusiveLock
16655 | t | database1 | tuple | 19613 | t1 | | ExclusiveLock
-------+---------+-----------+---------------+----------+----------+---------------+------------------
term-3
17007 | t | | transactionid | | | 19574 | ExclusiveLock
17007 | f ★ | database1 | tuple | 19613 | t1 | | ExclusiveLock
17007 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
17007 | t | | virtualxid | | | | ExclusiveLock
-------+---------+-----------+---------------+----------+----------+---------------+------------------
term-4
17045 | f ★ | database1 | tuple | 19613 | t1 | | ExclusiveLock
17045 | t | | virtualxid | | | | ExclusiveLock
17045 | t | | transactionid | | | 19575 | ExclusiveLock
17045 | t | database1 | relation | 19613 | t1 | | RowExclusiveLock
(分かりやすいようにpidごとに横線を加えてあります)
以上よりわかることは、次項でまとめて記載。
■6■ 前項の結果の見方
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
マニュアルに説明がない部分については、一部「Let's Postgres」の記事を参考にした。
Let's Postgres→●[記事]
上記記事の中に以下のような解説がある。(内容を要約)
----------------------------------------------------------------------------------
locktypeの主なもの
・relation テーブルレベルのロック
・transactionid や tuple 行レベルのロック
・extend INSERTなどによる特定テーブル拡張で競合
locktype = transactionid or tupleの場合
・ある行への最初のロックはpg_locksには表れない。
・次にロックを取りにきたトランザクションはlocktype=「transactionid」がロック待ち。
・その次にロックを取りに来たトランザクションは locktype=「tuple」がロック待ち。
----------------------------------------------------------------------------------
以上を踏まえて前項(■5■)の結果を確認すると以下のようになる。
・4つのトランザクションがあるので、pidは4種類現れている。
・「granted=f」のもの(左側の3つの★印)がロック待ちである。
・最初のトランザクションpid:16643 (term-1から実行した分)
・ロック待ちをしていない。
・ロック対象オブジェクト(locktype)にtoupleが出てこない。
これは行ロックがかかっていないということではなく、上の記事をみる限りでは
「ある行への最初のロックはpg_locksには表れない」という表示上の都合になる。
・2番目のトランザクションpid:16555 (term-2から実行した分)
・ロック待ちがある。 (★印)
・ただtoupleへのロックは成功している(ことになっている)。
待ちが発生しているのは最初のトランザクションと同じtransactionid:19567
に対してShareLockを獲得しようとしている部分である。
・3番目のトランザクションpid:17007(term-3から実行した分)
・ロック待ちがある。 (★印)
・ロック待ちはtoupleへのExclusiveLockを試みて発生している。
・4番目のトランザクションpid:17045 (term-4から実行した分)
・ロック待ちがある。 (★印)
・ロック待ちはtoupleへのExclusiveLockを試みて発生している。
以上、Let'sPostgresの記事の中の 「locktype = transactionid or tupleの場合」
の記述の通りとなった。
3番目と4番目の結果は分かりやすいが、最初と2番目のトランザクションの
結果はあらかじめ知識がないと、普通のロック待ちなのに何か別の原因であるかのような
印象をもってしまう。
【補足】
・tuple | ExclusiveLock
・・・・これは誰がみても行への排他ロックである。
繰り返しになるが、3番目と4番目はここでgranted=fになっており、
UPDATEによる行の排他ロックを獲得しようとして待ちになっているという
ことでわかりやすい。
・transactionid | ShareLock
・・・・2番目のトランザクションではこれで待ちになっている。
2番目はそうなりますよと記事にはかかれているが、やはりUPDATEなら行の排他ロックが待た
されるはずで、何かしらよくわからないと思うのはこのあたりが大きい。
・relation | RowExclusiveLock
・・・・これは「テーブルレベルロック」である。
Locktypeがrelationになっていることからもわかる。
「行排他ロック」そのものではなく「行排他ロックの際のテーブルレベルロック」
行ロックが多くてテーブルロックにエスカレーションするという意味でもない。
行排他ロックの際には必ずこのテーブルレベルロックが発生する。
マニュアルでも「Rowとあるからといって誤解しないように」と記載がある。
行ロックの検証なのにRowExclusiveLockのところは今回全く関係がない。
■7■ 参考・その他の確認手段と表示例
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1.pg_stat_activity
──────────────────────────────
pg_stat_activityでwaiting=tとなっている状況が確認できる。
【□】
SELECT
procpid,datname,usename,application_name as appli,
to_char(backend_start,'YYYY/MM/DD HH24:MI'),
to_char(xact_start,'YYYY/MM/DD HH24:MI'),
to_char(query_start,'YYYY/MM/DD HH24:MI'),
waiting,
current_query
FROM pg_stat_activity
WHERE procpid != pg_backend_pid();
procpid | datname | usename | appli | to_char | to_char | to_char | waiting | current_query
---------+-----------+----------+--------+------------------+------------------+------------------+---------+--------------------------------------
16643 | database1 | dba01 | psql | 2013/03/03 22:15 | 2013/03/03 22:43 | 2013/03/03 22:43 | f | in transaction
17007 | database1 | dba01 | psql | 2013/03/03 22:56 | 2013/03/03 22:56 | 2013/03/03 22:56 | t ★ | UPDATE t1 set c2='aaaaa' WHERE c1=1;
16655 | database1 | dba01 | psql | 2013/03/03 22:15 | 2013/03/03 22:43 | 2013/03/03 22:43 | t ★ | UPDATE t1 set c2='aaaaa' WHERE c1=1;
17045 | database1 | dba01 | psql | 2013/03/03 22:58 | 2013/03/03 22:58 | 2013/03/03 22:58 | t ★ | UPDATE t1 set c2='aaaaa' WHERE c1=1;
2.psコマンド
──────────────────────────────
psコマンドでもpostgreSQLバックエンドで何が行われどんな状態かがおおよそ把握できる。
以下の例ではUPDATE文が waitingになっていることが確認できる。
手軽なpsコマンドを一次切り分けに活用するのがよいと思う。
【□】 ps axwfo user,pid,ppid,tty,command|egrep "^postgres|^USER"
USER PID PPID TT COMMAND
postgres 8141 8137 ? | \_ sshd: postgres@pts/0
postgres 8142 8141 pts/0 | \_ -bash
postgres 13793 8142 pts/0 | \_ psql database1 dba01
postgres 10930 10926 ? | \_ sshd: postgres@pts/3
postgres 10931 10930 pts/3 | \_ -bash
postgres 16642 10931 pts/3 | \_ psql database1 dba01
postgres 16616 16613 ? | \_ sshd: postgres@pts/1
postgres 16618 16616 pts/1 | \_ -bash
postgres 16654 16618 pts/1 | \_ psql database1 dba01
postgres 16981 16977 ? | \_ sshd: postgres@pts/4
postgres 16982 16981 pts/4 | \_ -bash
postgres 17006 16982 pts/4 | \_ psql database1 dba01
postgres 17019 17016 ? | \_ sshd: postgres@pts/5
postgres 17020 17019 pts/5 | \_ -bash
postgres 17044 17020 pts/5 | \_ psql database1 dba01
postgres 17127 17123 ? \_ sshd: postgres@pts/6
postgres 17128 17127 pts/6 \_ -bash
postgres 17170 17128 pts/6 \_ ps axwfo user,pid,ppid,tty,command
postgres 17171 17128 pts/6 \_ -bash
postgres 16818 1 ? /usr/pgsql-9.1/bin/postgres
postgres 16820 16818 ? \_ postgres: logger process
postgres 16822 16818 ? \_ postgres: writer process
postgres 16823 16818 ? \_ postgres: wal writer process
postgres 16824 16818 ? \_ postgres: autovacuum launcher process
postgres 16825 16818 ? \_ postgres: stats collector process
postgres 13794 16818 ? \_ postgres: dba01 database1 [local] idle
postgres 16643 16818 ? \_ postgres: dba01 database1 [local] idle in transaction
postgres 16655★ 16818 ? \_ postgres: dba01 database1 [local] UPDATE waiting★
postgres 17007★ 16818 ? \_ postgres: dba01 database1 [local] UPDATE waiting★
postgres 17045★ 16818 ? \_ postgres: dba01 database1 [local] UPDATE waiting★
postgres 17066 16818 ? \_ postgres: postgres postgres [local] idle
■■ その他参考
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
・強制的にセッションを終了させることで対応する方法は以下の通り。
SELECT pg_cancel_backend(プロセスID);
システム管理関数-サーバシグナル送信関数→●[マニュアル]
以上