1. 程式人生 > 其它 >postgresql 子查詢_PostgreSQL子事務及效能分析

postgresql 子查詢_PostgreSQL子事務及效能分析

技術標籤: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;BEGINtest=*> SELECT 'Some work is done';?column? -------------------Some work is done(1 row)test=*> SELECT 12 / (factorial(0) - 1);ERROR: division by zerotest=!> SELECT 'try to do more work';ERROR: current transaction is aborted, commands ignored until end of transaction block
test=!> COMMIT;ROLLBACK
對於一個做了很多工作的事務來說,這是非常煩人的,因為這意味著失去到目前為止完成的所有工作。子事務可以幫助我們從這種情況中進行恢復
test=> BEGIN;BEGINtest=*> SELECT 'Some work is done';?column? -------------------Some work is done(1 row)test=*> SAVEPOINT a;SAVEPOINTtest=*> SELECT 12 / (factorial(0) - 1);ERROR: division by zerotest=!> ROLLBACK TO SAVEPOINT a;
ROLLBACKtest=*> 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, 0FROM generate_series(1, 10000) AS i;VACUUM (ANALYZE) contend;
這個表資料量很少、不記錄日誌以及低的填充因子,這些都是為了儘可能降低I/O。這樣,可以更好地觀察子事務的影響。 我將使用pgbench(一個PostgreSQL附帶的基準測試工具)來執行下面的自定義SQL指令碼。
BEGIN;PREPARE sel(integer) ASSELECT count(*)FROM contendWHERE id BETWEEN $1 AND $1 + 100;PREPARE upd(integer) ASUPDATE contend SET val = val + 1WHERE 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 600transaction type: subtrans.sqlscaling factor: 1query mode: simplenumber of clients: 6number of threads: 1duration: 600 snumber of transactions actually processed: 100434latency average = 35.846 mstps = 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_value1.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_compare0.51% [.] ExecScan
Test2(90個子事務)
pgbench -f subtrans.sql -n -c 6 -T 600transaction type: subtrans.sqlscaling factor: 1query mode: simplenumber of clients: 6number of threads: 1duration: 600 snumber of transactions actually processed: 41400latency average = 86.965 mstps = 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]

922af274afec4359fe28ca40baa2cbf1.png