事務隔離級別——READ-COMMITTED(讀已提交)


版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接: https://blog.csdn.net/Zzze0101/article/details/91344710

首先,我們先設置MySQL事務隔離級別為Read committed

  1. 在my.ini配置文件最后加上如下配置
#可選參數有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.
[mysqld]
transaction-isolation = READ-COMMITTED
  1. 重啟MySQL服務

1、臟讀

提出問題
同一個應用程序中的多個事務或不同應用程序中的多個事務在同一個數據集上並發執行時, 可能會出現許多意外的問題。
例如: 已知有兩個事務A和B, B讀取了已經被A更新但還沒有被提交的數據,之后,A回滾事務,B讀取的數據就是臟數據。
場景:
Tom的賬戶money=0,公司發工資把5000元打到Tom的賬戶上,Tom的money=money+5000元,但是該事務並未提交,而Tom正好去查看賬戶,發現工資已經到賬,賬戶money=5000元,非常高興,可是不幸的是,公司發現發給Tom的工資金額不對,應該是2000元,於是迅速回滾了事務,修改金額后,將事務提交,Tom再次查看賬戶時發現賬戶money=2000元,Tom空歡喜一場,從此郁郁寡歡,走上了不歸路……
當我們設置事務隔離級別為READ-COMMITTED(讀已提交)時事務流程如下:

事務A(代表公司) 事務B(代表Tom)
read(money);  
money=money+5000;  
write(money)  
  read(money);(讀已提交數據)
 
rollback;(money=0)  
money=money+2000  
submit ;  
  read(money);(讀已提交數據)

分析:上述情況即為臟讀,兩個並發的事務:“事務A:公司給Tom發工資”、“事務B:Tom查詢工資賬戶”,事務隔離級別為READ-COMMITTED(讀已提交)時事務B只會讀取已提交的數據。
實驗
我們在java代碼中觀察這種情況:

