PG的事務和併發控制

PG的事務和併發控制

1 概述

事務與併發控制
數據完整性有關
併發與吞吐量有關

併發體現的方面
網絡吞吐量
CPU使用率
IO使用率
內存資源使用率
其他資源消耗

** 併發可能會引起事務的混亂**
ACID特性
控制數據庫併發
基於鎖的併發控制( Locked- based Concurrency Control
基於多版本的併發控制( MVCC Mutil Version Concurrency Control)

廣義上的併發控制
MVCC
讀寫互相不阻塞
S2PL(嚴格兩階段鎖定 Strict two phase locking)
寫入數據會阻塞數據讀取
** OCC**(樂觀併發控制 Optimistic Concurrency Contro)

事務
實際上就是一個執行的語句塊
BEGIN
執行語句塊
EnD
在 PostgreSQL中,通常對於一個事務需要顯式指定.TRANSACTION(事務)是數據庫管理系統執行過程中的一個邏輯單位,由一個有限的數據庫操作序列構成。

2 事務的屬性ACID:

事務具有以下四個標準屬性的縮寫ACID,通常被稱爲: yiibai.com

  • 原子性: 確保工作單位內的所有操作都成功完成,否則,該事務所被中止在故障點,和以前的操作將回滾到以前的狀態。
ATM取錢
用戶1轉賬給用戶2
用戶2賬戶上操作失敗了,本次事務就整體失敗
用戶1和用戶2賬戶都不會被修改
  • 一致性: 確保數據庫正確地改變狀態後成功提交的事務。
業務邏輯整體完整
用戶1給用戶2轉賬,無論事務操作是否成功
兩個用戶賬戶的總金額不會改變
1—>2轉賬成功500用戶1:1000-500=500
用戶2:2000+500=2500
2轉賬失敗500用戶1:1000
用戶2:2000
數據庫的完整性
表結構上面的約束,索引都是完整正確的。
  • 隔離性: 使交易操作相互獨立的和透明的。
  • 持久性: 確保已提交事務的結果或效果在系統發生故障的情況下仍然存在。

表1:事務的4個特徵ACID及響應的實現技術

ACID 實現技術
原子性 MVCC
一致性 約束(主鍵,外鍵等)
隔離性 MVCC
持久性 WAL

可以看到PostgreSQL中支撐ACID的主要是MVCC和WAL兩項技術。MVCC和WAL是兩個比較成熟的技術,通常的關係數據庫中都有相應的實現,但每個數據庫具體的實現方式又存在很大差異。下面介紹一下PostgreSQL中MVCC和WAL的基本實現原理。

MVCC的實現方法有兩種:
1.寫新數據時,把舊數據移到一個單獨的地方,如回滾段中,其他人讀數據時,從回滾段中把舊的數據讀出來;
2.寫數據時,舊數據不刪除,而是把新數據插入。
PostgreSQL數據庫使用第二種方法,而Oracle數據庫和MySQL中的innodb引擎使用的是第一種方法。
與oracle數據庫和MySQL中的innodb引擎相比較,PostgreSQL的MVCC實現方式的優缺點如下。
優點:
1.事務回滾可以立即完成,無論事務進行了多少操作;
2.數據可以進行很多更新,不必像Oracle和MySQL的Innodb引擎那樣需要經常保證回滾段不會被用完,也不會像oracle數據庫那樣經常遇到“ORA-1555”錯誤的困擾;
缺點:
1.舊版本數據需要清理。PostgreSQL清理舊版本的命令成爲Vacuum;
2.舊版本的數據會導致查詢更慢一些,因爲舊版本的數據存在於數據文件中,查詢時需要掃描更多的數據塊。

3 事務控制:

使用下面的命令來控制事務:

  • BEGIN TRANSACTION: 開始事務.
  • COMMIT: 保存更改,或者可以使用END TRANSACTION命令.
  • ROLLBACK: 回滾事務。

4 事務的隔離級別

四種隔離級別

SQL標準定義了四種隔離級別。最嚴格的是可序列化,在標準中用了一整段來定義它,其中說到一組可序列化事務的任意併發執行被保證效果和以某種順序一個一個執行這些事務一樣。其他三種級別使用併發事務之間交互產生的現象來定義,每一個級別中都要求必須不出現一種現象。注意由於可序列化的定義,在該級別上這些現象都不可能發生。

在各個級別上被禁止出現的現象是:

髒讀:一個事務讀取了另一個並行未提交事務寫入的數據。

時間 事務A 事務B
T1 開始事務
T2 開始事務
T3 查詢賬戶餘額1000
T4 去除500元,餘額500
T5 查詢餘額爲500(髒讀)

不可重複讀:一個事務重新讀取之前讀取過的數據,發現該數據已經被另一個事務(在初始讀之後提交)修改。

時間 事務A 事務B
T1 開始事務
T2 查詢餘額爲1000 開始事務
T3 查詢賬戶餘額1000
T4 去除500元,餘額500
T5 提交事務
T6 查詢餘額爲500
T7 提交事務

幻讀:一個事務重新執行一個返回符合一個搜索條件的行集合的查詢, 發現滿足條件的行集合因爲另一個最近提交的事務而發生了改變。
序列化異常:成功提交一組事務的結果與一次運行這些事務的所有可能順序不一致。

時間 事務A 事務B
T1 開始事務
T2 select count(*) from Foos where flag1=1 //(10條) 開始事務
T3 update Foos set flag2=2 where flag1=1 //(10條)
T4 insert into Foos (…,flag1,…) values (…, 1 ,…)
T5 提交事務
T6 select count(*) from Foos where flag1=1 //(11條)
T7 update Foos set flag2=2 where flag1=1 //(更新11條)
T8 提交事務

會看到新插入的那條數據會被更新

SQL標準和PostgreSQL實現的事務隔離級別如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-al766qXg-1589641294695)(D:\SYBASE&informix&DB2\2 Postgresql\3 markdown筆記\體系架構\晟數體系架構7天之旅\4 PG的事務.assets\20190311164109266.png)]

