摘要:在相關(guān)數(shù)據(jù)庫中,所有規(guī)則都必須應(yīng)用于事務(wù)的修改,以保持所有數(shù)據(jù)的完整性。隔離性一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)所影響。比如,事務(wù)在事務(wù)提交前讀到的結(jié)果,和提交后讀到的結(jié)果可能不同。
重要
由于之前代碼的不嚴(yán)謹(jǐn),導(dǎo)致結(jié)果和結(jié)論的錯(cuò)誤,深表歉意,現(xiàn)在對(duì)其進(jìn)行修正
摘要事務(wù)事務(wù)在日常開發(fā)中是不可避免碰到的問題,JDBC中的事務(wù)隔離級(jí)別到底會(huì)如何影響事務(wù)的并發(fā),臟讀(dirty reads), 不可重復(fù)讀(non-repeatable reads),幻讀(phantom reads)到底是什么概念
原子性(atomicity) 事務(wù)是數(shù)據(jù)庫的邏輯工作單位,而且是必須是原子工作單位,對(duì)于其數(shù)據(jù)修改,要么全部執(zhí)行,要么全部不執(zhí)行。
一致性(consistency) 事務(wù)在完成時(shí),必須是所有的數(shù)據(jù)都保持一致狀態(tài)。在相關(guān)數(shù)據(jù)庫中,所有規(guī)則都必須應(yīng)用于事務(wù)的修改,以保持所有數(shù)據(jù)的完整性。
隔離性(isolation) 一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)所影響。
持久性(durability) 一個(gè)事務(wù)一旦提交,事物的操作便永久性的保存在數(shù)據(jù)庫中,即使此時(shí)再執(zhí)行回滾操作也不能撤消所做的更改。
隔離性以上是數(shù)據(jù)庫事務(wù)-ACID原則,在JDBC的事務(wù)編程中已經(jīng)為了我們解決了原子性,持久性的問題,唯一可配置的選項(xiàng)是事務(wù)隔離級(jí)別,根據(jù)com.mysql.jdbc.Connection的定義有5個(gè)級(jí)別:
TRANSACTION_NONE(不支持事務(wù))
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_READ_COMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE
讀不提交(TRANSACTION_READ_UNCOMMITTED)不能避免dirty reads,non-repeatable reads,phantom reads
讀提交(TRANSACTION_READ_COMMITTED)可以避免dirty reads,但是不能避免non-repeatable reads,phantom reads
重復(fù)讀(TRANSACTION_REPEATABLE_READ)可以避免dirty reads,non-repeatable reads,但不能避免phantom reads
序列化(TRANSACTION_SERIALIZABLE)可以避免dirty reads,non-repeatable reads,phantom reads
創(chuàng)建一個(gè)簡單的表來測試一下隔離性對(duì)事務(wù)的影響
CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;臟讀(dirty reads)
事務(wù)A修改了一個(gè)數(shù)據(jù),但未提交,事務(wù)B讀到了事務(wù)A未提交的更新結(jié)果,如果事務(wù)A提交失敗,事務(wù)B讀到的就是臟數(shù)據(jù)。
TEST:
事務(wù)A: update account += 1000, 然后回滾
事務(wù)B: 嘗試讀取 account 的值
期望結(jié)果:
當(dāng)設(shè)置隔離級(jí)別為TRANSACTION_READ_UNCOMMITTED時(shí),事務(wù)B讀取到的值不一致
當(dāng)設(shè)置隔離級(jí)別大于TRANSACTION_READ_UNCOMMITTED時(shí),事務(wù)B讀取到的值一致
先創(chuàng)建一個(gè)read任務(wù)
class ReadTask implements Runnable { int level = 0; public ReadTask(int level) { super(); this.level = level; } @Override public void run() { Db.tx(level, new IAtom() { @Override public boolean run() throws SQLException { AccountService service = new AccountService(); System.out.println(Thread.currentThread().getId() + ":" + service.audit()); return true; } }); } }
其中AccountService代碼(提供了讀和寫balance的方法)
public class AccountService { // 貌似這個(gè)方法有執(zhí)行了行鎖 public void deposit(int num) throws Exception { int index = Db.update("update account set balance = balance + " + num + " where user_id = 1"); if(index != 1) throw new Exception("Oop! deposit fail."); } public int audit() { return Db.findFirst("select balance from account where user_id = 1").getInt("balance"); } }
PS: 上述代碼所使用的框架為JFinal(非常優(yōu)秀的國產(chǎn)開源框架)
對(duì)于Db.findFirst和Db.update這2個(gè)方法就是對(duì)JDBC操作的一個(gè)簡單的封裝
然后再創(chuàng)建一個(gè)writer任務(wù)
class WriterTask implements Runnable { int level = 0; public WriterTask(int level) { super(); this.level = level; } @Override public void run() { Db.tx(level, new IAtom() { @Override public boolean run() throws SQLException { AccountService service = new AccountService(); try { service.deposit(1000); System.out.println("Writer 1000."); Thread.sleep(1000); System.out.println("Writer complete."); } catch (Exception e) { e.printStackTrace(); } return false; } }); } }
然后執(zhí)行主線程
public static void main(String[] args) throws Exception { int level = Connection.TRANSACTION_READ_UNCOMMITTED; for(int j = 0; j < 10; j++) { if(j == 3) new Thread(new WriterTask(level)).start(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new ReadTask(level)).start(); } }
上訴代碼開啟ReadTask和WriterTask對(duì)balance的值進(jìn)行并發(fā)的寫入和讀取,并且WriterTask最終會(huì)回滾事務(wù)
當(dāng)隔離級(jí)別為TRANSACTION_READ_UNCOMMITTED時(shí),發(fā)現(xiàn)在WriterTask-commit事務(wù)前后讀取到的值不一樣
13:0 14:0 15:0 Writer 1000. 17:1000 18:1000 19:1000 Writer complete. 20:0 21:0 22:0 23:0
然后修改代碼的隔離級(jí)別為TRANSACTION_READ_COMMITTED,發(fā)現(xiàn)前后讀取的值一致,但是值得注意的是的,數(shù)據(jù)一致是建立在WriterTask事務(wù)回滾的情況下,如果事務(wù)正確的提交了,還是有出現(xiàn)數(shù)據(jù)不一致的問題,關(guān)于數(shù)據(jù)的一致性就不能簡單的使用事務(wù)隔離來解決了,需要lock,關(guān)于數(shù)據(jù)一致的問題不在本文章討論內(nèi)
13:0 14:0 15:0 Writer 1000. 17:0 18:0 19:0 Writer complete. 20:0 21:0 22:0 23:0不可重復(fù)讀(non-repeatable reads)
在同一個(gè)事務(wù)中,對(duì)于同一份數(shù)據(jù)讀取到的結(jié)果不一致。比如,事務(wù)B在事務(wù)A提交前讀到的結(jié)果,和提交后讀到的結(jié)果可能不同。
TEST:
事務(wù)A: update account += 1000, 然后commit
事務(wù)B: 嘗試讀取 account 的值(間隔2秒),再次嘗試讀取
為了滿足不可重復(fù)讀的測試對(duì)ReadTask作一些小改動(dòng)
class ReadTask2 implements Runnable { int level = 0; public ReadTask2(int level) { super(); this.level = level; } @Override public void run() { Db.tx(level, new IAtom() { @Override public boolean run() throws SQLException { AccountService service = new AccountService(); System.out.println(Thread.currentThread().getId() + ":" + service.audit()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + ":" + service.audit()); return true; } }); } }
在代碼中間隔2s,然后重復(fù)訪問同一個(gè)balance字段
主線程代碼
public static void main(String[] args) throws Exception { int level = Connection.TRANSACTION_REPEATABLE_READ; new Thread(new ReadTask2(level)).start(); Thread.sleep(1500); new Thread(new WriterTask2(level)).start(); Thread.sleep(1500); }
設(shè)置隔離界別為TRANSACTION_READ_UNCOMMITTED
10:17000 Writer 1000. 10:18000
設(shè)置隔離界別為TRANSACTION_REPEATABLE_READ
10:18000 Writer 1000. 10:18000
讀取到的1800是WriterTask事務(wù)未提交之前的值,假如要實(shí)時(shí)的獲取balance的最新值,WriterTask很顯然還是需要加lock,所以無可重復(fù)讀的隔離級(jí)別只是避免了在同一個(gè)事務(wù)中數(shù)據(jù)讀取的一致性,而不保證最終的數(shù)據(jù)一致性
幻讀(phantom reads)在同一個(gè)事務(wù)中,同一個(gè)查詢多次返回的結(jié)果不一致。
ReadTask和WriterTask分別進(jìn)行insert的sql與select的操作(select count(*) from account)
TEST:
事務(wù)A: insert account 然后commit
事務(wù)B: 嘗試讀取 account 的數(shù)量(間隔2秒),再次嘗試讀取
設(shè)置隔離界別為TRANSACTION_READ_COMMITTED
12:0 create account. 12:1
設(shè)置隔離界別為TRANSACTION_REPEATABLE_READ
12:1 create account. 12:1
設(shè)置隔離界別為TRANSACTION_SERIALIZABLE
12:2 create account. 12:2
關(guān)于最高級(jí)別序列化是只有當(dāng)一個(gè)事務(wù)完成后才會(huì)執(zhí)行下一個(gè)事務(wù),但是這里我測試使用TRANSACTION_REPEATABLE_READ級(jí)別是還是避免了幻讀,不知道是程序的問題還是JDBC的問題,這里我可能還需要進(jìn)一步的測試和研究,但是根據(jù)官方對(duì)TRANSACTION_REPEATABLE_READ的說明
A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented. This level includes the prohibitions in TRANSACTION_REPEATABLE_READ and further prohibits the situation where one transaction reads all rows that satisfy a WHERE condition, a second transaction inserts a row that satisfies that WHERE condition, and the first transaction rereads for the same condition, retrieving the additional "phantom" row in the second read.
表示幻讀的定義是在同一個(gè)事務(wù)中,讀取2次的值是不一樣的,因?yàn)橛衅渌聞?wù)添加了一行,并且這行數(shù)據(jù)是滿足第一個(gè)事務(wù)的where查詢條件的數(shù)據(jù)
總結(jié)本次測試使用JFinal框架(它對(duì)JDBC進(jìn)行了很簡易的封裝),使用不同的隔離級(jí)別對(duì)3種并發(fā)情況進(jìn)行測試,但是在幻讀的測試中TRANSACTION_REPEATABLE_READ級(jí)別同樣也避免了幻讀的情況,這個(gè)有待進(jìn)一步測試和研究
補(bǔ)充說明同一個(gè)事務(wù): 在JDBC編程中同一個(gè)事務(wù)意味著擁有相同的Connection,也就是說如果想保證事務(wù)的原子性所有的執(zhí)行必須使用同一個(gè)Connection,事務(wù)的代表就是Connection
commit和rollback:在JDBC編程中一旦代碼commit成功就無法rollback,所以一般rollback是發(fā)生在commit出現(xiàn)異常的情況下
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/65096.html
摘要:隔離級(jí)別個(gè)等級(jí)的事務(wù)隔離級(jí)別,在相同的數(shù)據(jù)環(huán)境下,使用相同的輸入,執(zhí)行相同的工作,根據(jù)不同的隔離級(jí)別,可以導(dǎo)致不同的結(jié)果。不同事務(wù)隔離級(jí)別能夠解決的數(shù)據(jù)并發(fā)問題的能力是不同的。 Java知識(shí)點(diǎn)總結(jié)(JDBC-事務(wù)) @(Java知識(shí)點(diǎn)總結(jié))[Java, JDBC] 事務(wù) 事務(wù)基本概念 一組要么同時(shí)執(zhí)行成功,要么同時(shí)執(zhí)行失敗的 SQL 語句。是數(shù)據(jù)庫操作的一個(gè)執(zhí)行單元! 事務(wù)開始于:...
摘要:一致性一個(gè)事務(wù)中,事務(wù)前后數(shù)據(jù)的完整性必須保持一致。持久性持久性是指一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的,接下來即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對(duì)其有任何影響。 一、事務(wù)概述1.什么是事務(wù)一件事情有n個(gè)組成單元 要不這n個(gè)組成單元同時(shí)成功 要不n個(gè)單元就同時(shí)失敗就是將n個(gè)組成單元放到一個(gè)事務(wù)中2.mysql的事務(wù)默認(rèn)的事務(wù):一條sql語句就是一個(gè)事務(wù) 默認(rèn)就開啟事務(wù)并提交事...
摘要:使用需要使用作為事務(wù)管理器。兩個(gè)事務(wù)互不影響。這是默認(rèn)的隔離級(jí)別,使用數(shù)據(jù)庫默認(rèn)的事務(wù)隔離級(jí)別下邊的四個(gè)與的隔離級(jí)別相對(duì)應(yīng)這是事務(wù)最低的隔離級(jí)別,它充許另外一個(gè)事務(wù)可以看到這個(gè)事務(wù)未提交的數(shù)據(jù)。這種事務(wù)隔離級(jí)別可 Spring事務(wù)整理 工作了幾年了,今天抽時(shí)間整理一下spring的事務(wù),說起spring的事務(wù)是面試的時(shí)候面試官經(jīng)常提及的問題,接下來結(jié)合網(wǎng)上資料再總結(jié)下spring的事務(wù)...
摘要:常見面試題操作數(shù)據(jù)庫的步驟操作數(shù)據(jù)庫的步驟注冊數(shù)據(jù)庫驅(qū)動(dòng)??梢苑乐棺⑷?,安全性高于。只有隔離級(jí)別才能防止產(chǎn)生幻讀。對(duì)象維護(hù)了一個(gè)游標(biāo),指向當(dāng)前的數(shù)據(jù)行。一共有三種對(duì)象。 以下我是歸納的JDBC知識(shí)點(diǎn)圖: showImg(https://segmentfault.com/img/remote/1460000013312769); 圖上的知識(shí)點(diǎn)都可以在我其他的文章內(nèi)找到相應(yīng)內(nèi)容。 JDBC...
閱讀 3702·2021-11-11 11:00
閱讀 2180·2021-10-08 10:05
閱讀 2671·2021-10-08 10:04
閱讀 3204·2021-09-30 09:48
閱讀 3763·2021-09-27 14:10
閱讀 1704·2021-09-09 09:33
閱讀 2100·2019-08-30 15:55
閱讀 1602·2019-08-30 13:53