事務和鎖
事務
transaction,一組原子性的SQL查詢,或者說是一個獨立的工作單元。如果能夠成功執行這組查詢的全部語句,就會執行這組查詢;如果其中任何一條語句無法成功執行,那么這組查詢的所有語句都不會執行。
也就是說,事務內部的語句,要么全部執行成功,要么全部執行失敗。
使用事務
- 開始標志:任何一條DML語句的執行
- 結束標志:
- 提交:成功的結束,將所有的DML語句操作記錄和底層硬盤文件中數據進行同步
- 回滾:失敗的結束:將所有DML語句操作記錄全部清除
MySQL默認是自動提交
#開啟事務
start transaction;
#提交事務
commit;
#保存回滾點
savepoint 回滾點名;
#回滾事務
rollback[to 回滾點名];#查看事務的提交方式
show variables like 'autocommit';
#啟用和關閉自動提交模式
set autocommit = 1;
set autocommit = 0;
舉例:轉賬業務
- 創建表并插入數據
drop table if exists account;
create table account(id int primary key auto_increment,name varchar(20),money int check(money >= 0)
);
insert into account(name,money
)
values
('張三',20000),
('李四',500);
- 自動提交和手動提交,再開一個MySQL—Front窗口查看數據變化
事務的四個特性(ACID)
ACID:
- A(原子性 atomicity):事物是最小工作單元,不可再分,一個事務的所有操作要么全部執行成功,要么執行失敗全部回滾,不會出現成功一部分的情況
- C(一致性 consisyency):數據庫總會從一個一致的狀態轉變成另一個一致的狀態,不會因為一條語句的失敗而出現另外的狀態,打個比方事物執行成功前的狀態是 0 ,執行成功后的狀態是 1,不會因為某一條語句的失敗而出現 0.5的狀態。
- I(隔離性 isolation):通常來說,一個事物所做的修改在最終提交前,對于其他事物是不可見的,即在最終提交前該事務的修改不會影響到其他事務。
- D(持久性 durability):事務一旦提交,其做的修改會持久的保存到數據庫中,即使系統崩潰,修改的數據也不會丟失。
一個兼容ACID的數據庫系統很多復雜但可能用戶并沒有察覺到的工作.相比沒有實現ACID的數據庫,通常回需要更強的CPU處理能力,更大的內存和更多的磁盤空間。
持久性原理:
一般事務的持久性都是借助redo log來實現的。
Redo log(重做日志)是一種日志文件,記錄了對于數據庫的修改操作(包括插入,修改刪除等等操作),它的主要作用就是確保系統或數據庫崩潰之后,系統能夠通過重做日志上記錄的操作,從而是數據庫回到事務提交后的狀態,確保事務的持久性。
并行事務帶來的問題
數據一致性問題:
- 臟讀(dirty read):一個事務讀到另一個事務未提交的數據就是臟讀,因為一個事務對于一條記錄做修改,在該事務未提交前所做的修改隨時有可能回滾,因此被另一個事務讀到并回滾的數據就是臟數據。
- 不可重復讀(non-repeatable read):在一個事務內多次讀取同一條記錄,前后結果不一致就是不可重復讀,比如事務A多次查詢同一行數據,在某次查詢間隔中,事務B對該行數據進行了修改并提交,導致事務A對該行數據的下次讀取出現了不一致,這種情況就叫不可重復讀。
- 幻讀(phantom read):在一個事務內按照相同的條件多次查詢,前后結果集的數量不同就是幻讀,比如事務A按照id>5的條件進行多次查詢,出現幻讀前的查詢結果一直是count(*)=3,在某次查詢間隔中,事務B插入了一條id=9的數據并提交,那么在修改后事務A查詢到的結果就會變成count(*)=4,這就是幻讀問題。
注意:不可重復讀主要針對數據本身,幻讀則針對的是查詢讀取的結果集數量。
影響:
- 臟讀:讀到其他事務未提交的數據
- 不可重復讀:前后讀取的數據不一致
- 幻讀:前后讀取的結果集數量不同
隔離級別
在SQL標準中定義了四個隔離級別,每一種級別都規定了一個事務中所做的修改,哪些事物內和事務間是可見的,哪些是不可見的。較低級別的隔離通常可以執行更高的并發,系統的開銷也更低。
- 未提交讀(READ UNCOMMITED):事務中未提交的修改也可以被其他事務讀取到,這個隔離等級會出現包括臟讀,不可重復讀,幻讀問題,一般不用。
- 提交讀(READ COMMITED):事務中的修改在提交之后才會被其它事務讀取到,這樣就避免了臟讀問題,但是還是會出現不可重復讀和幻讀問題。
- 可重復讀(REPEATABLE READ):同一事物中多次讀取同樣記錄的結果是一致的,就解決的臟讀和不可重復讀的問題,但是幻讀問題還是未得到完全的解決,值得注意的一點是隔離級別是MySQL的默認隔離級別,又因為MySQL的特質該隔離級別可以解決大部分幻讀問題。
- 可串行化(SERIALIZABLE):強制事務串行執行,不會出現并行事務,所以就解決了臟讀,不可重復讀,幻讀的問題,該隔離等級會讀取的行上添加行鎖以及隔離鎖以避免幻讀問題,同時對于讀取操作加入共享鎖,寫入操作加入排他鎖,值得一提的串行化雖然能夠避免所有有關一致性的問題,但是因為它并發性能低,容易死鎖,效率低,在一般項目中很少用,更多的還是使用可重復讀或提交讀。
查看隔離級別:
#查看會話級的當前隔離級別:(會話級只對當前窗口有效)
select @@tx_isolation
select @@session.tx_isolation
#查看全局級的當前隔離級別:
select @@global.tx_isolation
設置隔離級別:
- 方法1:my.ini文件中的[mysqld]下面添加transaction-isolation =隔離級別
- 方法2:通過指令
#設置全局或當前會話范圍
set global transaction isolation level 隔離級別;
set session transaction isolation level 隔離級別;
實現隔離級別的兩種方法:
- 加鎖:讀數據前對其,阻止其他事務對數據修改
- 快照:讀取數據的快照(之前的版本)實現,例如,可重復讀級別在整個事務期間都讀取事務開始時的快照去解決不可重復讀問題。
InnoDB中的鎖
共享鎖和排他鎖
- 共享鎖(S Lock):讀鎖,允許事務讀取數據
- 排他鎖(X Lock):也叫獨占鎖,寫鎖,允許事務刪除或更新數據
如果事務獲取了某個數據的共享鎖,其他事務可以立即獲取該數據的共享鎖,這種情況叫鎖兼容。如果事務獲取了某個數據的共享鎖或排他鎖,其他事務想要獲取該數據的排他鎖,必須要等到該行的鎖被釋放掉。
排他鎖和共享鎖的兼容性
意向鎖
InnoDB允許事務在行鎖和表鎖同時存在。為支持在不同粒度上進行加鎖,InnoDB支持意向鎖。
意向鎖,將鎖定的對象分為多個層級,意向鎖意味著事務有意向在更細粒度上加鎖。如果需要加鎖,需要先對表意向鎖再對加行鎖。
意向鎖在InnoDB中就是表級別的鎖,支持兩種意向鎖:
- 意向共享鎖(IS Lock),事務有意向對表中某些行加共享鎖
- 意向排他鎖(IX Lock),事務有意向對表中某些行加排他鎖
表級鎖的兼容性
注意:意向鎖不會和行級鎖沖突,意向鎖之間也不會沖突,意向鎖只會和共享表鎖和排他表鎖沖突
意向鎖的作用:如果沒有意向鎖,想要給一個表加表鎖必須要檢查該表是否有表鎖和每一行是否有鎖。而如果在加行鎖前給這個表加上了意向鎖,這時只需要檢查表鎖和意向鎖就可以了,不需要檢查每一行鎖。
一致性非鎖定讀
一致性非鎖定讀,讀取正在執行delete和update操作的行時,不會等待該行上鎖的釋放,而是讀取該行的一個快照數據(該行之前的版本)。非鎖定讀極大的提高了數據庫的并發性,InnoDB默認時這種讀取方式,也就是說默認普通的select語句不會加鎖而是通過讀取快照實現數據一致性。
上面提到的快照數據就是該行數據的歷史版本,由此帶來的并發控制稱為多版本并發控制(MVCC)。
READ COMMITTED(提交讀)和REPEATABLE READ(可重復讀)在InnoDB中使用的是一致性非鎖定讀,但是讀取的快照不同。提交讀級別讀取的是最新版本的快照,可重復讀級別讀取的是事務開始時數據的快照。
可重復讀級別通過讀快照,可以解決前面提到的幻讀問題,但是有些情況需要鎖定讀。
一致性鎖定讀
InnoDB默認使用一致性非鎖定讀。某些情況用戶需要顯示加鎖保證數據的一致性,支持兩種一致性鎖定讀的操作。
- select ... for update:對讀取的行加一個排他鎖
- select ... lock in share mode:對讀取的行加一個共享鎖
如果只對讀取的行加鎖會有幻讀問題:
鎖定讀時使用鍵值間隙鎖(Next-Key Lock),就是行鎖加間歇鎖,來解決幻讀問題。
- Record Lock:單行記錄的鎖,比如只鎖3這行記錄
- Gap Lock:間歇鎖,鎖定一個范圍,但不包括記錄本身,就像這樣只鎖(1-2),(4-5)這個范圍的記錄
- Next-Key Lock:Record Lock+Gap Lock,鎖定一個范圍,并鎖定記錄本身,兩個鎖一相加就成了鎖定(1-5)中所有的記錄
非鎖定讀使用快照和鎖定讀使用間歇鎖可以基本解決幻讀問題,但是極特殊情況還是有可能發生幻讀。