PostgreSQL事務的隔離級別目前有4種,分別是:讀未提交,讀已提交,可重複讀,串行化。在PostgreSQL裏,你可以請求四種可能的事務隔離級別中的任意一種。但是在內部, 實際上只有三種獨立的隔離級別,分別對應讀已提交,可重複讀和可串行化。如果你選擇了讀未提交的級別, 實際上你用的是讀已提交,在重複讀的PostgreSQL執行時,幻讀是不可能的, 所以實際的隔離級別可能比你選擇的更嚴格。這是因爲把標準隔離級別映射到 PostgreSQL 的多版本併發控制架構的唯一合理的方法。

該表格也顯示 PostgreSQL 的可重複讀實現不允許幻讀。而 SQL 標準允許更嚴格的行爲:四種隔離級別只定義了哪種現像不能發生,但是沒有定義哪種現像必須發生。某些PostgreSQL數據類型和函數關於事務的行爲有特殊的規則。特別是,對一個序列的修改(以及用serial聲明的一列的計數器)是立刻對所有其他事務可見的,並且在作出該修改的事務中斷時也不會被回滾。

要設置一個事務的事務隔離級別,使用SET TRANSACTION命令。

SET TRANSACTION transaction_mode [, ...]
SET TRANSACTION SNAPSHOT snapshot_id
SET SESSION CHARACTERISTICS AS TRANSACTION transaction_mode [, ...]

where transaction_mode is one of:

    ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

例如:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT pg_export_snapshot();
 pg_export_snapshot
---------------------
 00000003-0000001B-1
(1 row)

