mysql 事務

-------------------------------------------------------------------------------------------------------------------------------------------html

參考文檔:mysql

http://www.importnew.com/22083.htmlsql

http://www.javashuo.com/article/p-xisokohk-bp.html數據庫

-------------------------------------------------------------------------------------------------------------------------------------------session

mysql:併發

C:\WINDOWS\system32>mysql -V
mysql  Ver 14.14 Distrib 5.5.27, for Win64 (x86)

-------------------------------------------------------------------------------------------------------------------------------------------高併發

1.事務的四大特性(ACID):

原子性(Atomicity):一個事務必須被視爲一個不可分割的最小工做單元,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾,對於一個事務來講,不可能只執行其中的一部分操做。性能

一致性(Consistency):數據庫老是從一個一致性的狀態轉換到另外一個一致性的狀態。測試

隔離性(Isolation):一般來講,一個事務所作的修改在最終提交之前,對其餘事務是不可見的。spa

持久性(Durability):事務完成後,事務對數據庫的全部更新將被保存到數據庫,不能回滾。

2.事務的併發問題:

3種數據讀取問題(髒讀,不可重複讀,幻讀)、2種數據更新問題

髒讀:A事務讀取B事務還沒有commit的數據,並在此基礎上操做,以後B事務進行rollback,那麼A事務讀取到的數據就是髒數據.

時間 轉帳事務A 取款事務B
1 開啓事務  
2   開啓事務
3   查詢帳戶餘額爲1000元
4   取出500元餘額修改成500元
5 查詢帳戶餘額爲500元(髒讀)  
6   撤銷事務餘額恢復爲1000元
7 匯入100元把餘額修改成600元  
8 提交事務  

不可重複讀:事務A從新讀取前面讀取過的數據,發現該數據已經被另外一個已提交的事務B修改過了。

時間 轉帳事務A 取款事務B
1   開始事務
2 開始事務  
3   查詢帳戶餘額爲1000元
4 查詢帳戶餘額爲1000元  
5   取出100元修改餘額爲900元
6   提交事務
7 查詢帳戶餘額爲900元(不可重複讀)  

幻讀:事務A從新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務B提交的行。

時間 轉帳事務A 取款事務B
1   開始事務
2 開始事務  
3 統計總存款爲10000元  
4   新增一個存款帳戶存入100元
5   提交事務
6 再次統計總存款爲10100元(幻讀)  

第1類丟失更新:事務A撤銷時,把已經提交的事務B的更新數據覆蓋了。

時間 取款事務A 轉帳事務B
T1 開始事務  
T2   開始事務
T3 查詢帳戶餘額爲1000元  
T4   查詢帳戶餘額爲1000元
T5   匯入100元修改餘額爲1100元
T6   提交事務
T7 取出100元將餘額修改成900元  
T8 撤銷事務  
T9 餘額恢復爲1000元(丟失更新)  

第2類丟失更新:事務A覆蓋事務B已經提交的數據,形成事務B所作的操做丟失。

時間 轉帳事務A 取款事務B
T1   開始事務
T2 開始事務  
T3   查詢帳戶餘額爲1000元
T4 查詢帳戶餘額爲1000元  
T5   取出100元將餘額修改成900元
T6   提交事務
T7 匯入100元將餘額修改成1100元  
T8 提交事務  
T9 查詢帳戶餘額爲1100元(丟失更新)  

3.事務隔離級別:

數據併發訪問所產生的問題,數據庫一般會經過鎖機制來解決。

按鎖定對象不一樣能夠分爲表級鎖和行級鎖;

按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖;

直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析SQL語句而後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖經過各類手段提升系統的性能,這些對用戶來講都是透明的。

隔離級別 髒讀 不可重複讀 幻讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED 容許 容許 容許 不容許 容許
READ COMMITTED 不容許 容許 容許 不容許 容許
REPEATABLE READ 不容許 不容許 容許 不容許 不容許
SERIALIZABLE 不容許 不容許 不容許 不容許 不容許

事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。

mysql 默認的事務隔離級別是:可重複讀(REPEATABLE READ)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

讀未提交:

1.打開終端A,設置當前會話的事務隔離級別爲read uncommitted。而後開啓事務T1,查詢表test中id爲1的記錄

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |  100 | 讀未提交   |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

2.打開終端B,設置當前會話的事務隔離級別爲read uncommitted。而後開啓事務T2,更新表test中id爲1的記錄中的data字段

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |  100 | 讀未提交       |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

mysql> update test set data=data-1 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |   99 | 讀未提交       |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

此時終端B開啓的事務T2,更新數據以後並未提交。

