事務介紹
-
什麼是事務?
- 事務指的是由一系列操作,將系統從一個狀態變化爲另一個狀態
-
事務的一致性?
- 事務的一系列操作,要麼全部成功,要麼全部失敗,不存在中間狀態,稱爲事務性
- 如果出現失敗,則需要通過“回滾”rollback實現事務的一致性
-
數據一旦提交,則不可回滾
-
數據庫事務注意點:
- DDL操作一但執行,不可回滾
- 即無法通過auto commit控制DDL操作
- DML操作默認爲自動提交,一旦commit,不可回滾
- 可通過set auto_commit=false關閉自動提交
- 連接關閉後,DML操作會被執行,不論是否有設置過auto commit
- 故:如果需要實現事務,需要由同一個鏈接完成
- DDL操作一但執行,不可回滾
-
注意事項:
- 如果使用了數據庫連接池,那麼在連接使用完成後,需要將auto commit恢復原樣。
ACID
-
事務的ACID特性:
- 事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲ACID特性。
- 原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。
- 一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相*關的。
- 隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
- 持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
-
事務併發問題:
- 髒讀dirty read:無效數據的讀出,一般是指由於t1事務回滾,導致t2事務讀取的值停留在t1回滾之前的無效數據
- 不可重複讀non-repeatable read:t2事務開啓期間,同一個查詢命令卻反回不同的查詢結果
- 幻讀phantom read:幻讀是指當事務不是獨立執行時發生的一種現象。
- 典型場景:有t1和t2兩個事務(一般將autocommit置爲false,便於手動控制、復現事務問題,否則一次提交事務就結束了,觀察不到現象),t1和t2同時修改某一個結果集,導致結果不可控:比如t1將某一列的值+1,但是t2又新插入了一行值,那麼t1和t2在提交後發現,t1會查到有一行沒有+1。
- 更新丟失lost update:
- 第一類更新丟失,回滾覆蓋:一個事務的回滾,覆蓋了其它事務的提交
- 第二類更新丟失,提交覆蓋:一個事務的提交,覆蓋了另一個事務的提交
-
數據庫的事務能力:
- 數據庫需要有隔離運行各個事務的能力,避免出現併發問題
- 事務之間的隔離程度,稱爲隔離級別,數據庫裏存在多種隔離級別。
- 但隔離級別和併發型成反比:隔離性越高(一致性越好),但隔離性越弱
-
隔離級別:
- 一般,選擇第2、3隔離級別:READ COMMITED、REPEATABLE READ
- 一般,選擇第2、3隔離級別:READ COMMITED、REPEATABLE READ
mysql設置隔離級別的操作:
查看隔離級別
- SHOW VARIABLES WHERE Variable_name LIKE ‘%iso%’;
- select @@tx_isolation; 查看隔離級別:當前正在生效的隔離級別
- select @@global.tx_isolation; 查看全局隔離級別
- select @@session.tx_isolation;查看會話隔離級別
- global和session區別:
- global:
- 範圍:全局生效(包括所有session),正在運行中的session不受到影響。
- 權限:只有超級用戶才能修改
- 查看:需要登錄退出,才能看到變化
- session:
- 範圍:當前session(一次鏈接、一次cmd窗口)的所有事務,重新登錄/連接後失效
- 權限:都可修改
- 查看:立馬可查到:通過@@tx_isolation或者@@
- 省略不寫時
- 範圍:表示當前session的下一次(當前事務不受影響)事務生效
- 權限:都可修改
- 查看:查不到
- global:
- 注意:數據庫重啓後,所有的隔離設置都會失效
- 官方文檔:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_tx_isolation
- 注意:5.x版本時,transaction_isolation是tx_isolation的別名
- transaction_isolation was added in MySQL 5.7.20 as an alias for tx_isolation, which is now deprecated and is removed in MySQL 8.0. Applications should be adjusted to use transaction_isolation in preference to tx_isolation. See the description of transaction_isolation for details.
mysql> SHOW VARIABLES WHERE Variable_name LIKE '%iso%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.01 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.01 sec)
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> mysql> select @@xx.tx_isolation;
ERROR 1272 (HY000): Variable 'tx_isolation' is not a variable component (can't be used as XXXX.variable_name)
修改隔離級別,查看生效範圍—打開兩個窗口
- 語法:MySQL 提供了 SET TRANSACTION 語句,該語句可以改變單個會話或全局的事務隔離級別。語法格式如下:
- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
- 示例1:set global transaction isolation level READ UNCOMMITTED;:全局
- 示例2:set transaction isolation level READ UNCOMMITTED;:下一次事務
- 默認爲:REPEATABLE-READ
測試修改隔離級別:— 需要開啓兩個事務觀察
-
測試global修改:
-
測試session級別修改:
驗證不同級別的隔離性問題:— 需要開啓兩個事務觀察
-
READ UNCOMMITTED:
-
READ COMMITTED:
-
REPEATABLE READ:沒有復現出來,有點奇怪:從官網的描述來看,在該模式下,只要讀了一次,那就會形成一個副本,但是沒有提及幻讀:
- 官網:https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_consistent_read
- 幻讀的場景比較複雜,涉及到數據庫鎖相關的知識,目前未復現出來:https://blog.csdn.net/zl1zl2zl3/article/details/90755790
- 待後續對sql的鎖進行分析後再看,目前已知的是:mysql的innodb引擎一定程度上解決了幻讀的問題
java代碼測試隔離性:
- 場景:這裏只列出“讀未提交+髒讀”場景
- 代碼:測試場景參見下面代碼裏的註釋
package PreparedStatementTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import utils.ConnGetUtil;
import java.sql.*;
/**
* 測試隔離級別
*/
public class TestIsolation {
private Connection connection;
private PreparedStatement preparedStatement;
private ResultSet resultSet;
@Before
public void setConnection() {
try {
connection = ConnGetUtil.getConnection();
} catch (Exception e) {
System.out.println("獲取連接失敗");
e.printStackTrace();
}
}
@After
public void close() {
ConnGetUtil.closeConnection(connection, preparedStatement, resultSet);
}
/**
* t1事務進程
* 驗證場景:
* t1用例和t2用例同時執行,重現髒讀的場景:
* t1事務:開啓事務下,先更新balance,然後回滾rollback掉
* t2事務:開啓事務下,每隔3s查詢一次balance
* 驗證結果:
* 可以觀察到t2事務的balance在變化,出現了髒讀場景
*
* 備註:幻讀、不可重複讀暫時不管
*/
@Test
public void t1() throws Exception {
// 4種隔離級別:1爲讀未提交、2位讀已提交、4爲可重複讀、8爲串行化讀
connection.setTransactionIsolation(1);
connection.setAutoCommit(false);
String sql = "update user_table set balance=9999 where user='lisi'";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.execute();
Thread.sleep(1000 * 5); // 15s
connection.rollback(); // 回滾
}
/**
* t2事務
*/
@Test
public void t2() throws Exception {
// 4種隔離級別:1爲讀未提交、2位讀已提交、4爲可重複讀、8爲串行化讀
connection.setTransactionIsolation(1);
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement("select balance from user_table where user='lisi'");
for (int i = 0; i < 10; i++) {
Thread.sleep(1000 * 3);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
int anInt = resultSet.getInt(1);
System.out.println("查詢到balance爲:" + anInt);
}
}
}
}
- t2事務的輸出:可以看到,在事務開啓之間,查到的balance值是在不斷變化,出現了髒讀現象
查詢到balance爲:3333
查詢到balance爲:3333
查詢到balance爲:9999
查詢到balance爲:9999
查詢到balance爲:9999
查詢到balance爲:9999
查詢到balance爲:9999
查詢到balance爲:3333
查詢到balance爲:3333
查詢到balance爲:3333
- 參考1:mysql 5.x版本的發佈文檔:https://dev.mysql.com/doc/relnotes/mysql/5.7/en/
- 參考2:https://www.jb51.net/article/182017.htm