postgresql 子查詢_PostgreSQL子事務及效能分析
阿新 • • 發佈:2021-02-14
技術標籤:postgresql 子查詢
作者介紹 Laurenz Albe:Cybertec的高階顧問和支援工程師。自2006年以來,一直與PostgreSQL合作併為其做出貢獻。 譯者簡介 陳雁飛:開源PostgreSQL愛好者,一直從事PostgreSQL資料庫運維工作 最近,在排查PostgreSQL效能問題的時候,兩次遇到子事務相關問題。所以,我想這個話題非常適合作為部落格內容。什麼是子事務?
每個人都瞭解資料庫事務。在PostgreSQL中,事務是預設工作在自動提交模式下,多語句情況下,需要顯示呼叫BEGIN或者START TRANSACTION來開啟一個事務,最後使用END或者COMMIT結束它。如果用ROLLBACK中斷一個事務(或者資料庫會話結束的時候沒有執行提交操作),那麼在事務中的操作將成為沒有完成的。 現在子事務允許你回滾部分已經在事務中完成的工作。可以使用下面的標準語句在一個事務中開啟子事務: SAVEPOINT name; “name”表示一個子事務的識別符號(沒有單引號!)。不能在SQL中提交一個子事務(將和包含它的事務一起自動提交),但是可以使用下面的命令回滾: ROLLBACK TO SAVEPOINT name;子事務的使用
子事務在長事務中有非常大的作用。在PostgreSQL中,事務中任何一個錯誤都會中斷整個事務:
test=> BEGIN;
BEGIN
test=*> SELECT 'Some work is done';
?column?
-------------------
Some work is done
(1 row)
test=*> SELECT 12 / (factorial(0) - 1);
ERROR: division by zero
test=!> SELECT 'try to do more work';
ERROR: current transaction is aborted, commands ignored until end of transaction block
test=!> COMMIT;
ROLLBACK
對於一個做了很多工作的事務來說,這是非常煩人的,因為這意味著失去到目前為止完成的所有工作。子事務可以幫助我們從這種情況中進行恢復
test=> BEGIN;
BEGIN
test=*> SELECT 'Some work is done';
?column?
-------------------
Some work is done
(1 row)
test=*> SAVEPOINT a;
SAVEPOINT
test=*> SELECT 12 / (factorial(0) - 1);
ERROR: division by zero
test=!> ROLLBACK TO SAVEPOINT a;
ROLLBACK
test=*> SELECT 'try to do more work';
?column?
---------------------
try to do more work
(1 row)
test=*> COMMIT;
COMMIT
注意ROLLBACK TO SAVEPOINT回滾一箇舊事務a的時候,會重新開始一個新的子事務。
PL/pgSQL中子事務
即使你從來沒有使用過SAVEPOINT語句,但是你可能遇到過子事務。在PL/pgSQL中,上面的程式碼類似下面BEGINPERFORM 'Some work is done';BEGIN -- a block inside a blockPERFORM 12 / (factorial(0) - 1);EXCEPTIONWHEN division_by_zero THENNULL; -- ignore the errorEND;PERFORM 'try to do more work';END;
每次輸入帶有
EXCEPTION
子句的語句塊時,都會開啟一個新的子事務。當離開這個塊的時候會提交該子事務,進入異常處理分支的時候表示回滾。
資料庫之間相容性
其它資料庫處理事務中錯誤的方式不盡相同。不會中止完整的事務,而是僅僅回滾導致錯誤的語句,從而使事務本身處於活動狀態。 當從這樣的資料庫遷移或移植到PostgreSQL中時,你可能需要在子事務中包裝每個語句,以模擬上面的行為。 PostgreSQL JDBC驅動程式中有一個連線引數“autosave”,如果將其設定為“always”,就會在每條語句之前自動設定一個儲存點,方便在失敗的時候回滾。 如下所示,這種轉換技巧存在嚴重的效能瓶頸。效能測試用例
為了說明由於過度使用子事務導致效能問題,建立下面的測試用例表CREATE UNLOGGED TABLE contend (
id integer PRIMARY KEY,
val integer NOT NULL
)
WITH (fillfactor='50');
INSERT INTO contend (id, val)
SELECT i, 0
FROM generate_series(1, 10000) AS i;
VACUUM (ANALYZE) contend;
這個表資料量很少、不記錄日誌以及低的填充因子,這些都是為了儘可能降低I/O。這樣,可以更好地觀察子事務的影響。
我將使用pgbench(一個PostgreSQL附帶的基準測試工具)來執行下面的自定義SQL指令碼。
BEGIN;
PREPARE sel(integer) AS
SELECT count(*)
FROM contend
WHERE id BETWEEN $1 AND $1 + 100;
PREPARE upd(integer) AS
UPDATE contend SET val = val + 1
WHERE id IN ($1, $1 + 10, $1 + 20, $1 + 30);
SAVEPOINT a;
\set rnd random(1,990)
EXECUTE sel(10 * :rnd + :client_id + 1);
EXECUTE upd(10 * :rnd + :client_id);
SAVEPOINT a;
\set rnd random(1,990)
EXECUTE sel(10 * :rnd + :client_id + 1);
EXECUTE upd(10 * :rnd + :client_id);
...
SAVEPOINT a;
\set rnd random(1,990)
EXECUTE sel(10 * :rnd + :client_id + 1);
EXECUTE upd(10 * :rnd + :client_id);
DEALLOCATE ALL;
COMMIT;
第一組測試用例將設定60個子事務,第二組測試用例將設定90個子事務。通過使用預備語句方式儘可能減少查詢解析的影響。
在每個資料庫會話中,pgbench將:client_id替換成一個唯一的數字。所以只要沒有不超過10個客戶端,每個客戶端的更新操作不會產生衝突,但是會查詢其他客戶端產生的資料行。
效能測試
由於機器只有8核,因此在測試中將使用6個併發客戶端執行十分鐘。 為了讓“perf top”能檢視到重要的資訊,需要安裝PostgreSQL除錯符號資訊。這在生產系統上也是推薦的。 TEST 1(60個子事務)pgbench -f subtrans.sql -n -c 6 -T 600
transaction type: subtrans.sql
scaling factor: 1
query mode: simple
number of clients: 6
number of threads: 1
duration: 600 s
number of transactions actually processed: 100434
latency average = 35.846 ms
tps = 167.382164 (including connections establishing)
tps = 167.383187 (excluding connections establishing)
下面是在測試執行中,使用“perf top --no-children --call-graph=fp --dsos=/usr/pgsql-12/bin/postgres”命令展示的資訊
+ 1.86% [.] tbm_iterate
+ 1.77% [.] hash_search_with_hash_value
1.75% [.] AllocSetAlloc
+ 1.36% [.] pg_qsort
+ 1.12% [.] base_yyparse
+ 1.10% [.] TransactionIdIsCurrentTransactionId
+ 0.96% [.] heap_hot_search_buffer
+ 0.96% [.] LWLockAttemptLock
+ 0.85% [.] HeapTupleSatisfiesVisibility
+ 0.82% [.] heap_page_prune
+ 0.81% [.] ExecInterpExpr
+ 0.80% [.] SearchCatCache1
+ 0.79% [.] BitmapHeapNext
+ 0.64% [.] LWLockRelease
+ 0.62% [.] MemoryContextAllocZeroAligned
+ 0.55% [.]_bt_checkkeys
0.54% [.] hash_any
+ 0.52% [.] _bt_compare
0.51% [.] ExecScan
Test2(90個子事務)
pgbench -f subtrans.sql -n -c 6 -T 600
transaction type: subtrans.sql
scaling factor: 1
query mode: simple
number of clients: 6
number of threads: 1
duration: 600 s
number of transactions actually processed: 41400
latency average = 86.965 ms
tps = 68.993634 (including connections establishing)
tps = 68.993993 (excluding connections establishing)
下面是命令“perf top --no-children --call-graph=fp --dsos=/usr/pgsql-12/bin/postgres”得到的內容
+ 10.59% [.] LWLockAttemptLock
+ 7.12% [.] LWLockRelease
+ 2.70% [.] LWLockAcquire
+ 2.40% [.] SimpleLruReadPage_ReadOnly
+ 1.30% [.] TransactionIdIsCurrentTransactionId
+ 1.26% [.] tbm_iterate
+ 1.22% [.] hash_search_with_hash_value
+ 1.08% [.] AllocSetAlloc
+ 0.77% [.] heap_hot_search_buffer
+ 0.72% [.] pg_qsort
+ 0.72% [.] base_yyparse
+ 0.66% [.] SubTransGetParent
+ 0.62% [.] HeapTupleSatisfiesVisibility
+ 0.54% [.] ExecInterpExpr
+ 0.51% [.] SearchCatCache1
即使考慮到test2都是長事務,與test1相比,仍然有60%效能差距。
子事務實現
要了解發生什麼,我們需要了解事務和子事務實現方式。 當一個事務或者子事務中修改了資料後,會為該事務分配一個事務ID(transaction ID)。PostgreSQL在提交日誌(commit log)中跟蹤這些事務ID資訊,日誌資訊持久化儲存在資料目錄下pg_xact子目錄中。 但是,事務和子事務之間有下面幾點差異: l.每個子事務包含一個事務或者子事務(“父親”) l.提交子事務不會重新整理WAL l.一個數據庫會話中有且只能有一個事務,但是可以有多個子事務 儲存給定子事務的父資訊相關的(子)事務資訊持久化儲存在資料目錄下的pg_subtrans子目錄。由於這些資訊隨著包含事務結束後立即變成過去時,因此不必在關閉或者崩潰期間保留這些資料。子事務和可見性
PostgreSQL中行級版本(元組)可見性由xmin和xmax系統列決定的,分別表示建立和刪除事務的事務ID。如果儲存的事務ID是子事務資訊,那麼PostgreSQL還必須查詢包含(子)事務的狀態,以確定對該事務ID是否可見。 為了確定語句可以看到哪些元組,PostgreSQL在語句(或事務)開始的地方首先獲取資料庫的快照資訊。快照主要包含如下資訊: l.最大事務ID:任何超過該事務ID都是不可見的 l.獲取快照的時候處於活躍狀態的事務和子事務 l.當前(子)事務中可見的最早命令號(commnad number) 快照通過查詢程序陣列(process array)資訊來進行初始化,程序陣列儲存在共享記憶體中幷包含有當前執行程序的相關資訊。當前,它也包含後端程序的當前事務ID,並且每個會話最多可以容納64個未中止的子事務。如果有超過64個這樣的子事務,那麼快照被標記為子事務溢位(suboverflowed)。結果分析
一個子溢位的快照不會包含檢測可見性的所有資料資訊,所以PostgreSQL有時將不得不求助於pg_subtrans。這些頁快取在共享記憶體中,但是在perf中可以看到SimpleLruReadPage_ReadOnly函式排在前面輸出。其它事務必須更新pg_subtrans後才能註冊子事務,可以在perf輸出中看到如何與讀程序爭奪輕量級鎖。分析子事務太多問題
除了檢視”perf top”,還有其它指向該問題方向的可疑點:
l.執行單個程序的時候負載表現很好,但是併發多個數據庫會話後會變高 l.在pg_stat_activity檢視中經常看到等待實踐“SubtransControlLock” l.如果使用“pg_export_snapshot()”函式匯出快照資訊,資料目錄下的pg_snapshots子目錄儲存的結果檔案中包含有“sof:1”資訊,其表示子事務陣列溢位結論
子事務是一個很好的工具,但是需要合理使用它。如果需要併發,每個事務不要啟動超過64個子事務。
本文中提供的分析方法應該可以幫助你確定是否存在類似問題。 找到問題的根因可能很棘手。例如:對於SQL語句的每個結果行(可能在觸發器中)呼叫的帶有異常處理程式的函式,啟動新的子事務不會那麼明顯。 原文地址 PostgreSQL中文社群歡迎廣大技術人員投稿 投稿郵箱:[email protected]