數據庫事務的4個特性:
原子性(Atomic): 事務中的多個操做,不可分割,要麼都成功,要麼都失敗; All or Nothing.
一致性(Consistency): 事務操做以後, 數據庫所處的狀態和業務規則是一致的; 好比a,b帳戶相互轉帳以後,總金額不變;
隔離性(Isolation): 多個事務之間就像是串行執行同樣,不相互影響;
持久性(Durability): 事務提交後被持久化到永久存儲.html
其中 隔離性 分爲了四種:mysql
READ UNCOMMITTED:能夠讀取未提交的數據,未提交的數據稱爲髒數據,因此又稱髒讀。此時:幻讀,不可重複讀和髒讀均容許;
READ COMMITTED:只能讀取已經提交的數據;此時:容許幻讀和不可重複讀,但不容許髒讀,因此RC隔離級別要求解決髒讀;
REPEATABLE READ:同一個事務中屢次執行同一個select,讀取到的數據沒有發生改變;此時:容許幻讀,但不容許不可重複讀和髒讀,因此RR隔離級別要求解決不可重複讀;
SERIALIZABLE: 幻讀,不可重複讀和髒讀都不容許,因此serializable要求解決幻讀;sql
髒讀:能夠讀取未提交的數據。RC 要求解決髒讀;數據庫
不可重複讀:同一個事務中屢次執行同一個select, 讀取到的數據發生了改變(被其它事務update而且提交);session
可重複讀:同一個事務中屢次執行同一個select, 讀取到的數據沒有發生改變(通常使用MVCC實現);RR各級級別要求達到可重複讀的標準;併發
幻讀:同一個事務中屢次執行同一個select, 讀取到的數據行發生改變。也就是行數減小或者增長了(被其它事務delete/insert而且提交)。SERIALIZABLE要求解決幻讀問題;app
這裏必定要區分 不可重複讀 和 幻讀:async
不可重複讀的重點是修改:
一樣的條件的select, 你讀取過的數據, 再次讀取出來發現值不同了
幻讀的重點在於新增或者刪除:
一樣的條件的select, 第1次和第2次讀出來的記錄數不同性能
從結果上來看, 二者都是爲屢次讀取的結果不一致。但若是你從實現的角度來看, 它們的區別就比較大:
對於前者, 在RC下只須要鎖住知足條件的記錄,就能夠避免被其它事務修改,也就是 select for update, select in share mode; RR隔離下使用MVCC實現可重複讀;
對於後者, 要鎖住知足條件的記錄及全部這些記錄之間的gap,也就是須要 gap lock。大數據
而ANSI SQL標準沒有從隔離程度進行定義,而是定義了事務的隔離級別,同時定義了不一樣事務隔離級別解決的三大併發問題:
solation Level |
Dirty Read |
Unrepeatable Read |
Phantom Read |
Read UNCOMMITTED |
YES |
YES |
YES |
READ COMMITTED |
NO |
YES |
YES |
READ REPEATABLE |
NO |
NO |
YES |
SERIALIZABLE |
NO |
NO |
NO |
參見:你真的明白事務的隔離性嗎? (姜承堯)
除了MySQL默認採用RR隔離級別以外,其它幾大數據庫都是採用RC隔離級別。
可是他們的實現也是極其不同的。Oracle僅僅實現了RC 和 SERIALIZABLE隔離級別。默認採用RC隔離級別,解決了髒讀。可是容許不可重複讀和幻讀。其SERIALIZABLE則解決了髒讀、不可重複讀、幻讀。
MySQL的實現:MySQL默認採用RR隔離級別,SQL標準是要求RR解決不可重複讀的問題,可是由於MySQL採用了gap lock,因此實際上MySQL的RR隔離級別也解決了幻讀的問題。那麼MySQL的SERIALIZABLE是怎麼回事呢?其實MySQL的SERIALIZABLE採用了經典的實現方式,對讀和寫都加鎖。
MySQL數據庫中默認隔離級別爲RR,可是實際狀況是使用RC 和 RR隔離級別的都很多。好像淘寶、網易都是使用的 RC 隔離級別。那麼在MySQL中 RC 和 RR有什麼區別呢?咱們該如何選擇呢?爲何MySQL將RR做爲默認的隔離級別呢?
1> 顯然 RR 支持 gap lock(next-key lock) (間隙鎖),而RC則沒有gap lock。由於MySQL的RR須要gap lock來解決幻讀問題。而RC隔離級別則是容許存在不可重複讀和幻讀的。因此RC的併發通常要好於RR;
2> RC 隔離級別,經過 where 條件過濾以後,不符合條件的記錄上的行鎖,會釋放掉(雖然這裏破壞了「兩階段加鎖原則」);可是RR隔離級別,即便不符合where條件的記錄,也不會是否行鎖和gap lock;因此從鎖方面來看,RC的併發應該要好於RR;另外 insert into t select ... from s where 語句在s表上的鎖也是不同的,參見下面的例子2;
例子1:
下面是來自 itpub 的一個例子:http://www.itpub.net/thread-1941624-1-1.html
MySQL5.6, 隔離級別RR,autocommit=off;
表結構:
mysql> show create table t1\G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `a` int(11) NOT NULL, `b` int(11) NOT NULL, `c` int(11) NOT NULL, `d` int(11) NOT NULL, `e` varchar(20) DEFAULT NULL, PRIMARY KEY (`a`), KEY `idx_t1_bcd` (`b`,`c`,`d`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 row in set (0.00 sec)
表數據:
mysql> select * from t1; +---+---+---+---+------+ | a | b | c | d | e | +---+---+---+---+------+ | 1 | 1 | 1 | 1 | a | | 2 | 2 | 2 | 2 | b | | 3 | 3 | 2 | 2 | c | | 4 | 3 | 1 | 1 | d | | 5 | 2 | 3 | 5 | e | | 6 | 6 | 4 | 4 | f | | 7 | 4 | 5 | 5 | g | | 8 | 8 | 8 | 8 | h | +---+---+---+---+------+ rows in set (0.00 sec)
操做過程:
session 1:
delete from t1 where b>2 and b<5 and c=2;
執行計劃以下:
mysql> explain select * from t1 where b>2 and b<5 and c=2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: range possible_keys: idx_t1_bcd key: idx_t1_bcd key_len: 4 ref: NULL rows: 2 Extra: Using index condition row in set (0.00 sec)
session 2:
delete from t1 where a=4
結果 session 2 被鎖住。
session 3:
mysql> select * from information_schema.innodb_locks; +---------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+ | lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data | +---------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+ | 38777:390:3:5 | 38777 | X | RECORD | `test`.`t1` | PRIMARY | 390 | 3 | 5 | 4 | | 38771:390:3:5 | 38771 | X | RECORD | `test`.`t1` | PRIMARY | 390 | 3 | 5 | 4 | +---------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
根據鎖及ICP的知識,此時加鎖的狀況應該是在索引 idx_t1_bcd 上的b>2 and b<5之間加gap lock, idx_t1_bcd 上的c=2 加 X鎖主鍵 a=3 加 x 鎖。
應該a=4上是沒有加X鎖的,能夠進行刪除與更改。
可是從session3上的結果來,此時a=4上被加上了X鎖。
-------
要理解這裏爲何 a=4 被鎖住了,須要理解 gap lock,鎖處理 RR 隔離級別和RC隔離級別的區別等等。
這裏的緣由以下:
很簡單,咱們注意到:key_len: 4 和 Extra: Using index condition
這說明了,僅僅使用了索引 idx_t1_bcd 中的 b 一列,沒有使用到 c 這一列。c 這一列是在ICP時進行過濾的。因此:
delete from t1 where b>2 and b<5 and c=2 其實鎖定的行有:
mysql> select * from t1 where b>2 and b<=6; +---+---+---+---+------+ | a | b | c | d | e | +---+---+---+---+------+ | 3 | 3 | 2 | 2 | c | | 4 | 3 | 1 | 1 | d | | 6 | 6 | 4 | 4 | f | | 7 | 4 | 5 | 5 | g | +---+---+---+---+------+ rows in set (0.00 sec)
因此顯然 delete from t1 where a=4 就被阻塞了。那麼爲何 delete from t1 where a=6 也會被阻塞呢???
這裏 b<=6 的緣由是,b 列中沒有等於 5 的記錄,因此 and b<5 實現爲鎖定 b<=6 的全部索引記錄,這裏有等於號的緣由是,若是咱們不鎖定 =6 的索引記錄,那麼怎麼實現鎖定 <5 的gap 呢?也就是說鎖定 b=6 的索引記錄,是爲了實現鎖定 b< 5 的gap。也就是不能刪除 b=6 記錄的緣由。
而這裏 b >2 沒有加等於號(b>=2) 的緣由,是由於 b>2的這個gap 是由 b=3這個索引記錄(的gap)來實現的,不是由 b=2索引記錄(的gap) 來實現的,b=2的索引記錄的gap lock只能實現鎖定<2的gap,b>2的gap鎖定功能,須要由 b=3的索引記錄對應的gap來實現(b>2,b<3的gap)。
因此咱們在session2中能夠刪除:a=1,2,5,8的記錄,可是不能刪除 a=6(由於該行的b=6)的記錄。
若是咱們使用 RC 隔離級別時,則不會發生阻塞,其緣由就是:
RC和RR隔離級別中的鎖處理不同,RC隔離級別時,在使用c列進行ICP where條件過濾時,對於不符合條件的記錄,鎖會釋放掉,而RR隔離級別時,即便不符合條件的記錄,鎖也不會釋放(雖然違反了「2階段鎖」原則)。因此RC隔離級別時session 2不會被阻塞。
Gap lock: This is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
例子2:
insert into t select ... from s where 在RC 和 RR隔離級別下的加鎖過程
下面是官方文檔中的說明:http://dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html
INSERT INTO T SELECT ... FROM S WHERE ... sets an exclusive index record lock (without a gap lock) on each row inserted into T. If the transaction isolation level is READ COMMITTED, or innodb_locks_unsafe_for_binlog is enabled and the transaction isolation level is not SERIALIZABLE, InnoDB does the search on S as a consistent read (no locks). Otherwise, InnoDB sets shared next-key locks on rows from S. InnoDB has to set locks in the latter case: In roll-forward recovery from a backup, every SQL statement must be executed in exactly the same way it was done originally.
CREATE TABLE ... SELECT ... performs the SELECT with shared next-key locks or as a consistent read, as for INSERT ... SELECT.
When a SELECT is used in the constructs REPLACE INTO t SELECT ... FROM s WHERE ... or UPDATE t ... WHERE col IN (SELECT ... FROM s ...), InnoDB sets shared next-key locks on rows from table s.
insert inot t select ... from s where ... 語句和 create table ... select ... from s where 加鎖過程是類似的(RC 和 RR 加鎖不同):
1> RC 隔離級別時和 RR隔離級別可是設置innodb_locks_unsafe_for_binlog=1 時,select ... from s where 對 s 表進行的是一致性讀,因此是無需加鎖的;
2> 若是是RR隔離級別(默認innodb_locks_unsafe_for_binlog=0),或者是 serializable隔離級別,那麼對 s 表上的每一行都要加上 shared next-key lock.
這個區別是一個很大的不一樣,下面是生成中的一個 insert into t select ... from s where 致使的系統宕機的案例:
一程序猿執行一個分表操做:
insert into tb_async_src_acct_201508 select * from tb_async_src_acct where src_status=3 and create_time>='2015-08-01 00:00:00' and create_time <= '2015-08-31 23:59:59';
表 tb_async_src_acct有4000W數據。分表的目的是想提高下性能。結果一執行該語句,該條SQL被卡住,而後全部向 tb_async_src_acct的寫操做,要麼是 get lock fail, 要麼是 lost connection,所有卡住,而後主庫就宕機了。
顯然這裏的緣由,就是不知道默認RR隔離級別中 insert into t select ... from s where 語句的在 s 表上的加鎖過程,該語句一執行,全部符合 where 條件的 s 表中的行記錄都會加上 shared next-key lock(若是沒有使用到索引,還會鎖住表中全部行),在整個事務過程當中一直持有,由於表 tb_async_src_acct 數據不少,因此運行過程是很長的,因此加鎖過程也是很長,因此其它全部的對 tb_async_src_acct 的insert, delete, update, DDL 都會被阻塞掉,這樣被阻塞的事務就愈來愈多,而事務也會申請其它的表中的行鎖,結果就是系統中被卡住的事務愈來愈多,系統天然就宕機了。
1> RC 隔離級別不支持 statement 格式的bin log,由於該格式的複製,會致使主從數據的不一致;只能使用 mixed 或者 row 格式的bin log; 這也是爲何MySQL默認使用RR隔離級別的緣由。複製時,咱們最好使用:binlog_format=row
具體參見:
http://blog.itpub.net/29254281/viewspace-1081678/
http://www.cnblogs.com/vinchen/archive/2012/11/19/2777919.html
2> MySQL5.6 的早期版本,RC隔離級別是能夠設置成使用statement格式的bin log,後期版本則會直接報錯;
簡單並且,RC隔離級別時,事務中的每一條select語句會讀取到他本身執行時已經提交了的記錄,也就是每一條select都有本身的一致性讀ReadView; 而RR隔離級別時,事務中的一致性讀的ReadView是以第一條select語句的運行時,做爲本事務的一致性讀snapshot的創建時間點的。只能讀取該時間點以前已經提交的數據。
具體能夠參加:MySQL 一致性讀 深刻研究
RC隔離級別下的update語句,使用的是半一致性讀(semi consistent);而RR隔離級別的update語句使用的是當前讀;當前讀會發生鎖的阻塞。
1> 半一致性讀:
A type of read operation used for UPDATE statements, that is a combination of read committed and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again, and this time InnoDB either locks it or waits for a lock on it. This type of read operation can only happen when the transaction has the read committed isolation level, or when the innodb_locks_unsafe_for_binlog option is enabled.
簡單來講,semi-consistent read是read committed與consistent read二者的結合。一個update語句,若是讀到一行已經加鎖的記錄,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否知足 update的where條件。若知足(須要更新),則MySQL會從新發起一次讀操做,此時會讀取行的最新版本(並加鎖)。semi-consistent read只會發生在read committed隔離級別下,或者是參數innodb_locks_unsafe_for_binlog被設置爲true(該參數即將被廢棄)。
對比RR隔離級別,update語句會使用當前讀,若是一行被鎖定了,那麼此時會被阻塞,發生鎖等待。而不會讀取最新的提交版本,而後來判斷是否符合where條件。
半一致性讀的優勢:
減小了update語句時行鎖的衝突;對於不知足update更新條件的記錄,能夠提早放鎖,減小併發衝突的機率。
具體能夠參見:http://hedengcheng.com/?p=220
Oracle中的update好像有「重啓動」的概念。