PostgreSQLスキルアップノート(自己啓発のための個人サイト)

データベースページ・ページに空きがない場合のUPDATE


【一覧に戻る】
マニュアルへのリンクは/9.2/としています。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■■■ PostgreSQL スキルアップノート
■■■■
■◆■■ データベースページ・ページに空きがない場合のUPDATE
■■■■
■■■■
■■■■ 使用環境:PostgreSQL9.2.3 (CentOS6.2) 
         2013/03/20 
                                                                   (C) 2013 ohdb
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ページに空きのない場合のUPDATEでは別のページに行が追記される。
その際のラインポインタの状態や、後続のSQL文が負わされてしまう処理をpageinspectを使って確認した。



【マニュアル】

付録 F. 追加で提供されるモジュール・pageinspect→●[マニュアル]


【マニュアル参考】
  −

【その他】
  −




■1■ autovacuum停止
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

今回の確認に支障がでないように、autovacuumを止める。


【□】 pg_ctl restart -o '-c autovacuum=off' 




■2■ テーブルの作成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ページの空きのないFILLFACTOR=100のテーブルを作成する。
テーブルのFILLFACTORデフォルトは100なので(9.2現在)指定は必須ではない。


【□】 テーブル作成し約100万件のデータを挿入  (1000001件)

┌────────────────────────────┐

DROP TABLE t1;
CREATE TABLE t1 (
    c1 int,
    c2 text,
    c3 text,
    c4 text,
    c5 text
) WITH (FILLFACTOR=100); 
\timing on
INSERT INTO t1 SELECT *,*,*,*,* FROM generate_series(1000000,2000000);
VACUUM ANALYZE t1;
\pset pager off
SELECT * FROM t1 LIMIT 20;

└────────────────────────────┘




■3■ UPDATE実行
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


1.UPDATE実行
──────────────────────────────

【□】 UPDATE t1 SET c4='AAAAAAA' WHERE mod(c1,10)=0;


2.ページの状態出力
──────────────────────────────

 以下によりページの状態を出力。 blkno=0の1ブロック分のみを確認。
 以下スクリプトの詳細は →●[サイト内]


【□】bashコマンドラインより一度にコピー&ペーストする

# ==ここから====
DBNAME=database1                # 対象のデータベース名を入力。
TBL=t1                          # 対象のテーブル名を入力。
BLKNO=0                         # 対象のブロック番号を入力。
FORK=main                       # main,fsm,vmのいずれか。
(
echo "database= ${DBNAME} table= ${TBL} fork= ${FORK} blk= ${BLKNO}   "`date "+%Y-%m-%d %H:%M:%S"`
echo ""
psql "$DBNAME" -P pager=off -c "SELECT * from heap_page_items(get_raw_page('$TBL','$FORK',$BLKNO))";
psql "$DBNAME" -P pager=off -t -A -c "SELECT get_raw_page('$TBL','$FORK',$BLKNO)"\
|awk '{skp=2;print substr($0,1,skp);i=skp+1;print "   OFFSET";\
       while (lin<=127) {printf("%4d-%4d %-128s\n",lin*64,lin*64+63,substr($0,i,128));lin++;i=i+128}}'\
)\
|tee page_${DBNAME}_${TBL}_${FORK}_${BLKNO}_`date "+%Y%m%d_%H%M%S"`.txt
# ==ここまで====



3.出力結果の確認
──────────────────────────────

実際の出力結果は、■資料1■を参照。→●[サイト内]

前半はheap_page_itemsにより得られたlpの一覧。
後半はget_raw_pageにより得られた16進のダンプ。

確認は主に前半部分の内容が中心。後半の16進のダンプの方は参考程度に記載。
ちなみに前半の「lp_off」の値から16進ダンプの対応する位置を確認できる。


資料1よりわかること。

・このページ内にあるlp(ラインポインタ)は計120個である。

・うちt_max(削除XID)に値のあるものは上記の更新で削除された行のlpである。
  lp=1,11,21,31,41,51,61,71,81,91,101,111 の計12個

・ページ内に空きがなかったのでUPDATEによる追記行はすべて別のページ(8333番)のブロックに追記された。
  (t_ctid の欄を参照)  

   (参考) なお、シェル変数に「BLKNO=8333」をセットして前述のスクリプトを実行す
          れば、追記された側のブロックも確認することができる。




■4■ UPDATE後にSELECT実行 (後続のSQL文への影響)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

UPDATE後、vacuumがかかる間もなく後続のSQLがそのブロックアクセスした場合、大きな
負担を負わされる。



1.SELECT実行
──────────────────────────────


【□】 EXPLAIN (ANALYZE,VERBOSE,BUFFERS) SELECT * from t1 WHERE c1=0;


  1回目

  database1=# EXPLAIN (ANALYZE,VERBOSE,BUFFERS) SELECT * from t1 WHERE c1=0;
                                                  QUERY PLAN
  --------------------------------------------------------------------------------------------------------------
   Seq Scan on public.t1  (cost=0.00..22916.41 rows=1 width=36) (actual time=1320.947..1320.947 rows=0 loops=1)
     Output: c1, c2, c3, c4, c5
     Filter: (t1.c1 = 0)
     Rows Removed by Filter: 1000001
     Buffers: shared hit=218 read=8949 written=3509  ★ writtenが発生
   Total runtime: 1320.999 ms ★

  2回目

  database1=# EXPLAIN (ANALYZE,VERBOSE,BUFFERS) SELECT * from t1 WHERE c1=0;
                                                 QUERY PLAN
  ------------------------------------------------------------------------------------------------------------
   Seq Scan on public.t1  (cost=0.00..22916.41 rows=1 width=36) (actual time=112.640..112.640 rows=0 loops=1)
     Output: c1, c2, c3, c4, c5
     Filter: (t1.c1 = 0)
     Rows Removed by Filter: 1000001
     Buffers: shared hit=4035 read=5132               ★ writtenは発生しない
   Total runtime: 112.668 ms  ★


以上のように1回目のSELECTの中でwrittenが発生している。
どんな書き込みが発生したのかを以降で確認する。



2.ページの状態出力
──────────────────────────────

【□】 前項の「2.ページの状態出力」と同じ方法でページ状態を出力する。



3.出力結果の確認
──────────────────────────────

実際の出力結果は、■資料2■を参照。→●[サイト内]




資料2よりわかること。

UPDATEの後、同じブロックへアクセスするSELECT文は以下のような書き込みを強いられた。

・lp_flagsを「3」(LP_DEAD)に変更。
・そのラインポインタの内容の大半をクリア。
 
  (lp=1,11,21,31,41,51,61,71,81,91,101,111の計12個のlp)





■5■ まとめ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ページ内に空きがない場合はUPDATE時の追記行は別ブロックの中に書き込まれる。
その後、同じブロックへアクセスする後続の別のSQL文(今回はSELECT)によって、
lp_flagsを3(LP_DEAD)へ変更する。


(参考比較)ページ内に空きがある場合

UPDATE時の追記行は同じブロックの中に書き込まれる。
その後の誰か別のSQL文によって、lp_flagsを2(LP_REDIRECT)へ変更する。
→●[サイト内]




ただしVACUUMが先に実行されればUPDATEの後始末を強いられることはない。


以上 
inserted by FC2 system