一、事務
事務是由一組SQL語句組成的邏輯處理單元。
事務的特徵ACID,即原子性、一致性、隔離性和永續性。
原子性(Atomicity)事務作為整體執行,操作要麼全部執行、要麼全部不執行。
一致性(Consistent)事務應該確保資料庫狀態從一個一致狀態轉變為另一個一致狀態。
隔離性(Isolation)多個事務併發執行時,一個事務執行不影響其他事務執行(隔離級別可設定)。
永續性(Durable)事務提交後,對資料庫的修改應該永久儲存在資料庫中。
二、事務的隔離級別
1、讀未提交(Read Uncommited,RU)
這種隔離級別下,事務間完全不隔離,會產生髒讀,可以讀取未提交的記錄,實際情況下不會使用。
讀取未提交的資料,又稱為“髒讀”。
2、讀提交(Read commited,RC)
僅能讀取到已提交的記錄,這種隔離級別下,會存在幻讀現象,所謂幻讀是指在同一個事務中,多次執行同一個查詢,返回的記錄不完全相同的現象。幻讀產生的根本原因是,在RC隔離級別下,每條語句都會讀取已提交事務的更新,若兩次查詢之間有其他事務提交,則會導致兩次查詢結果不一致。雖然如此,讀提交隔離級別在生產環境中使用很廣泛。
3、可重複讀(Repeatable Read,RR)
可重複讀解決了幻讀問題。不是所有的資料庫都實現了該隔離級別。
4、序列化(Serializable)
在序列化隔離模式下,消除了髒讀、幻讀,但事務併發度急劇下降,事務的隔離級別與事務的併發度成反比,隔離級別越高,事務的併發度越低。實際生產環境下,dba會在併發和滿足業務需求之間做權衡,選擇合適的隔離級別。
三、關於鎖
1、MySQL常用儲存引擎的鎖機制:
MyISAM和MEMORY採用表級鎖(table-level locking)
BDB採用頁面鎖(page-level locking)或表級鎖,預設為頁面鎖
InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖
2、各種鎖特點:
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般
3、各種鎖的適用場景:
表級鎖更適合於以查詢為主,只有少量按索引條件更新資料的應用,如Web應用
行級鎖則更適合於有大量按索引條件併發更新資料,同時又有併發查詢的應用,如一些線上事務處理系統
4、死鎖:
是指兩個或兩個以上的程式在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。
表級鎖不會產生死鎖.所以解決死鎖主要還是針對於最常用的InnoDB.
5.1 InnoDB鎖型別
共享鎖(S):允許事務讀一行資料
排他鎖(X):允許事務刪除或更新一行資料
5.2 InnoDB意向鎖(表級別)
意向共享鎖(IS):事務想要獲得一個表中某幾行的共享鎖
意向排他鎖(IX):事務想要獲得一個表中某幾行的排他鎖
5.3、對讀操作進行枷鎖
select .... lock in share mode:加S鎖
select ... for update:加X鎖
5.4、InnoDB鎖演算法
Record Lock:單個行記錄上的鎖
Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身
Next-Key Lock:鎖定一個範圍的記錄,並且包括記錄本身(在Repeatable Read事務級別下,使用該演算法,可避免幻讀的產生)
6、InnoDB鎖效能監控
mysql> show status like '%innodb_row_lock_%';
+-------------------------------+---------+
| Variable_name | Value |
+-------------------------------+---------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 4517738 |
| Innodb_row_lock_time_avg | 2673 |
| Innodb_row_lock_time_max | 51386 |
| Innodb_row_lock_waits | 1690 |
+-------------------------------+---------+
Innodb_row_lock_current_waits :當前等待鎖的數量
Innodb_row_lock_time :系統啟動到現在鎖定的總時間長度
Innodb_row_lock_time_avg :每次平均鎖定的時間
Innodb_row_lock_time_max :最長一次鎖定時間
Innodb_row_lock_waits :系統啟動到現在總共鎖定次數
7、案例1
建立表 CREATE TABLE `class_teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `class_name` varchar(100) DEFAULT NULL, `teacher_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB; 插入資料 insert into class_teacher(class_name, teacher_id) values('class301', 5),('class201', 30); 根據二級索引進行更新(update或delete),之後進行插入[資料庫隔離級別:REPEATABLE-READ] 1)、 事務0: update class_teacher set class_name='class202' where teacher_id=31; 事務1:insert into class_teacher(class_name,teacher_id) values('class229', 29); [插入OK] 事務2:insert into class_teacher(class_name,teacher_id) values('class230', 30); [插入WAIT] 事務3:insert into class_teacher(class_name,teacher_id) values('class231', 31); [插入WAIT] 事務4:insert into class_teacher(class_name,teacher_id) values('class232', 32); [插入WAIT] 事務5:insert into class_teacher(class_name,teacher_id) values('class250', 50); [插入WAIT] 結論: 鎖定[30,無窮大) 2)、 事務0: update class_teacher set class_name='class202' where teacher_id=3; 事務1:insert into class_teacher(class_name,teacher_id) values('class210', 1); [插入WAIT] 事務2:insert into class_teacher(class_name,teacher_id) values('class220', 2); [插入WAIT] 事務3:insert into class_teacher(class_name,teacher_id) values('class230', 3); [插入WAIT] 事務4:insert into class_teacher(class_name,teacher_id) values('class250', 5); [插入OK] 事務5:insert into class_teacher(class_name,teacher_id) values('class260', 6); [插入OK] 結論: 鎖定(無窮小,5) 3)、 事務0: update class_teacher set class_name='class202' where teacher_id=5; 事務1:insert into class_teacher(class_name,teacher_id) values('class240', 4); [插入WAIT] 事務2:insert into class_teacher(class_name,teacher_id) values('class250', 5); [插入WAIT] 事務3:insert into class_teacher(class_name,teacher_id) values('class260', 6); [插入WAIT] 事務4:insert into class_teacher(class_name,teacher_id) values('class215', 15); [插入WAIT] 事務5:insert into class_teacher(class_name,teacher_id) values('class229', 29); [插入WAIT] 事務6:insert into class_teacher(class_name,teacher_id) values('class230', 30); [插入OK] 事務7:insert into class_teacher(class_name,teacher_id) values('class231', 31); [插入OK] 結論: 鎖定(無窮小,5]和[5,30) 4)、 事務0:update class_teacher set class_name='class202' where teacher_id=30; 事務1:insert into class_teacher(class_name,teacher_id) values('class240', 4); [插入OK] 事務2:insert into class_teacher(class_name,teacher_id) values('class250', 5); [插入WAIT] 事務3:insert into class_teacher(class_name,teacher_id) values('class260', 6); [插入WAIT] 事務4:insert into class_teacher(class_name,teacher_id) values('class215', 15); [插入WAIT] 事務5:insert into class_teacher(class_name,teacher_id) values('class229', 29); [插入WAIT] 事務6:insert into class_teacher(class_name,teacher_id) values('class230', 30); [插入WAIT] 事務7:insert into class_teacher(class_name,teacher_id) values('class231', 31); [插入WAIT] 事務8:insert into class_teacher(class_name,teacher_id) values('class250', 50); [插入WAIT] 結論: 鎖定(5,30]和[30,無窮大) 5)、 事務0: update class_teacher set class_name='class202' where teacher_id=29; 事務1:insert into class_teacher(class_name,teacher_id) values('class240', 4); [插入OK] 事務2:insert into class_teacher(class_name,teacher_id) values('class250', 5); [插入WAIT] 事務3:insert into class_teacher(class_name,teacher_id) values('class260', 6); [插入WAIT] 事務4:insert into class_teacher(class_name,teacher_id) values('class215', 15); [插入WAIT] 事務5:insert into class_teacher(class_name,teacher_id) values('class229', 29); [插入WAIT] 事務6:insert into class_teacher(class_name,teacher_id) values('class230', 30); [插入OK] 事務7:insert into class_teacher(class_name,teacher_id) values('class231', 31); [插入OK] 結論: 鎖定[5,30)
總結:當是根據二級索引進行更新,如update或delete時,在當前隔離級別下,其都會使用Next-Key鎖[a,b)[行鎖 + gap鎖]前閉後開的,無法進行插入。
備註:
Repeatable Read,當使用非索引欄位進行更新時,則會進行表鎖。
Read Uncommitted,資料庫一般不用,且在其上的任何操作都不會加鎖。
Read Committed,有Record鎖,沒有Next-Key鎖,即Next-Key鎖變成了Record鎖。