或者:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION SNAPSHOT '00000003-0000001B-1';
  1. 讀已提交(讀未提交相同)
    讀已提交是PostgreSQL中的默認隔離級別。 當一個事務運行使用這個隔離級別時, 一個查詢只能看到查詢開始之前已經被提交的數據, 而無法看到未提交的數據或在查詢執行期間其它事務提交的數據。務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變,造成了不可重複讀。

  2. 可重複讀
    可重複讀隔離級別只看到在事務開始之前被提交的數據;它從來看不到未提交的數據或者並行事務在本事務執行期間提交的修改(不過,查詢能夠看見在它的事務中之前執行的更新,即使它們還沒有被提交)。這個級別與讀已提交不同之處在於,一個可重複讀事務中的查詢可以看見在事務中第一個非事務控制語句開始時的一個快照,而不是事務中當前語句開始時的快照。因此,在一個單一事務中的後續SELECT命令看到的是相同的數據,即它們看不到其他事務在本事務啓動後提交的修改。

  3. 可序列化
    可序列化隔離級別提供了最嚴格的事務隔離。這個級別爲所有已提交事務模擬序列事務執行;就好像事務被按照序列一個接着另一個被執行,而不是並行地被執行。但是,和可重複讀級別相似,使用這個級別的應用必須準備好因爲序列化失敗而重試事務。事實上,這個給力級別完全像可重複讀一樣地工作,除了它會監視一些條件,這些條件可能導致一個可序列化事務的併發集合的執行產生的行爲與這些事務所有可能的序列化(一次一個)執行不一致。這種監控不會引入超出可重複讀之外的阻塞,但是監控會產生一些負荷,並且對那些可能導致序列化異常的條件的檢測將觸發一次序列化失敗。

實驗

duyeweb=# \h begin
Command:     BEGIN
Description: start a transaction block
Syntax:
BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ]

where transaction_mode is one of:

    ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

讀未提交

表示可以讀到其他會話未提交的數據(postgresql不支持)。

讀已提交

表示可以讀到其他會話已提交的數據。

  1. 創建一張表爲test,插入一條記錄
duyeweb=# create table test(a int, b varchar(128));
CREATE TABLE
duyeweb=# insert into test values(1, 'hello');
INSERT 0 1

2 在會話1中打開事務進行查詢

duyeweb=# begin;
BEGIN
duyeweb=# select * from test;
 a |   b   
---+-------
 1 | hello
(1 row)

3 在會話2中打開事務進行更新

duyeweb=# begin;
BEGIN
duyeweb=# update test set b='xxxx';
UPDATE 1

4 此時在會話2中還沒有關閉事務,在會話1中進行查詢

duyeweb=# select * from test;
 a |   b   
---+-------
 1 | hello
(1 row)

5 發現會話1中的記錄並沒有進行改變。當提交會話2中的事務,在會話1中進行查詢值已經改變

duyeweb=# begin;
BEGIN
duyeweb=# update test set b='xxxx';
UPDATE 1
duyeweb=# commit;
COMMIT

再次查詢:

duyeweb=# select * from test;
 a |  b   
---+------
 1 | xxxx
(1 row)
可重複讀

表示在一個事務中,執行同一條SQL,讀到的是同樣的數據(即使被讀的數據可能已經被其他會話修改並提交)。

  1. 在會話1中打開可重複讀事務,進行查詢
duyeweb=# begin transaction isolation level repeatable read;
BEGIN
duyeweb=# select * from test;
 a |  b   
---+------
 1 | xxxx
(1 row)

2 在會話2中進行更新操作

duyeweb=# update test set b='yyyyy';
UPDATE 1

3 在會話1中進行,發現會話1中的記錄沒有因爲會話2的提交而變化

duyeweb=# select * from test;
 a |   b   
---+-------
 1 | xxxx
(1 row)

4 在會話1中進行提交,再查詢,發現會話1中的記錄變化了

duyeweb=# commit;
duyeweb=# select * from test;
 a |   b   
---+-------
 1 | yyyyy
(1 row)

串行化

表示並行事務模擬串行執行,違反串行執行規則的事務,將回滾。

1 在會話 1中打開事務串行

duyeweb=# begin transaction isolation level serializable;

2 在會話2中打開事務串行

duyeweb=# begin transaction isolation level serializable;

3 在會話1中進行插入操作

duyeweb=# insert into test select * from test; 
INSERT 0 1
duyeweb=# select * from test;                 
 a |   b   
---+-------
 1 | yyyyy
 1 | yyyyy
(2 rows)

4 在會話2中進行插入操作

duyeweb=# insert into test select * from test; 
INSERT 0 1
duyeweb=# select * from test; 
 a |   b   
---+-------
 1 | yyyyy
 1 | yyyyy
(2 rows)

5 提交會話1中的事務,能夠正常提交,進行查詢能夠查到插入的記錄

duyeweb=# end;
COMMIT
duyeweb=# select * from test;
 a |   b   
---+-------
 1 | yyyyy
 1 | yyyyy
(2 rows)

6 當提交會話2的事務時會報錯

duyeweb=# end;
ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

