一、事務的簡介
事務是指邏輯上的一組操作,組成這組操作的各個單元,要麼全部成功,要麼全部失敗。
例如: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);
3.2 MySQL中事務是默認自動提交的,每當執行一條SQL,就會提交一個事務(一個SQL就是一個事務)。Oracle中事務是默認不自動提交的,需要在執行SQL語句後,通過commit命令手動提交事務。
3.3 MySQL管理事務
方式一:開啓事務管理SQL的語句
開啓事務命令
start transaction;
回滾事務:將數據回覆語到事務開始時候的狀態
rollback;
提交事務:對事務中的進行操作,進行確認操作,事務在提交後,數據就可恢復。
commit;
方式二:數據庫中存在了一個自動變量,通過
show variables like '%commit%';
可以查看,如果autocommit的值是on,說明開啓自動提交。
關閉自動提交,
set autocommit = off; 或 set autocommit = 0;
如果甚至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默認的隔離級別:
如果不考慮隔離性,可能會引起如下問題:
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;
執行的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) 設置數據庫隔離級別
七、事務的丟失更新問題
兩個事務或多個事務更新同一行,但這些事務彼此之間都不知道其他事務進行的修改,因此第二個更改會覆蓋了一個修改。
丟失更新問題的解決:
悲觀鎖(假設丟失更新一定會發生)--利用數據庫內部鎖機制,管理事務
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在插入和修改的時候,都會自動更新爲當前時間。
解決丟失更新,在數據表中添加版本字段,每次修改記錄後,版本字段都會更新,如果讀取的是版本字段和修改時的版本字段不一致,說明別人進行修改過數據。