日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

数据隔离级别

發布時間:2023/12/4 综合教程 23 生活家
生活随笔 收集整理的這篇文章主要介紹了 数据隔离级别 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們先來看一句話:

同一個應用程序中的多個事務或不同應用程序中的多個事務在同一個數據集上并發執行時, 可能會出現許多意外的問題

這句話的意思就好比說 你的電腦被其他人遠程操控了,而你自己也可以操控你的電腦,這種現象就是并發執行,數據庫中的事務也存在這樣的現象,這就有可能出問題。


這種現象通常會導致三類問題:

臟讀(Drity Read):

已知有兩個事務A和B,A讀取了已經被B更新但還沒有被提交的數據,之后,B回滾事務,A讀的就是臟數據(說白了臟讀就是讀取了還未提交的數據)。

舉個例子吧:
???????老板發工資,剛開始老板把5000塊錢達到Tom的賬號上,但還沒有提交commit事務,Tom這時候去查自己的工資卡(假設Tom工資卡的余額為1000塊錢),發現多了5000塊錢(即現在余額變成了6000塊錢);但過了一會兒,老板突然發現這5000塊錢打錯了,應該付給Tom 2000塊錢,于是趕緊回滾rollback了事務,又向Tom的工資卡里轉了2000塊錢,并提交commit了事務,這時Tom又查了自己的工資卡,發現余額只剩3000了,于是大呼了一聲:我去

演示:

由于MySQL數據庫默認為Repeatable read(重復讀),所以需要先配置以下(這里提供一種方案):

  • 在my.ini配置文件最后(mysqld下面)加上如下配置:
transaction-isolation = READ-UNCOMMITTED

其中READ-UNCOMMITTED為讀取未提交(即數據未提交也可以被讀取),還有另外三種:READ-COMMITTED為讀取以提交(即只能讀取已提交了的數據)、REPEATABLE-READ為重復讀(只能讀取已提交的數據,不明白了先記著,下面就會講),Serializable(序列化)

  • 配置好后我們測試一下(在服務里重啟一下MySQL數據庫):
    1. 在MySQL數據庫里執行以下語句:
# 此時不可避免臟讀(即可以讀取未提交的數據)
select @@global.tx_isolation,@@tx_isolation;

執行結果為 讀取未提交:

然后創建表

先創建一個表(Tom的工資卡)
create table account(id int(36) primary key comment '主鍵',card_id varchar(16) unique comment '卡號',name varchar(8) not null comment '姓名',balance float(10,2) default 0 comment '余額'
)engine=innodb;#然后向表中添加一條數據(相當于Tom的工資卡的余額為1000塊錢)
insert into account (id,card_id,name,balance) values (1,'6226090219290000','Tom',1000);

我們先執行一下Boss類中的Main方法:

