事務

一、事務的簡介

  事務是指邏輯上的一組操作,組成這組操作的各個單元,要麼全部成功,要麼全部失敗。

  例如:A-->B轉賬,對應如下的兩條SQL語句。

update account set money = money - 500 where name = 'a';
update account set money = money + 500 where name = 'b'

  數據庫默認事務是自動提交的,也就是發一條SQL它就執行提交。


二、數據庫開啓事務命令

  如果想多條SQL放在一個事務中執行,則需要執行下面的命令。(以MySQL爲例)


開始事務

start transaction;

回滾事務

rollback;

提交事務

commit;


三、MySQL中使用事務

3.1 創建表

create tableaccount(
   id int primary key auto_increment,
   name varchar(20),
   money double
);

增加數據

insert into accountvalues(null,'aaa',1000);
insert intoaccount values(null,'bbb',1000);
insert intoaccount values(null,'ccc',1000);

wKiom1jVFvPx4ff0AAAUxIH1Cxk070.png

3.2 MySQL中事務是默認自動提交的,每當執行一條SQL,就會提交一個事務(一個SQL就是一個事務)。Oracle中事務是默認不自動提交的,需要在執行SQL語句後,通過commit命令手動提交事務。


3.3 MySQL管理事務

方式一:開啓事務管理SQL的語句

開啓事務命令

start transaction;

回滾事務:將數據回覆語到事務開始時候的狀態

rollback;

提交事務:對事務中的進行操作,進行確認操作,事務在提交後,數據就可恢復。

commit;

wKioL1jVGkbih01aAAInD3HLJxg123.png

方式二:數據庫中存在了一個自動變量,通過

show variables like '%commit%';

可以查看,如果autocommit的值是on,說明開啓自動提交。

wKiom1jVG0GQUEPWAAAipK4_M3c225.png

關閉自動提交,

set autocommit = off;
或
set autocommit = 0;

wKiom1jVG7nws7O_AAAqlBK8QA0423.png

如果甚至autocommit爲off,意味着以後每條SQL都會處於一個事務中,相當於每條SQL執行前,都會執行start transaction開啓事務命令。

【提示】Oracle中的autocommit默認就是off。


四、JDBC中使用事務

  當JDBC程序向數據庫獲得一個Connection對象,默認情況下,這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。如果想關閉這種默認提交方式,讓多條SQL在一個事務中執行,則可以使用下面的語句。


JDBC控制事務語句

connection.setAutoCommit(false);//相當於start transaction
connection.rollback();//相當於rollback
connection.commit();//相當於commit


五、事務的特性(ACID)

☆原子性(Atomicity)

  原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部發生,要麼全部失敗。

  【提示】:現在物理學證明,夸克是最小的單位,但是在夸克證明之前,人們普遍認爲原子是最小的單位,用原子來描述事務,很貼切。

☆一致性(Consistency)

  事務前後數據的完成性必須保持一致。

隔離性(Isolation)

  事務的隔離性是指多個用戶併發訪問數據庫時,一個用戶的事務不能被其他用戶的事務干擾,多個併發事務之間數據要相互隔離

☆持久性(Durability)

  持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。


六、事務的隔離級別

  多個線程開啓各自事務操作數據庫中數據的時候,數據庫系統要負責隔離操作,以保證各個線程在獲取數據時的準確性。


數據庫共定義了四種隔離級別:

serializable:可避免髒讀、不可重複讀、虛讀情況的發生。(串行化)

repeatable read:可避免髒讀、不可重複讀情況的發生(可重複讀) ,不可以避免虛讀。

read committed:可避免髒讀情況發生(讀已提交)

read uncommitted:最低解綁,什麼也無法保證。(讀未提交)


設置事務隔離級別

set session transaction isolation level   xxxx;

查詢當前事務隔離級別

select @@tx_isolation;


查看MySQL默認的隔離級別:

wKiom1jVJDPS_coCAAAOxWZnc7Y755.png


  如果不考慮隔離性,可能會引起如下問題:

6.1 髒讀

一個事務讀取了另一個事務未提交的數據。

這是非常危險的,假設aaa向bbb轉賬100元,對應SQL語句如下:

