引題:為何引入事務?
1>.資料完整性
2>.資料安全性
3>.充分利用系統資源,提高系統並發處理的能力
1. 事務的特徵
事務具有四個特性:原子性(Atomiocity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability),這四個特性簡稱ACID特性。
1.1原子性
事務是資料庫的邏輯工作單位,事務中包括的所有操作要麼都做,要麼都不做。
1.2 一致性
事務執行的結果必須是使資料庫從一個一致性的狀態變到另外一個一致性狀態。
1.3 隔離性
一個事務的執行不能被其他事務幹擾。即一個事務內部的操作及使用的資料對其他
事務是隔離的,並發執行的各個事務之間互相不干擾。
1.4 持久性
一個事務一旦成功提交,對資料庫中資料的修改就是持久性的。接下來其他的其他
操作或故障不應該對其執行結果有任何影響。
2. MySQL的InnoDB引擎中事物與鎖
2.1 SELECT …… LOCK IN SHARE MODE
會話事務中尋找的資料,加上一個共用鎖定。若會話事務中尋找的資料已經被其他會話事務加上獨佔鎖的話,共用鎖定會等待其結束再加,若等待時間過長就會顯示事務需要的鎖等待逾時。
2.2 SELECT ….. FOR UPDATE
會話事務中尋找的資料,加上一個讀更新瑣,其他會話事務將無法再加其他鎖,必須等待其結束。
2.3 INSERT、UPDATE、DELETE
會話事務會對DML語句操作的資料加上一個獨佔鎖,其他會話的事務都將會等待其釋放獨佔鎖。
2.4 gap and next key lock(間隙鎖)
InnoDB引擎會自動給會話事務中的共用鎖定、更新瑣以及獨佔鎖,需要加到一個區間範圍的時候,再加上個間隙鎖(或稱範圍鎖),對不存在的資料也鎖住,防止出現幻寫。
備忘:
以上2.1,2.2,2.3,2.4中描述的情況,跟MySQL所設定的交易隔離等級也有關係。
3.四種事務隔離模式
3.1 READ UNCOMMITED
SELECT的時候允許髒讀,即SELECT會讀取其他事務修改而還沒有提交的資料。
3.2 READ COMMITED
SELECT的時候無法重複讀,即同一個事務中兩次執行同樣的查詢語句,若在第一次與第二次查詢之間時間段,其他事務又剛好修改了其查詢的資料且提交了,則兩次讀到的資料不一致。
3.3 REPEATABLE READ
SELECT的時候可以重複讀,即同一個事務中兩次執行同樣的查詢語句,得到的資料始終都是一致的。
3.4 SERIALIZABLE
與可重複讀的唯一區別是,預設把普通的SELECT語句改成SELECT …. LOCK IN SHARE MODE。即為查詢語句涉及到的資料加上共用瑣,阻塞其他事務修改真實資料。
4. 驗證事務與鎖定樣本
接下來,我們將以MySQL中的InnoDB引擎,解釋其如何?ACID特性,不同隔離等級下事務與事務之間的影響。樣本表結構:
CREATE TABLE `account ` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`VACCOUNT_ID` varchar(32) NOT NULL,
`GMT_CREATE` datetime NOT NULL,
PRIMARY KEY (`ID`),
KEY `idx_VACCOUNT_PARAMETER_VACCOUNTID ` (`VACCOUNT_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
然後向表account中寫入10W條建立日期分布合理的帳號資料,以方便測試之用。
tx_isolation:SET GLOBAL tx_isolation='read-uncommitted' |
ID |
事務1 |
事務1輸出 |
事務2 |
事務2輸出 |
1 |
START TRANSACTION; |
|
|
|
2 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
|
|
|
START TRANSACTION; |
|
3 |
|
|
UPDATE account set VACCOUNT_ID='uncommitted' where ID =1001; |
|
4 |
|
|
SELECT VACCOUNT_ID from account where ID =1001; |
uncommitted |
5 |
SELECT VACCOUNT_ID from account where ID =1001; |
uncommitted |
|
|
6 |
|
|
ROLLBACK; |
|
7 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
8 |
COMMIT; |
|
|
|
tx_isolation:SET GLOBAL tx_isolation='read-committed' |
ID |
事務1 |
事務1輸出 |
事務2 |
事務2輸出 |
1 |
START TRANSACTION; |
|
|
|
2 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
3 |
|
|
START TRANSACTION; |
|
4 |
|
|
UPDATE account set VACCOUNT_ID='uncommitted' where ID =1001; |
|
5 |
|
|
SELECT VACCOUNT_ID from account where ID =1001; |
uncommitted |
6 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
7 |
|
|
COMMIT; |
|
8 |
SELECT VACCOUNT_ID from account where ID =1001; |
uncommitted |
|
|
9 |
COMMIT; |
|
|
|
tx_isolation:SET GLOBAL tx_isolation='REPEATABLE-READ' |
ID |
事務1 |
事務1輸出 |
事務2 |
事務2輸出 |
1 |
START TRANSACTION; |
|
|
|
2 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
3 |
|
|
START TRANSACTION; |
|
4 |
|
|
UPDATE account set VACCOUNT_ID='uncommitted' where ID =1001; |
|
5 |
|
|
SELECT VACCOUNT_ID from account where ID =1001; |
uncommitted |
6 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
7 |
|
|
COMMIT; |
|
8 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
9 |
COMMIT; |
|
|
|
tx_isolation:SET GLOBAL tx_isolation='SERIALIZABLE' |
ID |
事務1 |
事務1輸出 |
事務2 |
事務2輸出 |
1 |
START TRANSACTION; |
|
|
|
2 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
3 |
|
|
START TRANSACTION; |
|
4 |
|
|
UPDATE account set VACCOUNT_ID='uncommitted' where ID =1001; |
STATE: Updating |
5 |
SELECT VACCOUNT_ID from account where ID =1001; |
caimao101510 |
|
|
|
|
|
事務2逾時 |
|
6 |
COMMIT; |
|
|
|
7 |
START TRANSACTION; |
|
|
|
8 |
UPDATE account set VACCOUNT_ID='uncommitted' where ID =1001; |
|
|
|
9 |
|
|
START TRANSACTION; |
|
10 |
|
|
SELECT VACCOUNT_ID from account where ID =1001; |
STATE:statistics |
11 |
|
|
事務2逾時 |
|
12 |
commit; |
|
|
|
tx_isolation:SET GLOBAL tx_isolation='REPEATABLE-READ' |
ID |
事務1 |
事務1輸出 |
事務2 |
事務2輸出 |
1 |
START TRANSACTION; |
|
|
|
2 |
select max(ID) FROM account; |
124999 |
|
|
3 |
|
|
START TRANSACTION; |
|
4 |
UPDATE account set gmt_create=date_add(gmt_create,interval +1 day) WHERE ID >=124999; |
|
|
|
5 |
|
|
insert into account(VACCOUNT_ID,gmt_create) values(‘eugene',now()); |
STATE:update |
6 |
|
|
事務2逾時 |
|
7 |
|
|
START TRANSACTION; |
|
8 |
|
|
SELECT * FROM account WHERE ID =124998; |
2007-10-20 13:47 |
9 |
|
|
UPDATE account set gmt_create=date_add(gmt_create,interval +1 day) WHERE ID =124998; |
執行成功 |
10 |
|
|
SELECT * FROM account WHERE ID =124998; |
2007-10-21 13:47 |
11 |
COMMIT; |
|
|
|
12 |
|
|
COMMIT; |
|
|
|
|
|
|
1 |
START TRANSACTION; |
|
|
|
2 |
UPDATE account set gmt_create=date_add(gmt_create,interval -1 day) WHERE gmt_create >'2009-07-01′; |
|
|
|
3 |
|
|
START TRANSACTION; |
|
4 |
|
|
SELECT * FROM account WHERE gmt_create>'2009-07-10′ LIMIT 1; |
2009-10-2 13:47 |
5 |
SELECT * FROM account WHERE gmt_create>'2009-07-10′ LIMIT 1; |
2009-10-1 13:47 |
|
STATE:update |
6 |
|
|
insert into account(VACCOUNT_ID,gmt_create) values(‘gmt_create_test',now()); |
|
7 |
|
|
事務2逾時 |
|
8 |
COMMIT; |
|
|
|
9 |
|
|
SELECT * FROM account WHERE gmt_create>'2009-07-10′ LIMIT 1; |
2009-10-1 13:47 |
無索引條件更新事務 |
1 |
START TRANSACTION; |
|
|
|
|
UPDATE account set gmt_create=date_add(gmt_create,interval -1 day) WHERE gmt_create >'2009-07-01′ AND gmt_create <'2009-07-10′; |
|
|
|
|
|
|
START TRANSACTION; |
|
|
|
|
insert into account(VACCOUNT_ID,gmt_create) values(‘gmt_create_interval',now()); |
|
|
|
|
事務2逾時 |
|
|
COMMIT; |