jdbc-3-事務以及隔離性驗證

事務介紹

  • 什麼是事務?

    • 事務指的是由一系列操作,將系統從一個狀態變化爲另一個狀態
  • 事務的一致性?

    • 事務的一系列操作,要麼全部成功,要麼全部失敗,不存在中間狀態,稱爲事務性
    • 如果出現失敗,則需要通過“回滾”rollback實現事務的一致性
  • 數據一旦提交,則不可回滾

  • 數據庫事務注意點:

    • DDL操作一但執行,不可回滾
      • 即無法通過auto commit控制DDL操作
    • DML操作默認爲自動提交,一旦commit,不可回滾
      • 可通過set auto_commit=false關閉自動提交
    • 連接關閉後,DML操作會被執行,不論是否有設置過auto commit
    • 故:如果需要實現事務,需要由同一個鏈接完成
  • 注意事項:

    • 如果使用了數據庫連接池,那麼在連接使用完成後,需要將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
      在這裏插入圖片描述

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的下一次(當前事務不受影響)事務生效
      • 權限:都可修改
      • 查看:查不到
  • 注意:數據庫重啓後,所有的隔離設置都會失效
  • 官方文檔: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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章