public class Boss {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();//向Tom的工資卡里先打5000塊錢,但并未提交String sql = "update account set balance=balance+5000 where card_id='6226090219290000'";statement.executeUpdate(sql);//讓程序在此處停止30s后再往下執行,30秒后老板發現工資發錯了Thread.sleep(30000);//于是老板趕緊回滾了剛才的事務connection.rollback();//重新向Tom的工資卡里打了2000塊錢sql = "update account set balance=balance+2000 where card_id='6226090219290000'";statement.executeUpdate(sql);//并提交了事務connection.commit();} catch (Exception e) {e.printStackTrace();} finally {//釋放資源try {if (statement!=null) {statement.close();}} catch (SQLException e) {e.printStackTrace();}try {if (connection!=null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}

然后執行Employee(相當于Tom查看自己的工資卡余額)(這里在30s之前執行一次,在30s執行之后再執行一次,會發現兩次的結果不一樣,即前后兩次Tom的工資卡余額不一樣):

public class Employee {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 {//釋放資源try {if (statement!=null) {statement.close();}} catch (SQLException e) {e.printStackTrace();}try {if (connection!=null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}

我們在Boss類Main方法執行后的前30s之前執行一次Employee類中Main方法,會發現余額為6000,但當30s之后再次執行,會發現余額變成了3000,這就是所謂的臟讀

解決臟讀的方法:

將該處的配置換為transaction-isolation = READ-COMMITTED(讀取已提交),重新啟動MySQL數據庫:

這時再次執行Boss類Main方法,在30s之前執行一次Employee類中Main方法,發現余額為1000塊錢,30s之后再次執行,發現余額變成了3000塊錢,即避免了臟讀


不可重復讀(Non-Repeatable Read):

已知有兩個事務A和B,A多次讀取同一數據,B在A多次讀取的過程中對數據作了修改并提交,導致了多次讀取同一數據時,結果不一致

場景:
???????Tom的工資卡里有3000塊錢余額。一天Tom請朋友們聚餐(沒對老婆說)消費了1000塊錢。Tom去柜臺支付,掏出銀行卡去刷pos機,pos機提示余額3000塊錢,可以扣款成功。但Tom剛和了四兩悶倒驢,頭有點犯暈,想了30s,但就在這30s之內,他老婆以迅雷不及掩耳的速度將Tom的工資卡里的3000余額賺到了她自己的賬戶里。Tom想了30s,靈光一閃,記起了密碼,趕緊向pos機里輸密碼,輸完密碼,pos機卻提示余額不足,扣款失敗。于是Tom心中泛起了嘀咕:莫非這pos機…

演示
我們先創建一個表account(向里面存兩個賬號,分別是Tom和他老婆Lily的)

create table account(id int(36) primary key comment '主鍵',card_id varchar(16) unique comment '卡號',name varchar(8) not null comment '姓名',balance float(10,2) default 0 comment '余額'
)engine=innodb;# 假設剛開始Tom的余額為3000,她老婆的余額為0
insert into account (id,card_id,name,balance) values (1,'6226090219290000','Tom',3000);
insert into account (id,card_id,name,balance) values (2,'6226090219299999','LilY',0);

我們先執行Machine類(相當于POS機的操作):

public class Machine {public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {//Tom的消費金額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 balance from account where card_id='6226090219290000'";resultSet = statement.executeQuery(sql);if(resultSet.next()) {System.out.println("余額:"+resultSet.getDouble("balance"));}System.out.println("請輸入支付密碼:");//Tom想密碼想了30sThread.sleep(30000);//30秒后密碼輸入成功resultSet = statement.executeQuery(sql);if(resultSet.next()) {double balance = resultSet.getDouble("balance");System.out.println("余額:"+balance);if(balance<sum) {System.out.println("余額不足,扣款失敗!");return;}}sql = "update account set balance=balance-"+sum+" where card_id='6226090219290000'";statement.executeUpdate(sql);connection.commit();System.out.println("扣款成功!");} catch (Exception e) {e.printStackTrace();} finally {//釋放資源try {if (resultSet != null) {resultSet.close();}} catch (SQLException e) {e.printStackTrace();}try {if (statement != null) {statement.close();}} catch (SQLException e) {e.printStackTrace();}try {if (connection != null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}

然后執行Wife類(相當于Tom的老婆):

public class Wife {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 balance=balance-"+money+" where card_id='6226090219290000'";statement.executeUpdate(sql);sql = "update account set balance=balance+"+money+" where card_id='6226090219299999'";statement.executeUpdate(sql);connection.commit();System.out.println("轉賬成功");} catch (Exception e) {e.printStackTrace();} finally {//釋放資源try {if (statement != null) {statement.close();}} catch (SQLException e) {e.printStackTrace();}try {if (connection != null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}

在執行Machine類后的前30s內,執行Wife類,提示轉賬成功,然后30s后,Tom輸入密碼成功,卻發現余額不足,扣款失敗,這就是不可重復讀

解決不可重復讀的方法(表面上解決了):

在my.ini配置文件最后(mysqld下面)加上如下配置:

transaction-isolation = Repeatable read

但我們會發現這時,POS機可以扣款成功,她老婆也可以轉賬成功,而此時再查兩人的余額,發現Tom的余額為-1000塊錢,他老婆的余額為3000塊錢。
(這是由于MVCC機制,這里暫且不贅述了,有興趣可以百度一下。)



幻讀(Phantom Read):

已知有兩個事務A和B,A從一個表中讀取了數據,然后B在該表中插入了一些數據,導致了A再次讀取同一個表時,就會多出幾行,簡單地說,一個事務中先后讀取一個范圍的記錄,但每次讀取的記錄數不同,稱之為幻像讀。

場景
??????Tom的老婆把Tom管的很嚴,時常查Tom的消費記錄。這一天又查了Tom的消費記錄,發現這個月Tom的消費總額為80塊錢,老婆覺得自己的老公好節儉啊,就準備看一下具體的消費記錄,看看把前都花在了哪里?但就在Tom老婆準備查消費細則時,Tom在外面消費了1000塊錢,Tom的老婆打開消費細則一看,“嗯,怎么是1080塊錢,剛還是80塊錢呢?”…
這就是幻讀
但幻讀一般危害不大,所以在開發中就不會管它。


最后總結一下事務隔離的級別:

事務隔離級別 臟讀 不可重復讀 幻讀
READ-UNCOMMITTED(讀未提交)
READ-COMMITTED(讀提交) ×
REPEATABLE-READ(重復讀) × ×
SERIALIZABLE(序列化) × × ×

注意:√表示可能會出現,×表示不會出現;這四種事務隔離級別從上到下依次提高。

???????一般在開發中SERIALIZABLE(序列化)很少用到,因為它的效率太低了,就好比單行道,雖然可以避免很多問題(上面的三種現象)的發生,但每次只能過一輛車(只能執行一個事務),效率低,花費時間長。你可以想象一下在雙十一晚上,幾萬個人同時點擊提交,結果卻要一個一個執行,可能到你提交訂單成功時已到了兩三天后了。

???????用的最多的還是REPEATABLE-READ(重復讀),既可以防止臟讀、不可重復讀,又可并發執行,效率提高了很多,雖然不可避免有幻讀,但幻讀本身危害不大,所以不足為慮。

???????其中MySQL數據庫支持四種事務隔離級別,默認為REPEATABLE-READ(重復讀),而Oracle數據庫支持READ-COMMITTED(讀提交)和SERIALIZABLE(序列化)兩種事務隔離級別,默認為READ-COMMITTED(讀提交)。

總結

以上是生活随笔為你收集整理的数据隔离级别的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。