深入理解Mysql事務(wù)隔離級(jí)別與鎖機(jī)制問(wèn)題
概述
數(shù)據(jù)庫(kù)一般都會(huì)并發(fā)執(zhí)行多個(gè)事務(wù),多個(gè)事務(wù)可能會(huì)并發(fā)的對(duì)相同的一批數(shù)據(jù)進(jìn)行增刪改查操作,可能導(dǎo)致臟讀、臟寫、不可重復(fù)度和幻讀。這些問(wèn)題的本質(zhì)都是數(shù)據(jù)庫(kù)的多事務(wù)并發(fā)問(wèn)題,為了解決事務(wù)并發(fā)問(wèn)題,數(shù)據(jù)庫(kù)設(shè)計(jì)了事務(wù)隔離機(jī)制、鎖機(jī)制、MVCC多版本并發(fā)控制隔離機(jī)制,用一整套機(jī)制來(lái)解決多事務(wù)并發(fā)問(wèn)題。
事務(wù)及其ACID屬性
原子性:操作的不可分割;
一致性:數(shù)據(jù)的一致性;
隔離性:事務(wù)之間互不干擾;
持久性:數(shù)據(jù)的修改時(shí)永久的;
并發(fā)事務(wù)處理帶來(lái)的問(wèn)題
臟寫:丟失更新,最后的更新覆蓋了由其他事務(wù)所做的更新;
臟讀:事務(wù)A讀取到了事務(wù)B已經(jīng)修改但未提交的數(shù)據(jù);
不可重復(fù)讀:事務(wù)內(nèi)部相同的查詢?cè)诓煌瑫r(shí)刻結(jié)果不一樣,針對(duì)的是數(shù)據(jù)的更新、刪除操作;
幻讀:事務(wù)A讀取到了其后開(kāi)始的事務(wù)B提交的新增數(shù)據(jù);針對(duì)的是數(shù)據(jù)的插入;
事務(wù)隔離級(jí)別
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
讀未提交 | √ | √ | √ |
讀已提交 | × | √ | √ |
可重復(fù)讀 | × | × | √ |
可串行化 | × | × | × |
READ-UNCONMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE
查看當(dāng)前數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別:
show variables like 'tx_isolation'
設(shè)置事務(wù)隔離級(jí)別:
set tx_isolation='REPEATABLE-READ';
MySQL默認(rèn)的事務(wù)隔離級(jí)別是可重復(fù)讀,用Spring開(kāi)發(fā)程序時(shí),如果不設(shè)置隔離級(jí)別默認(rèn)用MySQL設(shè)置的隔離級(jí)別,如果Spring設(shè)置了就用已設(shè)置的隔離級(jí)別;
鎖詳解
鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問(wèn)某一資源的機(jī)制。
鎖分類
從性能上分為:樂(lè)觀鎖(用版本對(duì)比來(lái)實(shí)現(xiàn))和悲觀鎖;
從對(duì)數(shù)據(jù)庫(kù)操作類型分:讀寫和寫鎖(悲觀鎖);
讀鎖(共享鎖,S鎖(Shared)):針對(duì)同一份數(shù)據(jù),多個(gè)讀操作可以同時(shí)進(jìn)行而不會(huì)互相影響;
寫鎖(排它鎖,X鎖(exclusive)):當(dāng)前寫操作沒(méi)有完成前,阻斷其他寫鎖和讀鎖;
從對(duì)數(shù)據(jù)庫(kù)操作的粒度分:表鎖和行鎖
表鎖:每次操作鎖住整張表,開(kāi)銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低;一般用在整表數(shù)據(jù)遷移的場(chǎng)景。
# 手動(dòng)增加表鎖 lock table 表名稱 read(write),表名稱2 read(write); # 查看表上加過(guò)的鎖 show open tables; # 刪除表鎖 unlock tables;
行鎖:每次操作鎖住一行數(shù)據(jù)。開(kāi)銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度最高。
InnoDB與MYISAM的最大不同點(diǎn):1、InnoDB支持事務(wù);2、InnoDB支持行級(jí)鎖。
總結(jié):
MyISAM在執(zhí)行查詢語(yǔ)句前,會(huì)自動(dòng)給涉及的表加讀鎖;執(zhí)行update、insert、delete操作加寫鎖;
InnoDB在執(zhí)行查詢語(yǔ)句前(非串行隔離級(jí)別),不會(huì)加鎖;執(zhí)行update、insert、delete操作會(huì)加行鎖。
讀鎖會(huì)阻塞寫,但不會(huì)阻塞讀。而寫鎖會(huì)把讀寫都阻塞。
行鎖與事務(wù)隔離級(jí)別案例分析
mysql準(zhǔn)備一張表
1、臟讀,事務(wù)A讀取到另外一個(gè)事務(wù)已修改但未提交的數(shù)據(jù),此種情形簡(jiǎn)單,不具體闡述。對(duì)應(yīng)的事務(wù)隔離級(jí)別:read uncommitted(讀未提交)。
2、不可重復(fù)讀,對(duì)應(yīng)的事務(wù)隔離級(jí)別:read committed(讀已提交)
事務(wù)A:
set session transaction isolation level read committed; start transaction; select * from t_user;
事務(wù)B:
set session transaction isolation level read committed; start transaction; -- insert into t_user values (1,'張',8); update t_user set age = 9 where id = 1; commit;
事務(wù)A第一次執(zhí)行到查詢語(yǔ)句,結(jié)果如下:
此時(shí),事務(wù)B執(zhí)行完畢,事務(wù)A還未結(jié)束,繼續(xù)執(zhí)行一次查詢,結(jié)果如下:
產(chǎn)生了不可重復(fù)讀的問(wèn)題,一個(gè)事務(wù)內(nèi)前后兩次查詢的數(shù)據(jù)結(jié)果不一致,讀取到了其他事務(wù)已經(jīng)提交的數(shù)據(jù)。
3、可重復(fù)讀,設(shè)置事務(wù)隔離級(jí)別為repeatable read(可重復(fù)讀);
事務(wù)A第一次執(zhí)行結(jié)果如下:
事務(wù)B執(zhí)行,修改操作,update age=8并提交,結(jié)果對(duì)比如下
左邊為事務(wù)A,查詢結(jié)果與開(kāi)始時(shí)一樣,解決了不可重復(fù)讀的問(wèn)題;直接查詢,此時(shí)age=8;
可重復(fù)讀的隔離級(jí)別下使用了MVCC(multi-version concurrency control)機(jī)制,select操作不會(huì)更新版本號(hào),是快照讀(歷史版本);insert、update和delete會(huì)更新版本號(hào),是當(dāng)前讀(當(dāng)前版本)。
4、幻讀,在3中,新增一條數(shù)據(jù),如下
此時(shí)事務(wù)A再次查詢,結(jié)果如下:
結(jié)果依然和開(kāi)始的一樣,此種場(chǎng)景,可重復(fù)讀隔離級(jí)別有效的防止了不可重復(fù)讀和幻讀的問(wèn)題;
如果,事務(wù)A,在第一次查詢后,執(zhí)行不加條件的update,這個(gè)update會(huì)作用在所有的行上面,包括事務(wù)B新增加的數(shù)據(jù)。此時(shí),再執(zhí)行查詢,結(jié)果如下:
出現(xiàn)了幻讀,Mysql官方給出的幻讀解釋是:只要在一個(gè)事務(wù)中,第二次select多出了row計(jì)算幻讀。
5、串行化,serializable,InnoDB的查詢也會(huì)被加上行鎖。如果查詢的是一個(gè)范圍,那么該范圍內(nèi)的所有記錄行包括每行記錄所在的間隙區(qū)間范圍都會(huì)被加鎖,即使該行數(shù)據(jù)還沒(méi)有被插入。
間隙鎖(Gap Lock)
session_1執(zhí)行update t_user set name ='哈哈' where id>8 and id<18;則其他session無(wú)法在這個(gè)范圍包含的所有行記錄以及行記錄所在的間隙里插入或修改任何數(shù)據(jù)
間隙鎖在可重復(fù)讀隔離級(jí)別下才會(huì)生效
臨鍵鎖(Next-key Locks)
Next-key Locks是行鎖與間隙鎖的組合。在間隙鎖(8,18)這個(gè)范圍,實(shí)際會(huì)找到存在的值,比如id距離這個(gè)區(qū)間最近的是,3,20;則實(shí)際在(3,20]這個(gè)范圍都處在行鎖范圍內(nèi)。
無(wú)索引行鎖會(huì)升級(jí)為表鎖
鎖主要是加在索引上,如果對(duì)非索引字段更新,行鎖可能會(huì)變表鎖。
InnoDB的行鎖是針對(duì)索引加的鎖,不是針對(duì)記錄加的鎖。并且該索引不能失效,否則都會(huì)從行鎖升級(jí)為表鎖
鎖定某一行還可以用lock in share mode(共享鎖)和for update(排它鎖)
結(jié)論:
Innodb存儲(chǔ)引擎由于實(shí)現(xiàn)了行級(jí)鎖定,雖然在鎖定機(jī)制的實(shí)現(xiàn)方面所帶來(lái)的性能損耗可能比表級(jí)鎖定會(huì)更高,但是在整體并發(fā)處理能力方面要遠(yuǎn)遠(yuǎn)優(yōu)于MYISAM的表級(jí)鎖定。
但是,Innodb的行級(jí)鎖定同樣有其脆弱的一面,如使用不當(dāng),可能會(huì)讓整體的性能更差。
行鎖分析
通過(guò)檢查InnoDB_row_lock狀態(tài)變量來(lái)分析系統(tǒng)上的行鎖的爭(zhēng)奪情況
show status like 'innodb_row_lock%';
比較重要的主要有:
Innodb_row_lock_time_avg(等待平均時(shí)長(zhǎng))
Innodb_row_lock_waits(等待總次數(shù))
Innodb_row_lock_time(等待總時(shí)長(zhǎng))
當(dāng)?shù)却螖?shù)很高,且每次等待時(shí)長(zhǎng)也不小的時(shí)候,就需要分析系統(tǒng)中為什么會(huì)有如此多的等待,根據(jù)分析結(jié)果制定優(yōu)化計(jì)劃。
死鎖
set session transaction isolation level repeatable read; start transaction; select * from t_user where id = 2 for update; select * from t_user where id = 1 for update;
事務(wù)A先鎖定id=1,再鎖定id=2;事務(wù)B順序相反,出現(xiàn)死鎖,結(jié)果如下:
大多數(shù)情況Mysql可以自動(dòng)檢測(cè)死鎖并回滾產(chǎn)生死鎖的那個(gè)事務(wù),但有些情況無(wú)招。
查看近期死鎖日志信息:
show engine innodb status\G;
鎖優(yōu)化建議:
1、盡可能讓所有數(shù)據(jù)檢索都通過(guò)索引來(lái)完成,避免無(wú)索引行鎖升級(jí)為表鎖;
2、合理設(shè)計(jì)索引,盡量縮小鎖的范圍;
3、盡可能減少索引條件范圍,避免間隙鎖;
4、盡量控制事務(wù)大小,減少鎖定資源量和時(shí)間長(zhǎng)度,涉及事務(wù)加鎖的sql盡量放在事務(wù)最后執(zhí)行;
5、盡可能低級(jí)別事務(wù)隔離
到此這篇關(guān)于深入理解Mysql事務(wù)隔離級(jí)別與鎖機(jī)制的文章就介紹到這了,更多相關(guān)Mysql事務(wù)隔離級(jí)別與鎖機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql格式化小數(shù)保留小數(shù)點(diǎn)后兩位(小數(shù)點(diǎn)格式化)
今天遇到一個(gè)問(wèn)題,格式化浮點(diǎn)數(shù)的問(wèn)題,用format(col,2)保留兩位小數(shù)點(diǎn),出現(xiàn)一個(gè)問(wèn)題,例如下面的語(yǔ)句,后面我們給出解決方法2013-12-12MySQL 修改數(shù)據(jù)庫(kù)名稱的一個(gè)新奇方法
這篇文章主要介紹了MySQL 修改數(shù)據(jù)庫(kù)名稱的一個(gè)新奇方法,MySQL 修改數(shù)據(jù)庫(kù)名的一個(gè)變通方法,需要的朋友可以參考下2014-07-07MySQL的源碼安裝及使用UDFs進(jìn)行數(shù)據(jù)自動(dòng)更新的教程
UDFs即是MySQL的用戶自定義函數(shù)的縮寫,配合觸發(fā)器可以自動(dòng)更新Memcached與MySql的數(shù)據(jù),這里我們就來(lái)總結(jié)一下MySQL的源碼安裝及使用UDFs進(jìn)行數(shù)據(jù)自動(dòng)更新的教程:2016-07-07MySQL到Kafka實(shí)時(shí)數(shù)據(jù)同步
很多 DBA 同學(xué)經(jīng)常會(huì)遇到要從一個(gè)數(shù)據(jù)庫(kù)實(shí)時(shí)同步到另一個(gè)數(shù)據(jù)庫(kù)的問(wèn)題,同構(gòu)數(shù)據(jù)還相對(duì)容易,遇上異構(gòu)數(shù)據(jù)、表多、數(shù)據(jù)量大等情況就難以同步,我自己親測(cè)了一種方式,可以非常方便的實(shí)現(xiàn)MySQL Kafka實(shí)時(shí)數(shù)據(jù)同步,需要的朋友可以參考下2024-01-01MySql8 WITH RECURSIVE遞歸查詢父子集的方法
這篇文章主要介紹了MySql8 WITH RECURSIVE遞歸查詢父子集的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12mysql 松散的索引掃描(Loose index scan)
今天讀《High Performance MySQL》,發(fā)現(xiàn)一個(gè)“Loose index scan”,之前完全沒(méi)有聽(tīng)說(shuō)過(guò)。網(wǎng)上查了些資料,這個(gè)叫松散的索引掃描(Loose index scan)2016-05-05MySQL分表實(shí)現(xiàn)上百萬(wàn)上千萬(wàn)記錄分布存儲(chǔ)的批量查詢?cè)O(shè)計(jì)模式詳解
本篇文章是對(duì)使用MySQL分表實(shí)現(xiàn)上百萬(wàn)上千萬(wàn)記錄分布存儲(chǔ)的批量查詢?cè)O(shè)計(jì)模式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06