mysql 中的脏读与幻读_一文带你理解脏读,幻读,不可重复读与mysql的锁,事务隔离机制...
首先說一下數(shù)據(jù)庫事務的四大特性
1 ACID
事務的四大特性是ACID(不是"酸"....)
(1) A:原子性(Atomicity)
原子性指的是事務要么完全執(zhí)行,要么完全不執(zhí)行.
(2) C:一致性(Consistency)
事務完成時,數(shù)據(jù)必須處于一致的狀態(tài).若事務執(zhí)行途中出錯,會回滾到之前的事務沒有執(zhí)行前的狀態(tài),這樣數(shù)據(jù)就處于一致的狀態(tài).若事務出錯后沒有回滾,部分修改的內(nèi)容寫入到了數(shù)據(jù)庫中,這時數(shù)據(jù)就是不一致的狀態(tài).
(3) I:隔離性(Isolation)
同時處理多個事務時,一個事務的執(zhí)行不能被另一個事務所干擾,事務的內(nèi)部操作與其他并發(fā)事務隔離.
(4) D:持久性(Durability)
事務提交后,對數(shù)據(jù)的修改是永久性的.
2 Mysql的鎖
Mysql的鎖其實可以按很多種形式分類:
按加鎖機制分,可分為樂觀鎖與悲觀鎖.
按兼容性來分,可分為X鎖與S鎖.
按鎖粒度分,可分為表鎖,行鎖,頁鎖.
按鎖模式分,可分為記錄鎖,gap鎖,next-key鎖,意向鎖,插入意向鎖.
這里主要討論S鎖,X鎖,樂觀鎖與悲觀鎖.
(1) S鎖與X鎖
S鎖與X鎖是InnoDB引擎實現(xiàn)的兩種標準行鎖機制.查看默認引擎可使用
show variables like '%storage_engine%';
作者的mysql版本為8.0.17,結果如下:
先建好測試庫與測試表,很簡單,表就兩個字段.
create database test;
use test;
create table a
(
id int primary key auto_increment,
money int
);
Ⅰ.S鎖
S鎖也叫共享鎖,讀鎖,數(shù)據(jù)只能被讀取不能被修改.
lock table a read;
然后.....
只能讀不能改,刪,也不能增.
Ⅱ.X鎖
X鎖也叫排他鎖,寫鎖,一個事務對表加鎖后,其他事務就不能對其進行加鎖與增刪查改操作.
設置手動提交,開啟事務,上X鎖.
set autocmmmit=0;
start transaction;
lock table a write;
在開啟另一個事務,使用select語句.
set autocommit=0;
start transaction;
select * from a;
這里是阻塞select操作,因為一直都沒釋放X鎖.
同樣也不能再加鎖,也是阻塞中.
回到原來那個加鎖的事務,嗯,什么事也沒有,正常讀寫.
釋放鎖后:
unlock table;
在另一個事務中可以看到中斷時間.
(2) 樂觀鎖與悲觀鎖
Ⅰ.樂觀鎖
樂觀鎖就是總是假設是最好的情況,每次去操作的時候都不會上鎖,但在更新時會判斷有沒有其他操作去更新這個數(shù)據(jù),是一種寬松的加鎖機制.
mysql本身沒有提供樂觀鎖的支持,需要自己來實現(xiàn),常用的方法有版本控制和時間戳控制兩種.
版本控制
版本控制就是為表增加一個version字段,讀取數(shù)據(jù)時連同這個version字段一起讀出來,之后進行更新操作,版本號加1,再將提交的數(shù)據(jù)的版本號與數(shù)據(jù)庫中的版本號進行比較,若提交的數(shù)據(jù)的版本號大于數(shù)據(jù)庫中的版本號才會進行更新.
舉個例子,假設此時version=1,A進行操作,更新數(shù)據(jù)后version=2,與此同時B也進行操作,更新數(shù)據(jù)后version=2,A先完成操作,率先將數(shù)據(jù)庫中的version設置為2,此時B提交,B的version與數(shù)據(jù)庫中的version一樣,不接受B的提交.
時間戳控制
時間戳控制與版本控制差不多,把version字段改為timestamp字段
還有一種實現(xiàn)方法叫CAS算法,這個作者不怎么了解,有興趣可以自行搜索.
Ⅱ.悲觀鎖
悲觀鎖就是總是假設最壞的情況,在整個數(shù)據(jù)處理狀態(tài)中數(shù)據(jù)處于鎖定狀態(tài),悲觀鎖的實現(xiàn)往往依靠數(shù)據(jù)庫的鎖機制.每次在拿到數(shù)據(jù)前都會上鎖.
mysql在調(diào)用一些語句時會上悲觀鎖,如(先關閉自動提交,開啟事務):
set autocommit=0;
start transaction;
兩個事務都這樣操作,然后其中一個事務輸入:
select * from a where xxx for update;
在另一事務也這樣輸入:
這時語句會被阻塞,直到上鎖的那個事務commit(解開悲觀鎖).
在另一事務中可以看到這個事務被阻塞了2.81s.
*** lock in share mode.
也會加上悲觀鎖.
4 臟讀,幻讀,不可重復讀與兩類丟失更新
(1) 臟讀
臟讀是指一個事務讀取到了另一事務未提交的數(shù)據(jù),造成select前后數(shù)據(jù)不一致.
比如事務A修改了一些數(shù)據(jù),但沒有提交,此時事務B卻讀取了,這時事務B就形成了臟讀,一般事務A的后續(xù)操作是回滾,事務B讀取到了臨時數(shù)值.
事務A
事務B
開始事務
開始事務
更新X,舊值X=1,新值X=2
讀取X,X=2(臟讀)
回滾X=1
結束事務(X=1)
結束事務
(2) 幻讀
幻讀是指并不是指同一個事務執(zhí)行兩次相同的select語句得到的結果不同,
而是指select時不存在某記錄,但準備插入時發(fā)現(xiàn)此記錄已存在,無法插入,這就產(chǎn)生了幻讀.
事務A
事務B
開始事務
開始事務
select某個數(shù)據(jù)為空,準備插入一個新數(shù)據(jù)
插入一個新數(shù)據(jù)
提交,結束事務
插入數(shù)據(jù),發(fā)現(xiàn)插入失敗,由于事務B已插入相同數(shù)據(jù)
結束事務
(3) 不可重復讀
不可重復讀指一個事務讀取到了另一事務已提交的數(shù)據(jù),造成select前后數(shù)據(jù)不一致.
比如事務A修改了一些數(shù)據(jù)并且提交了,此時事務B卻讀取了,這時事務B就形成了不可重復讀.
事務A
事務B
開始事務
開始事務
讀取X=1
讀取X=1
更新X=2
提交,結束事務
讀取X=2
結束事務
(4) 第一類丟失更新
第一類丟失更新就是兩個事務同時更新一個數(shù)據(jù),一個事務更新完畢并提交后,另一個事務回滾,造成提交的更新丟失.
事務A
事務B
開始事務
開始事務
讀取X=1
讀取X=1
修改X=2
修改X=3
提交,結束事務
回滾
結束事務(X=1)
X=1,X本應為提交的3
(5) 第二類丟失更新
第二類丟失更新就是兩個事務同時更新一個數(shù)據(jù),先更新的事務提交的數(shù)據(jù)會被后更新的事務提交的數(shù)據(jù)覆蓋,即先更新的事務提交的數(shù)據(jù)丟失.
事務A
事務B
開始事務
開始事務
讀取X=1
讀取X=1
更新X=2
提交事務,X=2,結束
更新X=3
提交事務,X=3,事務A的更新丟失,結束
5 封鎖協(xié)議與隔離級別
封鎖協(xié)議就是在用X鎖或S鎖時制定的一些規(guī)則,比如鎖的持續(xù)時間,鎖的加鎖時間等.不同的封鎖協(xié)議對應不同的隔離級別.事務的隔離級別一共有4種,由低到高分別是Read uncommitted,Read committed,Repeatable read,Serializable,分別對應的相應的封鎖協(xié)議等級.
(1) 一級封鎖協(xié)議
一級封鎖協(xié)議對應的是Read uncommitted隔離級別,Read uncommitted,讀未提交,一個事務可以讀取另一個事務未提交的數(shù)據(jù),這是最低的級別.一級封鎖協(xié)議本質上是在事務修改數(shù)據(jù)之前加上X鎖,直到事務結束后才釋放,事務結束包括正常結束(commit)與非正常結束(rollback).
一級封鎖協(xié)議不會造成更新丟失,但可能引發(fā)臟讀,幻讀,不可重復讀.
設置手動提交與事務隔離等級為read uncommited,并開啟事務(注意要先設置事務等級再開啟事務).
set autocommit=0;
set session transaction isolation level read uncommitted;
start transaction;
(中間有一行打多了一個t可以忽略.....)
a.引發(fā)臟讀
在一個事務中修改表中的值,不提交,另一個事務可以select到未提交的值.
出現(xiàn)了臟讀.
b.引發(fā)幻讀
在一個事務中插入一條數(shù)據(jù),提交.
另一事務中select時沒有,準備insert,但是insert時卻提示已經(jīng)存在.引發(fā)幻讀.
c.引發(fā)不可重復讀
未操作提交前:
另一事務修改并提交:
再次讀:
引發(fā)不可重復讀.
(2) 二級封鎖協(xié)議
二級封鎖協(xié)議本質上在一級協(xié)議的基礎上(在修改數(shù)據(jù)時加X鎖),在讀數(shù)據(jù)時加上S鎖,讀完后立即釋放S鎖,可以避免臟讀.但有可能出現(xiàn)不可重復讀與幻讀.二級封鎖協(xié)議對應的是Read committed與Repeatable Read隔離級別.
先設置隔離等級
set session transaction isolation level read committed;
Ⅰ.Read committed
Read committed,讀提交,讀提交可以避免臟讀,但可能出現(xiàn)幻讀與不可重復讀.
a.避免臟讀
開啟一個事務并更新值,在這個事務中money=100(更新后)
另一事務中money為未更新前的值,這就避免了臟讀.
注意,事實上臟讀在read committed隔離級別下是不被允許的,但是mysql不會阻塞查詢,而是返回未修改之前數(shù)據(jù)的備份,這種機制叫MVCC機制(多版本并發(fā)控制).
b.引發(fā)幻讀
在一個事務中插入數(shù)據(jù)并提交.
另一事務中不能插入"不存在"的數(shù)據(jù),出現(xiàn)幻讀.
c.引發(fā)不可重復讀
事務修改并提交前:
事務修改并提交:
出現(xiàn)不可重復讀.
Ⅱ.Repeatable read
Repeatable read比Read committed嚴格一點,是Mysql的默認級別,讀取過程更多地受到MVCC影響,可防止不可重復讀與臟讀,但仍有可能出現(xiàn)幻讀.
a.避免臟讀
在一個事務中修改數(shù)據(jù),不提交.
另一事務中兩次select的結果都不變,沒有出現(xiàn)臟讀.
b.避免不可重復讀
一個事務修改數(shù)據(jù)并提交.
另一事務中select的結果沒有發(fā)生改變,即沒有出現(xiàn)不可重復讀.
c.引發(fā)幻讀
同理,一個事務插入一條數(shù)據(jù)并提交.
另一個事務插入時出現(xiàn)幻讀.
(3) 三級封鎖協(xié)議
三級封鎖協(xié)議,在一級封鎖協(xié)議的基礎上(修改時加X鎖),讀數(shù)據(jù)時加上S鎖(與二級類似),但是直到事務結束后才釋放S鎖,可以避免幻讀,臟讀與不可重復讀.三級封鎖協(xié)議對應的隔離級別是Serializable.
先設置Serializable隔離級別
set session transaction isolation level serializable
a.避免臟讀
設置事務隔離等級后開啟事務并update,發(fā)現(xiàn)堵塞.從而避免了臟讀.
b.避免幻讀
插入時直接阻塞,避免了幻讀.
c.避免不可重復讀
在臟讀的例子中可以知道,update會被堵塞,都不能提交事務,因此也避免了不可重復讀.
6 兩段鎖協(xié)議
事務必須分為兩個階段對數(shù)據(jù)進行加鎖與解鎖,兩端鎖協(xié)議叫2PL(不是2PC),所有的加鎖都在解鎖之前進行.
(1) 加鎖
加鎖會在更新或者
select *** for update
*** lock in share mode
時進行
(2) 解鎖
解鎖在事務結束時進行,事務結束包括rollback與commit.
7.最后
這是作者的CSDN地址
最后,以下是作者的公眾號,一起學習,一起成長.
總結
以上是生活随笔為你收集整理的mysql 中的脏读与幻读_一文带你理解脏读,幻读,不可重复读与mysql的锁,事务隔离机制...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 找出两列数据的差集_excel快速查找数
- 下一篇: mysql直连1.执行语句_MySQL随