update account set money = money - 100 where name = 'aaa';
update account set money = money + 100 where name='bbb';

先設置事務的隔離級別爲read uncommited

set session transaction isolation level read uncommitted;

wKioL1jVJkPRFw7aAAAZeLb2rPo652.png


執行的SQL語句如下:

語句1:

update account set money = money + 100 where name = 'bbb';

語句2:

update account set money = money -100 where name = 'aaa';

當第1條SQL執行完畢,第2條還沒執行(沒提交時),如果此時B查詢自己的賬戶,就會發現自己多個100元,如果A等B走後再回滾,B就會損失100元。


6.2 不可重複讀(強調的是update)

  在一個事務內讀取表中某一行數據,多次讀取結果不同。

  

  例如銀行想查詢A賬戶餘額,第一次查詢A賬戶爲200元,此時A向賬戶存了100元並提交了。銀行接着又進行了一次查詢,此時A賬戶爲300元了。銀行兩次查詢不一致,可能就會很困惑,不知道那次查詢是準確的。

  

  和髒讀的區別是,髒讀是讀取前一事務未提交的髒數據,不可重複讀是重新讀取了前一事務已提交的數據。


  很多人認爲這種情況就是對的,無需困惑,當然是後面的爲準。我們可以考慮到如下的情況,比如銀行程序 需要將查詢結果分別輸出到電腦屏幕和寫到文件中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,導致文件和屏幕中的結果不一致,銀行工作人員就不知道以哪個爲準了。


6.3虛讀(幻讀)

  一個事務內讀取了別的事務插入的數據,導致前後讀取不一致。

  

  例如,第一次讀取,存在5條記錄,然後(另一個事務)向表中插入一條新的記錄,第二次讀取,存在6條記錄。


JDBC程序中能否指定事務的隔離級別 ?

Connection接口中定義事務隔離級別四個常量:

static int TRANSACTION_READ_COMMITTED

          指示不可以發生髒讀的常量;不可重複讀和虛讀可以發生。

static int TRANSACTION_READ_UNCOMMITTED

          指示可以發生髒讀 (dirty read)、不可重複讀和虛讀 (phantom read) 的常量。

static int TRANSACTION_REPEATABLE_READ

          指示不可以發生髒讀和不可重複讀的常量;虛讀可以發生。

static int TRANSACTION_SERIALIZABLE

          指示不可以發生髒讀、不可重複讀和虛讀的常量。

 

通過 void setTransactionIsolation(intlevel) 設置數據庫隔離級別


七、事務的丟失更新問題

  兩個事務或多個事務更新同一行,但這些事務彼此之間都不知道其他事務進行的修改,因此第二個更改會覆蓋了一個修改。

wKiom1jVK2qSByFaAAB2M5L7Jes477.png

丟失更新問題的解決:

悲觀鎖(假設丟失更新一定會發生)--利用數據庫內部鎖機制,管理事務


MySQL數據庫內部提供兩種常用的鎖機制:共享鎖和排它鎖。

允許一張數據表中數據記錄,添加多個共享鎖,添加共享鎖記錄,對於其他事務可讀不可寫。

一張數據表中數據記錄,只能添加一個排它鎖,在添加排它鎖的數據,不能再添加劑其他共享鎖和排它鎖,對於其他事務是可讀不可寫的。


所有數據記錄修改操作,自動爲數據添加排它鎖。

添加共享鎖方式:select * from account lock in share mode;

添加排它鎖方式:select * from account for update;


鎖必須在事務中添加,如果事務結束了,鎖就釋放了。


樂觀鎖(假設丟失更新不一定會發生)--採用記錄的版本子彈,來判斷記錄是否修改過--timestamp

timestamp是可以自動更新的。

create table product(
    id int,
    name varchar(20),
    updatetime timestamp
);
insert into product values(1,'哈哈',null);
update product set name='呵呵' where id = 1;

timestamp在插入和修改的時候,都會自動更新爲當前時間。


解決丟失更新,在數據表中添加版本字段,每次修改記錄後,版本字段都會更新,如果讀取的是版本字段和修改時的版本字段不一致,說明別人進行修改過數據。






 










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章