Mysql-innoDB存储引擎(事务,锁,MVCC)
innoDB的特性:
從圖中由上至下紅色框中的信息是:基于主鍵的聚集索引 ,數據緩存,外鍵支持(邏輯上建立外鍵),行級別鎖,MVCC多版本控制,事務支持。這些也是InnoDB最重要的特性。
事務:
數據庫操作的最小工作單元,是作為單個邏輯工作單元執行的一系列操作;事務是一組不可再分割的操作集合(工作邏輯單元)。典型事務場景(轉賬):這是兩個事務
update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance +1000 where userID = 1;
mysql中如何開啟事務:
通過navicat使用命令 showvariables like ‘autocommit’; 查看自動提交是否開啟。當開啟后執行update語句會自動提交,當自動提交是關閉的,可以通過以下方式來創建事務提交:
BEGIN;-- 這兩個二選一開啟事務 START TRANSACTION; -- 這是一個事務 UPDATE ...... UPDATE ......COMMIT;-- 提交或者回滾 ROLLBACK;begin / start transaction -- 手工開啟事務。
commit / rollback -- 事務提交或回滾。
set session autocommit = on/off; -- 從Session的角度設定事務是否自動開啟。
JDBC 編程:
connection.setAutoCommit(boolean);
Spring 事務AOP編程:
expression=execution(com.gpedu.dao.*.*(..))
事務ACID特性:
- 原子性(Atomicity):最小的工作單元,整個工作單元要么一起提交成功,要么全部失敗回滾
- 一致性(Consistency):事務中操作的數據及狀態改變是一致的,即寫入資料的結果必須完全符合預設的規則,不會因為出現系統意外等原因導致狀態的不一致
- 隔離性(Isolation):數據并發的時候,一個事務所操作的數據在提交之前,對其他事務的可見性設定(一般設定為不可見)
- 持久性(Durability):事務所做的修改就會永久保存,不會因為系統意外導致數據的丟失
事務并發帶來什么問題:
先來看第一張圖:在下圖中,一張表中記錄只有一條,事務B修改該條記錄的 age字段,而此刻 事務A來查詢了,獲得的age是18,接著事務B 回滾了,這樣子就出現了臟讀問題。
再來看第二個圖:事務A先查詢了數據信息,此刻事務B進行了修改并提交,然后事務A又去查詢了一遍,這個時候就會出現不可重復讀的問題。
第三張圖:通過范圍查詢獲得一條數據,此刻事務B 插入了一條數據,事務A又去查詢獲得了兩條數據,此刻就發生了幻讀。
綜上,事務并發給我們帶來了三個主要問題:臟讀,不可重復讀,幻讀。
事務的隔離級別:
- Read Uncommitted(未提交讀) --未解決并發問題,事務未提交對其他事務也是可見的,臟讀(dirty read)。
- Read Committed(提交讀) --解決臟讀問題,一個事務開始之后,只能看到自己提交的事務所做的修改,不可重復讀(nonrepeatableread)。
- Repeatable Read (可重復讀) --解決不可重復讀問題在同一個事務中多次讀取同樣的數據結果是一樣的,這種隔離級別未定義解決幻讀的問題。
- Serializable(串行化) --解決所有問題,最高的隔離級別,通過強制事務的串行執行。
設置read uncommitted級別:set session transaction isolation level read uncommitted;
?
innoDB對隔離級別的支持程度:
在InnoDB中隔離級別到底如何實現的呢? --通過鎖、MVCC。
InnoDB中的鎖:
鎖是用于管理不同事務對共享資源的并發訪問,InnoDB存儲引擎支持行鎖和表鎖(另類的行鎖,通過行鎖鎖住所有的行)。官方文檔:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html。表鎖與行鎖的區別:
- 鎖定粒度:表鎖 > 行鎖
- 加鎖效率:表鎖 > 行鎖
- 沖突概率:表鎖 > 行鎖
- 并發性能:表鎖 < 行鎖
MYSQL innoDB鎖類型:
- l 共享鎖(行鎖):Shared Locks
- l 排它鎖(行鎖):Exclusive Locks
- l 意向共享鎖(表鎖):Intention Shared Locks
- l 意向排它鎖(表鎖):Intention Exclusive Locks
- l 自增鎖:AUTO-INC Locks
行鎖的算法:
- l 記錄鎖 Record Locks
- l 間隙鎖 Gap Locks
- l 臨鍵鎖 Next-key Locks
?共享鎖:
又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對于同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改,加鎖釋鎖方式:
-- 共享鎖加鎖 BEGIN select * from users WHERE id=1 LOCK IN SHARE MODE; rollback; commit; -- 在以上的SQL枷鎖后未執行提交或者回滾執行其他事務執行 select * from users where id =1; -- 可以執行,共享鎖特性 update users set age=19 where id =1;--會阻塞排他鎖:
又稱為寫鎖,簡稱X鎖,排他鎖不能與其他鎖并存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖、排他鎖),只有該獲取了排他鎖的事務是可以對數據行進行讀取和修改,(其他事務要讀取數據可來自于快照),加鎖釋鎖方式:delete / update / insert 默認加上X鎖。
-- 自動獲取排它鎖 set session autocommit = OFF; -- 設置手動提交事務 update users set age = 23 where id =1; --執行該語句后未提交,在其他線程上,執行下列其他事務執行語句會處于阻塞commit; ROLLBACK;-- 手動獲取排它鎖 set session autocommit = ON; begin select * from users where id =1 for update; commit;-- 其他事務執行 select * from users where id =1 lock in share mode; select * from users where id =1 for update; select * from users where id =1;innoDB--行鎖到底鎖了什么?
首先先來看一下測試表的結構,其中用的是InnoDB引擎,有一個name的唯一索引,主鍵自增,有3條數據
DROP TABLE IF EXISTS `users`; CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`uname` varchar(32) NOT NULL,`userLevel` int(11) NOT NULL,`age` int(11) NOT NULL,`phoneNum` char(11) NOT NULL,`createTime` datetime NOT NULL,`lastUpdate` datetime NOT NULL,PRIMARY KEY (`id`),KEY `idx_name` (`uname`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=100006 DEFAULT CHARSET=utf8mb4;-- ---------------------------- -- Records of users -- ---------------------------- INSERT INTO `users` VALUES ('1', '李二狗', '2', '18', '13666666666', '2018-12-01 15:39:46', '2018-12-01 15:39:50'); INSERT INTO `users` VALUES ('2', '張三豐', '1', '29', '13777777777', '2018-12-01 16:35:41', '2018-12-01 16:35:44'); INSERT INTO `users` VALUES ('3', '武大郎', '2', '44', '13888888888', '2018-12-01 16:36:01', '2018-12-01 16:36:03');案例1:緊接著在一個事務中執行以下語句:可以發現我們把事務設置成手動提交,但是我并未提交或者回滾:
set session autocommit = OFF; update users set lastUpdate=NOW() where phoneNum = '13666666666';然后在其他事務中執行如下語句:會發現,上述SQL執行修改會獲得默認的排它鎖,而此刻并未釋放,鎖的列是ID為1,然后我們下列要修改ID為2的數據也是出于阻塞,這是為什么呢?
update users set lastUpdate=NOW() where id =2; update users set lastUpdate=NOW() where id =1;案例2,執行以下語句,可以發現我們把事務設置成手動提交,但是我并未提交或者回滾:
set session autocommit = OFF; update users set lastUpdate=NOW() where id = 1;然后在其他事務上執行:會發現下面2條SQL執行后 第一條會順利執行,而第二條會被阻塞。
update users set lastUpdate=NOW() where id =2; update users set lastUpdate=NOW() where id =1;案例三:執行一下語句:
set session autocommit = OFF; update users set lastUpdate=NOW() where `name` = '李二狗';然后在其他事務上執行:會發現前面兩條會執行失敗,而后面兩條執行成功
-- 其他查詢執行 update users set lastUpdate=NOW() where `name` = '李二狗'; update users set lastUpdate=NOW() where id =1; update users set lastUpdate=NOW() where `name` = '張三豐'; update users set lastUpdate=NOW() where id =2;InnoDB的行鎖是通過給索引上的索引項加鎖來實現的。對于二級索引,會對一級索引也加鎖。只有通過索引條件進行數據檢索,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖(鎖住索引的所有記錄)表鎖:lock tables xx read/write;
意向共享鎖(IS):表示事務準備給數據行加入共享鎖,即一個數據行加共享鎖前必須先取得該表的IS鎖,意向共享鎖之間是可以相互兼容的。
意向排它鎖(IX):表示事務準備給數據行加入排他鎖,即一個數據行加排他鎖前必須先取得該表的IX鎖,意向排它鎖之間是可以相互兼容的。
意向鎖(IS、IX)是InnoDB數據操作之前自動加的,不需要用戶干預。
意義:相當于一個標記flgs,當事務想去進行鎖表時,可以先判斷意向鎖是否存在,存在時則可快速返回該表不能啟用表鎖。
自增鎖 AUTO-INC Locks:
針對自增列自增長的一個特殊的表級別鎖,查看自增鎖默認值:show variables like 'innodb_autoinc_lock_mode';默認取值1,代表連續,事務未提交ID永久丟失。當級別為1,執行一下SQL:在插入數據的時候,這個表的ID為自增,連續回滾3次,這3次的ID會永久消失,在下次執行commit的時候ID會在原來的數值上加3.
begin; insert into users(name , age ,phoneNum ,lastUpdate ) values ('tom2',30,'1344444444',now()); ROLLBACK;針對行鎖的算法:
臨鍵鎖 Next-key Locks:
Next-key locks:InnoDB行鎖的默認算法。鎖住記錄+區間(左開右閉),當sql執行按照索引進行數據的檢索時,查詢條件為范圍查找(between and、<、>等)并有數據命中則此時SQL語句加上的鎖為Next-key locks,鎖住索引的記錄+區間(左開右閉)。先來搞一張表:
DROP TABLE IF EXISTS `test`; CREATE TABLE `test` (`id` int(11) NOT NULL ,`name` varchar(32) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- ---------------------------- -- Records of users -- ---------------------------- INSERT INTO `test` VALUES ('1', '1'); INSERT INTO `test` VALUES ('4', '4'); INSERT INTO `test` VALUES ('7', '7'); INSERT INTO `test` VALUES ('10', '10');在InnoDB的默認行級算法中會對數據行進劃分:可以看到是一個左開右閉的這個一個展現。
執行以下sql不提交:由于有數據命中則會鎖住(4,7](7,10] 兩個區間。未提交的情況下執行下列其他事務中前四條全部阻塞而最后一條會成功執行。
begin; select * from test where id>5 and id<9 for update; -- 其他事務 select * from test where id=4 for update; -- 阻塞 select * from test where id=7 for update; -- 阻塞 select * from test where id=10 for update; -- 阻塞 INSERT INTO `test` (`id`, `name`) VALUES (9, '9'); -- 阻塞 INSERT INTO `test` (`id`, `name`) VALUES (11, '11');-- 成功為什么InnoDB要選擇(臨鍵鎖)Next-key locks作為InnoDB行鎖的默認算法?解決幻讀,因為B+Tree是有順序的,從左往右順序遞增,把臨鍵區間也鎖住,其他事務要往里插入數據是插不進去的。
間隙鎖 Gap Locks:繼臨鍵鎖要是沒有命中數據的情況下:
Gap鎖只在?Repeatable Read (可重復讀)? 的隔離級別的情況下才存在。
記錄鎖 Record Locks:繼臨鍵鎖之后,在條件為精準匹配的時候。
?
那么鎖是怎么解決上述產生 臟讀,不可重復讀,以及幻讀的情況呢?
解決臟讀:
解決不可重復讀:
解決幻讀:
死鎖:
- 多個并發事務(2個或者以上);
- 每個事務都持有鎖(或者是已經在等待鎖);
- 每個事務都需要再繼續持有鎖;
- 事務之間產生加鎖的循環等待,形成死鎖
避免死鎖:
- 類似的業務邏輯以固定的順序訪問表和行。
- 大事務拆小。大事務更傾向于死鎖,如果業務允許,將大事務拆小。
- 在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。
- 降低隔離級別,如果業務允許,將隔離級別調低也是較好的選擇
- 為表添加合理的索引。可以看到如果不走索引將會為表的每一行記錄添加上鎖(或者說是表鎖)
Mysql 中MVCC版本控制:
MVCC是multiversion concurrency control的縮寫,并發訪問(讀或寫)數據庫時,對正在事務內處理的數據做多版本的管理。以達到用來避免寫操作的堵塞,從而引發讀操作的并發問題 。提供MySQL事物隔離級別下無鎖讀,例如一個事物在執行update等修改數據的sql,并未提交時其他事物進行數據讀取是不影響的,而且讀取內容為數據變更之前的數據。
? MVCC多本版快照由innodb的rollback segment構照的,一個sql進行查找數據當查找到某一個數據需要到回滾段中查找數據時,就會根據當前頁上行數據的一個指針到回滾段中查找對應數據,在innodb的表主鍵中都會存在三個隱藏的字段:
- ????DB_TRX_ID:該字段存儲最后一個修改該行數據的事務ID,占用6byte的空間,MySQL的delete操作是標記刪除,所以對應行數據的該字段就為一個刪除標記。
- ????DB_ROLL_PTR:該字段就記錄執行roll segment的指針信息,當事務需要rollback時就通過該字段尋找記錄重新構照行數據,該字段占用7byte空間。
- ????DB_ROW_ID:記錄每個行ID,該ID值為單調遞增型整數,在innodb表指定了主鍵之后DB_ROW_ID存在于主鍵索引上,如果無主鍵該值就不會存在,占用6byte空間。
? 在一個sql進行查詢時,讀取到一行數據的DB_TRX_ID值和自己事物ID的對比,假如隔離級別為MySQL的默認級別,就只讀取該ID值小于本身事物ID的數據,其余數據就需要通過DB_ROLL_PTR的信息到回滾段中讀取。MVCC是否起到相應的作用需取決于數據庫隔離級別的配置。
? 在insert和update、delete的操作是有區別的,一個insert語句插入數據再rollback就是直接對undo log的刪除,他并不會影響其他事物的讀取操作,而update、delete操作是在原有數據做更改,可能有其他事物在對該行數據做讀取操作,所以update、delete產生的undo log數據是由內部線程自動清理,在該數據無任何事務在使用時清理掉,所以在undo log中insert和update、delete產生的數據存于不同位置。
下面通過一個案例來熟悉一下MVCC的效果:
-- 數據準備 insert into teacher(name,age) value ('seven',18) ;--假設事務版本為1 insert into teacher(name,age) value ('qing',20) ;--假設事務版本為1 begin; ----------1 select * from users ; ----------2begin; ----------3 update teacher set age =28 where id =1;----------4在每一行數據 插入數據表的時候,都會開啟一個事務,每一行數據都會保存執行的時候所獲取的事務版本號,當進行修改的時候會先copy一份待修改的數據到 Undo 緩沖區,在提交后然寫入磁盤,在此過程中會將原先的數據行的刪除版本號置為當前事務ID,然后再在新的數據行把數據行版本號置為當前事務ID。
當我們按照 1,2,3,4,2 的順序去執行的時候,首先執行 1 拿到的事務ID 是2,那么執行2查詢出來就是原始數據,這個時候事務并沒有提交或者回滾,然后執行3開啟一個事務拿到的事務ID 為3 ,此刻執行4(在更新操作的時候,采用的是先標記舊的那行記錄為已刪除,并且刪除版本號是事務版本號,然后插入一行新的記錄的方式。)進行 update 操作的時候會 copy 數據到Undo 緩沖區,然后將Undo.log的原始數據的刪除版本號置為3,把新數據的事務版本號置為3,再執行2的時候由于此刻事務ID 還是為2,所以根據查詢規則查找數據行版本號小于當前事務版本的數據行,查找刪除版本號大于當前事務版本的或者刪除版本為nul的數據行,由于修改操作未提交,所以最終得到的結果數據還是原始數據的值,并不會把修改的數據加載回來,解決了不可重復讀的問題。
如果按照這樣的邏輯通過 3,4,1,2的順序去執行,那么首先修改的操作會拿到事務ID為2,將原來的數據行copy出來,將原來的刪除版本號置為當前事務ID,接著將備份數據的版本號置為當前版本號,然后執行查詢操作再開啟一個新事務,拿到的事務ID為3,根據查詢規則,拿到的是進行了update 操作但并未提交的新數據,造成了臟讀,這是為什么呢?那么是由誰去解決這個問題的呢?其實這里面涉及到了 Undo.log的機制以及當前讀,快照讀的問題,那么接下來看看他們是怎么處理這個問題的 。
Undo Log:
Undo Log 是什么:undo意為取消,以撤銷操作為目的,返回指定某個狀態的操作,undo log指事務開始之前,在操作任何數據之前,首先將需操作的數據備份到一個地方 (Undo Log),UndoLog是為了實現事務的原子性而出現的產物。
Undo Log實現事務原子性:事務處理過程中如果出現了錯誤或者用戶執行了 ROLLBACK語句,Mysql可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態。
UndoLog在Mysql innodb存儲引擎中用來實現多版本并發控制。
Undo log實現多版本并發控制:事務未提交之前,Undo保存了未提交之前的版本數據,Undo 中的數據可作為數據舊版本快照供其他并發事務進行快照讀。
如下圖這樣的處理就避免了臟讀的問題。
當前讀,快照讀:
快照讀:SQL讀取的數據是快照版本,也就是歷史版本,普通的SELECT就是快照讀innodb快照讀,數據的讀取將由 cache(原本數據) + undo(事務修改過的數據) 兩部分組成
當前讀:SQL讀取的數據是最新版本。通過鎖機制來保證讀取的數據無法通過其他事務進行修改UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE都是當前讀。
Redo Log:
Redo Log 是什么:Redo,顧名思義就是重做。以恢復操作為目的,重現操作;Redo log指事務中操作的任何數據,將最新的數據備份到一個地方 (Redo Log)。
Redo log的持久:不是隨著事務的提交才寫入的,而是在事務的執行過程中,便開始寫入redo 中。具體的落盤策略可以進行配置.RedoLog是為了實現事務的持久性而出現的產物。
Redo Log實現事務持久性:防止在發生故障的時間點,尚有臟頁未寫入磁盤,在重啟mysql服務的時候,根據redolog進行重做,從而達到事務的未入磁盤數據進行持久化這一特性。
? 流程圖如下:
指定Redo log 記錄在{datadir}/ib_logfile1&ib_logfile2 可通過innodb_log_group_home_dir 配置指定目錄存儲。一旦事務成功提交且數據持久化落盤之后,此時Redo log中的對應事務數據記錄就失去了意義,所以Redo log的寫入是日志文件循環寫入的。
- 指定Redo log日志文件組中的數量 innodb_log_files_in_group 默認為2
- 指定Redo log每一個日志文件最大存儲量innodb_log_file_size 默認48M
- 指定Redo log在cache/buffer中的buffer池大小innodb_log_buffer_size 默認16M
Redo buffer 持久化Redo log的策略, Innodb_flush_log_at_trx_commit:
- 取值 0 每秒提交 Redo buffer --> Redo log OS cache -->flush cache to disk[可能丟失一秒內的事務數據]。
- 取值 1 默認值,每次事務提交執行Redo buffer --> Redo log OS cache -->flush cache to disk[最安全,性能最差的方式]。
- 取值 2 每次事務提交執行Redo buffer --> Redo log OS cache 再每一秒執行 ->flush cache todisk操作。
來源:https://www.cnblogs.com/wuzhenzhao/p/10348566.html
總結
以上是生活随笔為你收集整理的Mysql-innoDB存储引擎(事务,锁,MVCC)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一枝梅(说一说一枝梅的简介)
- 下一篇: mysql之索引的工作机制