public class Boss {//公司給Tom發工資

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            String sql = "update account set money=money+5000 where card_id='6226090219290000'";
            statement.executeUpdate(sql);
            Thread.sleep(10000);//10秒后發現工資發錯了
            connection.rollback();
            sql = "update account set money=money+2000 where card_id='6226090219290000'";
            statement.executeUpdate(sql);
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}
public class Employee {//Tom查詢余額

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            statement = connection.createStatement();
            String sql = "select balance from account where card_id='6226090219290000'";
            resultSet = statement.executeQuery(sql);
            if(resultSet.next()) {
                System.out.println(resultSet.getDouble("balance"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}

 

 

在執行Boss中main方法后立即執行Employee中的main方法得:
在這里插入圖片描述
在執行Boss中main方法后等待10秒,執行Employee中的main方法得:
在這里插入圖片描述
得出結論
事務隔離級別為READ-COMMITTED(讀已提交)時不會出現“臟讀”

2、不可重復讀

提出問題
場景:Tom拿着工資卡去消費,酒足飯飽后在收銀台買單,服務員告訴他本次消費1000元,Tom將銀行卡給服務員,服務員將銀行卡插入POS機,POS機讀到卡里余額為3000元,就在Tom磨磨蹭蹭輸入密碼時,他老婆以迅雷不及掩耳盜鈴之勢把Tom工資卡的3000元轉到自己賬戶並提交了事務,當Tom輸完密碼並點擊“確認”按鈕后,POS機檢查到Tom的工資卡已經沒有錢,扣款失敗,Tom十分納悶,明明卡里有錢,於是懷疑POS有鬼,和收銀小姐姐大打出手,300回合之后終因傷勢過重而與世長辭,Tom老婆痛不欲生,郁郁寡歡,從此走上了不歸路…
當我們設置事務隔離級別為READ-COMMITTED(讀已提交)時事務流程如下:

事務A(代表POS機) 事務B(代表老婆)
read(money);  
輸入密碼 read(money);
money=money-3000;(轉賬)
write(money);submit ;
read(money);  
余額不足!  

分析:上述情況即為不可重復讀,兩個並發的事務,“事務A:POS機扣款”、“事務B:Tom的老婆網上轉賬”,事務A事先讀取了數據,事務B緊接了更新數據並提交了事務,而事務A再次讀取該數據扣款時,數據已經發生了改變。
實驗
我們在java代碼中觀察這種情況:

public class Machine {//POS機扣款

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            double sum=1000;//消費金額
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            String sql = "select money from account where card_id='6226090219290000'";
            resultSet = statement.executeQuery(sql);
            if(resultSet.next()) {
                System.out.println("余額:"+resultSet.getDouble("money"));
            }
            
            System.out.println("請輸入支付密碼:");
            Thread.sleep(10000);//10秒后密碼輸入成功
            
            resultSet = statement.executeQuery(sql);
            if(resultSet.next()) {
                double money = resultSet.getDouble("money");
                System.out.println("余額:"+money);
                if(money<sum) {
                    System.out.println("余額不足,扣款失敗!");
                    return;
                }
            }
            
            sql = "update account set money=money-"+sum+" where card_id='6226090219290000'";
            statement.executeUpdate(sql);
            connection.commit();
            System.out.println("扣款成功!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}
public class Wife {//Tom的老婆網上轉賬

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            double money=3000;//轉賬金額
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            String sql = "update account set money=money-"+money+" where card_id='6226090219290000'";
            statement.executeUpdate(sql);
            sql = "update account set money=money+"+money+" where card_id='6226090219299999'";
            statement.executeUpdate(sql);
            connection.commit();
            System.out.println("轉賬成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}

 

在執行Machine中main方法后立即執行Wife中的main方法得:

在這里插入圖片描述
等待10秒后Machine中main方法執行結果:
在這里插入圖片描述

得出結論
事務隔離級別為READ-COMMITTED(讀已提交)時會出現“不可重復讀”。

3、幻讀

幻讀(Phantom Read): 已知有兩個事務A和B,A從一個表中讀取了數據,然后B在該表中插入了一些新數據,導致A再次讀取同一個表, 就會多出幾行。
提出問題
場景:Tom的老婆工作在銀行部門,她時常通過銀行內部系統查看Tom的工資卡消費記錄。2019年5月的某一天,她查詢到Tom當月工資卡的總消費額為80元,Tom的老婆非常吃驚,心想“老公真是太節儉了,嫁給他真好!”,而Tom此時正好在外面胡吃海塞后在收銀台買單,消費1000元,即新增了一條1000元的消費記錄並提交了事務,沉浸在幸福中的老婆查詢了Tom當月工資卡消費明細一探究竟,可查出的結果竟然發現有一筆1000元的消費,Tom的老婆瞬間怒氣沖天,外賣訂購了一個大號的榴蓮,傍晚降臨,Tom生活在了水深火熱之中,只感到膝蓋針扎的痛…
當我們設置事務隔離級別為READ-COMMITTED(讀已提交)時事務流程如下:

事務A(代表老婆) 事務B(代表Tom消費)
read(消費記錄);  
消費金額80元 read(money);
money=money-1000;(消費)
write(money);submit ;
read(消費記錄);  
消費金額1080元  

分析:上述情況即為幻讀,兩個並發的事務,“事務A:獲取事務B消費記錄”、“事務B:添加了新的消費記錄”,事務A獲取事務B消費記錄時數據多出了一條。
實驗
我們在java代碼中觀察這種情況:

public class Bank {//老婆查看消費記錄

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            String sql = "select sum(amount) total from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";
            resultSet = statement.executeQuery(sql);
            if(resultSet.next()) {
                System.out.println("總額:"+resultSet.getDouble("total"));
            }

            Thread.sleep(10000);//30秒后查詢2019年5月消費明細
            
            sql="select amount from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";
            resultSet = statement.executeQuery(sql);
            System.out.println("消費明細:");
            while(resultSet.next()) {
                double amount = resultSet.getDouble("amount");
                System.out.println(amount);
            }
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}
public class Husband {//Tom消費1000元

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            double sum=1000;//消費金額
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/test";
            connection = DriverManager.getConnection(url, "root", "root");
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            String sql = "update account set money=money-"+sum+" where card_id='6226090219290000'";
            statement.executeUpdate(sql);
            sql = "insert into record (id,card_id,amount,create_time) values (3,'6226090219290000',"+sum+",'2019-05-19');";
            statement.executeUpdate(sql);
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
        }
    }
}

 

 

在執行Bank中main方法后立即執行Wife中的Husband方法得:
在這里插入圖片描述
等待10秒后控制台輸出:在這里插入圖片描述
得出結論
事務隔離級別為READ-COMMITTED(讀已提交)時會出現“幻讀”

所用表

create table account(
    id int(36) primary key comment '主鍵',
      card_id varchar(16) unique comment '卡號',
      name varchar(8) not null comment '姓名',
      money float(10,2) default 0 comment '余額'
)engine=innodb;
insert into account (id,card_id,name,money) values (1,'6226090219290000','Tom',3000);

create table record(
    id int(36) primary key comment '主鍵',
    card_id varchar(16) comment '卡號',
    amount float(10,2) comment '金額',
    create_time date comment '消費時間'
)engine=innodb;
insert into record (id,card_id,amount,create_time) values (1,'6226090219290000',37,'2019-05-01');
insert into record (id,card_id,amount,create_time) values (2,'6226090219290000',43,'2019-05-07');

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM