-------------------------------------------------------------------------------------------------------------------------------------------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)
-------------------------------------------------------------------------------------------------------------------------------------------高併發
原子性(Atomicity):一個事務必須被視爲一個不可分割的最小工做單元,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾,對於一個事務來講,不可能只執行其中的一部分操做。性能
一致性(Consistency):數據庫老是從一個一致性的狀態轉換到另外一個一致性的狀態。測試
隔離性(Isolation):一般來講,一個事務所作的修改在最終提交之前,對其餘事務是不可見的。spa
持久性(Durability):事務完成後,事務對數據庫的全部更新將被保存到數據庫,不能回滾。
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元(丟失更新) |
數據併發訪問所產生的問題,數據庫一般會經過鎖機制來解決。
按鎖定對象不一樣能夠分爲表級鎖和行級鎖;
按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖;
直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析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
以上只測試了兩個事務併發執行的狀況,更多事務併發,後續再測。