其他的測試辦法可以想見https://www.jianshu.com/p/2463578b2639

從上面的三個測試例子中我們可以看出,

1、在讀提交的隔離級別中,查詢可以看到select語句開始執行前的所有session 提交的所有數據更改;

2、在可重複讀和序列化讀級別,查詢只能看到事務開啓時刻之前的所有 session提交的數據,保留的數據是事務開啓時刻的快照版本,一直到commit這一時間點;

3、PG中序列化讀取級別比可重複讀級別更加嚴格,可重複讀不能修改其他事務語句修改的記錄,但是可以修改快照裏面的其他記錄,而序列化讀只要有事務進行了修改,那麼他做的所有的操作都會被回滾

ANSISQL可串行化
PostgreSQL中:可串行化異常
PostgreSQL使用MVCC的變體(SI) Snapshot Isolation


事務A(窗口一)
sdedu@sdedudb=>BEGIN
sdedu@sdedudb=>SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
SET
sdedu@sdedudb=>SELECT * FROM t1 WHERE id=3,
id name
3|DB2
事務B(窗口二)
BEGIN
sdeduasdedudb=>UPDATE t1 SET name= 'SQLServer' WHERE id=3;
UPDATE 1
sdedu@sdedudb=>END
COMMIT
事務A(窗口一)
sdedu@sdedudb=>UPDATE t1 SET name ='Redis' WHERE id=3;
ERROR: could not serialize access due to concurrent update

5 查看和修改隔離級別

設置事務的隔離等級
會話級別
SET SESSION TRANACTION ISOLATION LEVEL隔離等級
系統級別
ALTER SYSTEM SET TRANSACTION ISOLATION

postgresql. conf文件修改
查看事務的隔離等級
sdedu@sdedudb->SELECT name,setting FROM pg_settings
WHERE name ~ ‘transaction’;
name
default transaction isolation read committed
transaction isolation read committed

查看事務隔離級別

(postgres@[local]:5432)[akendb01]#show transaction_isolation ;

transaction_isolation

read committed
(1 row)
(postgres@[local]:5432)[akendb01]#

臨時修改會話的事務隔離級別:

(postgres@[local]:5432)[postgres]#begin transaction isolation level repeatable read;
BEGIN
(postgres@[local]:5432)[postgres]#show transaction_isolation ;

transaction_isolation

repeatable read
(1 row)
(postgres@[local]:5432)[postgres]#begin transaction isolation level read committed;
WARNING: there is already a transaction in progress
(postgres@[local]:5432)[postgres]#end;
COMMIT
(postgres@[local]:5432)[postgres]#show transaction_isolation ;

read committed
(1 row)
(postgres@[local]:5432)[postgres]#

永久修改事務隔離級別,需要修改postgresql.conf文件參數值。

[postgres@akendb01]KaTeX parse error: Expected 'EOF', got '#' at position 52: …ostgresql.conf #̲default_transac…
或者直接alter system將參數值寫到postgresql.auto.conf參數文件中

(postgres@[local]:5432)[postgres]#alter system set default_transaction_isolation=“repeatable read”;
ALTER SYSTEM
(postgres@[local]:5432)[postgres]#show default_transaction_isolation;

default_transaction_isolation

read committed
(1 row)
重新reload生效:

(postgres@[local]:5432)[postgres]#exit
[postgres@akendb01]pgctlreloadserversignaled[postgres@akendb01]pg_ctl reload server signaled [postgres@akendb01]psql
psql (11.5)
Type “help” for help.
(postgres@[local]:5432)[postgres]#show default_transaction_isolation;

default_transaction_isolation

repeatable read
(1 row)
(postgres@[local]:5432)[postgres]#
默認爲read committed,建議改回來:

(postgres@[local]:5432)[postgres]#alter system set default_transaction_isolation=“read committed”;
ALTER SYSTEM
(postgres@[local]:5432)[postgres]#exit
[postgres@akendb01]pgctlreloadserversignaled[postgres@akendb01]pg_ctl reload server signaled [postgres@akendb01]psql
psql (11.5)
Type “help” for help.
(postgres@[local]:5432)[postgres]#show default_transaction_isolation;

default_transaction_isolation

read committed
(1 row)
(postgres@[local]:5432)[postgres]#

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章