3.切換到終端A,再次查詢表test中id爲1的記錄

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |   99 | 讀未提交       |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

能夠發現,雖然終端B上開啓的事務T2還沒有提交,可是終端A上開啓的事務T1仍然能夠查詢到事務T2已經更新的數據。此時,若是事務T2進行回滾操做,那麼事務T1查詢到的數據就是髒數據。

4.切換到終端B,進行回滾操做

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |  100 | 讀未提交       |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

5.切換到終端A,更新表test中id爲1的記錄的data字段,更新以前查到的記錄中data字段的值是99

mysql> update test set data=data-1 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test where id=1;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  1 | read uncommitted          |   99 | 讀未提交       |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

能夠發現data字段的值還是99=100-1,而並不是是98=99-1

讀已提交:

1.切換到終端A,設置事務隔離級別爲read committed。而後開啓事務T1,查詢表test中id爲2的記錄

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |  100 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

2.打開終端B,設置當前會話的事務隔離級別爲read committed。而後開啓事務T2,更新表test中id爲2的記錄中的data字段

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |  100 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

mysql> update test set data=data-1 where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |   99 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

此時終端B開啓的事務T2,更新數據以後並未提交。

3.切換到終端A,查詢表test中id爲2的記錄

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |  100 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

能夠發現只要事務T2尚未提交,那麼事務T1中就查詢不到T2更新的數據,解決了髒讀的問題。

4.切換到終端B,進行提交操做

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |   99 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

5.切換到終端A,查詢表test中id爲2的記錄

mysql> select * from test where id=2;
+----+---------------------------+------+----------+
| id | transactionIsolationLevel | data | desc     |
+----+---------------------------+------+----------+
|  2 | read committed            |   99 | 讀已提交        |
+----+---------------------------+------+----------+
1 row in set (0.00 sec)

此時,事務T1 就能查詢到事務T2更新的數據。事務T2提交先後,事務T1各查詢了一次,第一次查詢到的data值爲100,第二次查詢到的data值是99,這就是所謂的不可重複讀。

可重複讀:

1.打開終端A,設置事務隔離級別爲repeatable read。而後開啓事務T1,查詢表test中id爲3的記錄

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=3;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  3 | repeatable read           |  100 | 重複讀      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

2.打開終端B,設置當前會話的事務隔離級別爲repeatable read。而後開啓事務T2,更新表test中id爲3的記錄中的data字段並提交

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=3;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  3 | repeatable read           |  100 | 重複讀      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

mysql> update test set data=data-1 where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=3;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  3 | repeatable read           |   99 | 重複讀      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

3.切換到終端A,查詢表test中id爲3的記錄

mysql> select * from test where id=3;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  3 | repeatable read           |  100 | 重複讀      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

終端A下的兩次查詢結果一致,沒有出現不可重複讀的問題。

可重複讀的隔離級別下使用了MVCC機制,T1事務中讀取的是記錄的快照版本,而非最新版本,T2事務的更新是建立了一個新版原本更新,不一樣事務的讀和寫是分離的。

此時,若在終端A上對data進行更新操做,好比data=data-1;獲得的結果是98=99-1,而不是99=100-1 ----數據的一致性

mysql> update test set data=data-1 where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=3;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  3 | repeatable read           |   98 | 重複讀      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

串行化:

1.打開終端A,設置事務隔離級別爲serializable。而後開啓事務T1,查詢表test中id爲4的記錄

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=4;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  4 | serializable              |  100 | 串行化      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

2.打開終端B,設置當前會話的事務隔離級別爲serializable。而後開啓事務T2

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

查詢表test中id爲4的記錄

mysql> select * from test where id=4;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  4 | serializable              |  100 | 串行化      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

更新表test中id爲4的記錄

mysql> update test set data=data-1 where id=4;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

更新失敗。此時表test已經被事務T1加上了讀鎖

3.切換到終端A,執行提交或是回滾的操做,結束了事務T1。

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

4.切換到終端B,再此執行update語句

mysql> update test set data=data-1 where id=4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where id=4;
+----+---------------------------+------+--------+
| id | transactionIsolationLevel | data | desc   |
+----+---------------------------+------+--------+
|  4 | serializable              |   99 | 串行化      |
+----+---------------------------+------+--------+
1 row in set (0.00 sec)

sql語句就能夠正常執行了。

如下是我在serializable事務隔離級別下作的測試,√表示的是執行過程當中一切正常,×則表示在執行最後一步select/update語句時報錯。
Lock wait timeout exceeded; try restarting transaction

以上只測試了兩個事務併發執行的狀況,更多事務併發,後續再測。
相關文章
相關標籤/搜索