MySQL 鎖信息和事務

1 鎖概念

1.1 什麼是鎖

鎖是數據庫系統區別於文件系統的一個關鍵特性。數據庫系統使用鎖是爲了支持對共享資源進行併發訪問,提供數據的完整性和一致性。例如:操做緩衝池中的LRU列表,刪除、添加、移動LUR列表中的元素。 mysql

對於任何一種數據庫來講都須要有相應的鎖定機制,因此MySQL天然也不能例外。MySQL數據庫因爲其自身架構的特色,存在多種數據存儲引擎,每種存儲引擎所針對的應用場景特色都不太同樣,爲了知足各自特定應用場景的需求,每種存儲引擎的鎖定機制都是爲各自所面對的特定場景而優化設計,因此各存儲引擎的鎖定機制也有較大區別。MySQL經常使用存儲引擎(MyISAM,InnoDB)用了兩種類型(級別)的鎖定機制:表級鎖定,行級鎖定。linux

一、表級鎖 
表級別的鎖定是MySQL各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特色是實現邏輯很是簡單,帶來的系統負面影響最小。因此獲取鎖和釋放鎖的速度很快。因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。 
算法

 固然,鎖定顆粒度大所帶來最大的負面影響就是出現鎖定資源爭用的機率也會最高,導致並大度大打折扣sql

使用表級鎖定的主要是MyISAM,MEMORY,CSV等一些非事務性存儲引擎。數據庫

二、行級鎖 
行級鎖定最大的特色就是鎖定對象的顆粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。因爲鎖定顆粒度很小,因此發生鎖定資源爭用的機率也最小,可以給予應用程序盡可能大的併發處理能力而提升一些須要高併發應用系統的總體性能 緩存

 雖然可以在併發處理能力上面有較大的優點,可是行級鎖定也所以帶來了很多弊端。因爲鎖定資源的顆粒度很小,因此每次獲取鎖和釋放鎖須要作的事情也更多,帶來的消耗天然也就更大了。此外,行級鎖定也最容易發生死鎖 網絡

 使用行級鎖定的主要是InnoDB存儲引擎。session

總結以下: 
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低; 
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高;
數據結構

例如:架構

 

上面如圖只是對myisam 表修改一行記錄 ,其餘insert 操做 就須要等待上個update 語句執行完成,再執行insert 操做,這時候就會產生表鎖。

1.2 InnoDB 鎖的類型

InnoDB 存儲引擎實現了以下兩種標準的行級鎖: 
一、共享鎖(S Lock),容許事務讀一行數據。但不能修改,增長,刪除數據。 
二、排他鎖 (X Lock),獲准排他鎖的事務既能讀數據,又能修改數據。 

若是一個事務 t1 已近得到了行 r 的共享鎖,那麼另外的事務 t2 能夠得到行 r 的共享鎖,由於讀取並無改變行 r 的數據,稱這種狀況爲鎖兼容(Lock Compatible)。但如有其餘的事務想得到行 r 的排它鎖,則必須等待事務t1,t2 釋放行 r 的共享鎖——這種狀況稱爲鎖不兼容(confilict)

此外,InnoDB存儲引擎支持多粒度(granular)鎖定,這種鎖定容許事務在行級上的鎖和表級上的鎖同時存在。爲了支持在不一樣粒度上進行加鎖操做,InnoDB 存儲引擎支持了一種額外的鎖方式,稱爲意向鎖(Intention Lock)。意向鎖是將鎖定的對象分爲多個層次,意向鎖意味着事務但願在更細粒度上進行加鎖

 

如上圖,若將上鎖的對象當作一顆樹,那麼最下層的對象(行記錄)上鎖,也就是對最細粒度的對象進行上鎖,那麼首先須要對粗粒度的對象上鎖。若是須要對頁上的記錄r進行上 X鎖,那麼分別須要對數據庫A、表、頁上意向鎖,最後對記錄r上 X 鎖,若其中任何一個部分致使等待,那麼該操做須要等待粗粒度鎖的完成。舉例來講,在對記錄r 加 X鎖以前,已近有事務對錶1 進行了 S 表鎖,那麼表1 上已存在 S 鎖,以後事務須要對記錄r 表1 上加 IX , 因爲不兼容,因此該事務,須要等待表鎖操做的完成。 

 InnoDB 存儲引擎支持意向鎖設計比較簡練,其意向鎖即爲表級別的鎖,設計目的主要是爲了在一個事務中揭示下一行將被請求的鎖類型。其支持兩種意向鎖: 

一、意向共享鎖( intention shared lock, Is),事務有意向對錶中的某些行加共享鎖(S鎖) 
二、意向排它鎖(intention exclusive lock,IX),事務有意向對錶中的某些行加排他鎖(X鎖) 

意向鎖是有數據引擎本身維護的,用戶沒法手動操做意向鎖,在爲數據行加共享 / 排他鎖以前,InooDB 會先獲取該數據行所在在數據表的對應意向鎖。

因爲InnoDB 存儲引擎支持的是行級別的鎖,所以意向鎖其實不會阻塞除全表掃之外的任何請求。

 表級意向鎖與行鎖的兼容性 
S:共享鎖 
X:排它鎖 
IS:意向共享鎖 
IX:意向排它鎖

- IS IX S X
IS 兼容(compatible) 兼容 兼容 不兼容(conflict)
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

 

 排它鎖(X):與任何鎖都不兼容。 
共享鎖(S):只兼容共享鎖和意向共享鎖 
意向鎖(IS,IX): 互相兼容,行級別的鎖只兼容共享鎖

1.3 一致性鎖定讀

用戶有時候須要顯示地對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。而這要求數據庫支持加鎖語句,即便是對於select 的只讀操做。 InnoDB 存儲引擎對於 select 語句支持兩種一致性的鎖定讀 操做:

select ... for update;
select ... lock in share mode;

  

select … for update 對讀取的行記錄加一個 X 鎖,其餘事務不能對已鎖定的行加上任何鎖。 
select … lock in share mode 對讀取的行記錄加一個S 鎖,其餘事務能夠向被鎖定的加S 鎖,可是若是加X鎖,則會組賽。 
此外 select ... for update , select ... lock in share mode 必須在一個事務中,當事務提交了,鎖也就釋放了。所以在使用上訴兩句select 鎖定語句時,務必加上BEGIN, START TRANSACTION 或者 SET AUTOCOMMIT=0.

 

1.4 一致性非鎖定讀

在默認的隔離級別下一致讀是指InnoDB在多版本控制中在事務的首次讀時產生一個鏡像,在首次讀時間點以前其餘事務提交的修改能夠讀取到,而首次讀時間點以後其餘事務提交的修改或者是未提交的修改都讀取不到

 

惟一例外的狀況是在首次讀時間點以前的本事務未提交的修改數據能夠讀取到

在讀取提交數據隔離級別下,一致讀的每一個讀取操做都會有本身的鏡像

一致讀操做不會施加任何的鎖,因此就不會阻止其餘事務的修改動做

好比最經典的mysqldump --single-transaction備份的時候就是把當前的事務隔離級別改變爲可重複讀並開啓一個一致性事務的快照 , 就是一致性非鎖定讀

 

一致讀在某些DDL語句下不生效: 
一、碰到drop table語句時,因爲InnoDB不能使用被drop的表,因此沒法實現一致讀 
二、碰到alter table語句時,也沒法實現一致讀 
三、當碰到insert into… select, update … select和create table … select語句時,在默認的事務隔離級別下,語句的執行更相似於在讀取提交數據的隔離級別下

1.5 自增加與鎖

自增加在數據庫中很是常見的一種屬性,也是不少DBA或開發人員首選主鍵方式。在InnoDB 存儲引擎的內存結構中,對每一個含有自增加值的表都有一個自增加計數器。

插入操做會依據這個自增加的計數器加1 賦予自增加列。這個實現方式稱做AUTO-INC Locking(自增鎖)。 這種自增鎖是採用一種特殊的表鎖機制,爲了提升插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增加值插入的sql 語句後當即釋放。 
AUTO-INC Locking 從必定程度上提升了併發插入的效率,但仍是存在一些性能上的問題。

一、 首先,對於有自增加值的列的併發插入性能較差,事務必須等待前一個插入完成。 
二、其次,對於insert …select 的大數據量的插入會影響插入的性能,由於另外一個事務中插入會被阻塞。 
Innodb_autoinc_lock_mode 來控制自增加的模式,改參數的默認值爲1

InnoDB提供了一種輕量級互斥量的自增加實現機制,大大提升了自增加值插入的性能。提供參數innodb_autoinc_lock_mode來控制自增加鎖使用的算法,默認值爲1。他容許你在可預測的自增加值和最大化併發插入操做之間進行權衡。

插入類型的分類:

插入類型 說明
insert-like 指全部的插入語句,例如:insert、replace、insert … select、replace… select、load data
simple inserts 指再插入前就肯定插入行數的語句。例如:insert、replace等。注意:simple inserts不包含 insert … on duplicate key update 這類sql語句
bulk inserts 指在插入前不能肯定獲得插入行數的語句,例如:insert … select、 replace … select、load data
mixed-mode inserts 指插入中有一部分的值是自增加的,一部分是肯定的。例如:insert into t1(c1, c2) values (1, ‘a’), (NULL, ‘b’), (5, ‘c’), (NULL,’d’); 也能夠指 insert … on duplicate key update 這類sql語句

innodb_autoinc_lock_mode 在不一樣設置下對自增加的影響: 

innodb_autoinc_lock_mode = 0 : MySQL 5.1.22版本以前自增加的實現方式,經過表鎖的AUTO-INC Locking方式

innodb_autoinc_lock_mode = 1(默認值): 
對於『simple inserts』,該值會用互斥量(mutex)對內存中的計數器進行累加操做。對於『bulk inserts』會用傳統的AUTO-INC Locking方式。這種配置下,若是不考慮回滾,自增加列的增加仍是連續的。須要注意的是:若是已經使用AUTO-INC Locking方式去產生自增加的值,而此時須要『simple inserts』操做時,還須要等待AUTO-INC Locking的釋放

innodb_autoinc_lock_mode = 2 
對於全部『insert-like』自增加的產生都是經過互斥量,而不是AUTO-INC Locking方式。這是性能最高的方式。但會帶來一些問題:

 

由於併發插入的存在,每次插入時,自增加的值是不連續的基於statement-base replication會出現問題 

所以,使用這種方式,任何狀況下都須要使用row-base replication,這樣才能保證最大併發性能和replication的主從數據的一致 

2 行鎖的幾種算法

一、Record Lock :單個行記錄上的鎖 
二、Gap Lock :間隙鎖,鎖定一個範圍,但不包含記錄自己 
三、Next-Key Lock:Gap Lock + Record Lock ,鎖定一個範圍,而且鎖定記錄自己 
四、Insert Intention Locks:插入意向鎖

2.1 Record Lock

Record Lock 老是會去鎖住索引記錄, 若是InnoDB 存儲引擎表在創建的時候沒有設置任何一個索引,那麼這是InnoDB 存儲引擎會使用隱式的主鍵來進行鎖定。 

行級鎖是施加在索引行數據上的鎖,好比SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE語句是在t.c1=10的索引行上增長鎖,來阻止其餘事務對對應索引行的insert/update/delete操做。

行鎖老是在索引記錄上面加鎖,即便一張表沒有設置任何索引,InnoDB會建立一個隱藏的聚簇索引,而後在這個索引上加上行鎖。 

例如:

create table t (c1 int primary key);
insert into t select 1;
insert into t select 3;
insert into t select 10;

  

 

# 會話A
start transaction;
update t set c1=12 where c1 = 10 ;

# 會話B:
mysql> update t set c1=11 where c1=10;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
會阻止該事務對索引行上的修改

  

當一個InnoDB表沒有任何索引時, 則行級鎖會施加在隱含建立的聚簇索引上,因此說當一條sql沒有走任何索引時,那麼將會在每一條彙集索引後面加X鎖,這個相似於表鎖,但原理上和表鎖應該是徹底不一樣的

例:

# 刪除表t的主鍵索引
alter table t drop primary key;
開啓會話1:
start transaction;
update t set c1=11 where c1=10;

開啓會話2:
start transaction;
update t set c1=8 where c1=10;
這個時候發生了鎖等待,
這時候開啓會話3,鎖等待發生了什麼:
mysql> select * from sys.innodb_lock_waits\G;
以下截圖吧:

 

2.2 Gap Lock

當 咱們範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件 的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」, InnoDB也會對這個"間隙"加鎖

間隔鎖是施加在索引記錄之間的間隔上的鎖, 鎖定一個範圍的記錄、但不包括記錄自己,好比SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE語句,儘管有可能對c1字段來講當前表裏沒有=15的值,但仍是會阻止=15的數據的插入操做,是由於間隔鎖已經把索引查詢範圍內的間隔數據也都鎖住了 
間隔鎖的使用只在部分事務隔離級(可重複讀級)別纔是生效的 

間隔鎖只會阻止其餘事務的插入操做,就是隻有insert 操做會產生GAP鎖, update 操做不會參數GAP 鎖

例:

# 建立keme1 測試數據, 插入模擬數據
create table keme1 (id int primary key,name varchar(10));
insert into keme1 values (1,'a'),(3,'c'), (4,'d'), (5,'e'), (6,'f');
# 開啓三個session 窗口,兩個窗口模擬兩個事務, 另一個窗口看 兩個事務發生一些間隔鎖的信息
session1:
start transaction;
mysql> update keme1 set name='bb' where id between 1 and 3;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

session2:
start transaction;
mysql> insert into keme1 values (2,'bb');
# 這時候就有鎖等待了
select * from sys.innodb_lock_waits\G;

使用gap lock的前置條件: 

一、 事務隔離級別爲REPEATABLE-READ, innodb_locks_unsafe_for_binlog參數爲0,且sql走的索引爲非惟一索引(不管是等值檢索仍是範圍檢索) 
2 、事務隔離級別爲REPEATABLE-READ, innodb_locks_unsafe_for_binlog參數爲0,且sql是一個範圍的當前讀操做,這時即便不是非惟一索引也會加gap lock

Gap Lock 的做用是爲了阻止多個事務將記錄插入到同一範圍內,而這會致使幻讀問題的產生。 

能夠經過兩種方式來關閉Gap Lock: 
一、將事務的隔離級別設置爲READ COMMITTED 
二、將參數innodb_locks_unsafe_for_binlog 設置爲1

2.3 Next-Key Lock

在默認狀況下, mysql的事務隔離級別是可重複讀,而且innodb_locks_unsafe_for_binlog 
參數爲0,這時默認採用next-key locks。 
所謂Next-Key Locks, 就是記錄鎖和間隔鎖的結合,即除了鎖住記錄自己,還要再鎖住索引之間的間隙
  

 當掃描表的索引時,InnoDB以這種形式實現行級的鎖:遇到匹配的的索引記錄,在上面加上對應的 S 鎖或 X 鎖。所以,行級鎖其實是索引記錄鎖。若是一個事務擁有索引上記錄 r 的一個 S 鎖或 X 鎖,另外的事務沒法當即在 r 記錄索引順序以前的間隙上插入一條新的記錄。

假設有一個索引包含值:10,11,13和20。下列的間隔上均可能加上一個Next-Key 鎖(左開右閉)

 

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

在最後一個區間中,Next-Key鎖 鎖定了索引中的最大值到 正無窮。 
默認狀況下,InnoDB啓用 RR 事務隔離級別。此時,InnoDB在查找和掃描索引時會使用Next-Key 鎖,其設計的目的是爲了解決『幻讀』的出現
  

當查詢的索引含有惟一(主鍵索引和惟一索引)屬性是,InnoDB 存儲引擎會對 Next-Key Lock 進行優化, 將其降級爲Record Lock ,即僅鎖住索引自己,而不是範圍。

2.4 Insert Intention Lock

插入意向鎖是一種在數據行插入前設置的gap鎖。這種鎖用於在多事務插入同一索引間隙時,若是這些事務不是往這段gap的同一位置插入數據,那麼就不用互相等待。

create table keme2 (a int primary key);
insert into keme2 values (10),(11),(13),(20);

開啓三個會話窗口
session1:
start transaction;
mysql> select * from keme2 where a > 18 for update;
+----+
| a  |
+----+
| 20 |
+----+
1 row in set (0.00 sec)


session2;
start transaction;
mysql> insert into keme2 select 19;

 

 

 

客戶端A建立了一個keme2表,包含10,11,13,20四條索引記錄,而後去設置一個互斥鎖在大於18的全部索引記錄上。這個互斥鎖包含了在20記錄前的gap鎖。

 

3 鎖問題

經過鎖機制能夠實現事務的隔離性要求,使得事務能夠併發地工做。鎖提升了併發,可是也有有潛在的問題。不過好在由於事務隔離性的要求,鎖只會帶來三種問題,若是能夠防止這三種狀況的發生,哪將不會產生併發異常。 

3.1 髒讀

先了解髒數據,髒頁,髒讀。 
髒頁指的是在緩衝池中已近被修改的頁,可是沒有刷新到磁盤,即數據庫實例內存中的頁和磁盤中的頁數據是不一致的,固然在刷新到磁盤以前,日誌都已經被寫入到了重作日誌文件中。 

髒數據:是指事務對緩衝池中行記錄的修改,而且尚未被提交。

對於髒頁的讀取,是很是正常的。髒頁是由於數據庫實例內存和磁盤的異步形成的,這並不影響數據的一致性(或者說二者最終會達到一致性,即當髒頁都刷到磁盤)。而且由於髒頁的刷新是異步的,不影響數據庫的可用性,所以能夠帶來性能的提升。

髒數據是指未提交的數據,若是讀到髒數據,即一個事務能夠讀到另一個事務中未提交的數據,則顯然違反了數據庫的隔離性。 

髒讀:指的就是在不一樣的事務下,當前事務能夠讀到另外事務未提交的數據,簡單來講就是能夠讀到髒數據。

髒讀示例:

create table t (a int primary key);
insert into t values (1);

 

 

- 會話A 會話B
1 set @@tx_isolation=’read-uncommitted’; 
set autocommit=0;
 
2   set @@tx_isolation=’read-uncommitted’;
3   begin;
4   mysql> select * from t\G; 
a: 1
5 insert into t select 2;  
6   mysql> select * from t\G;
*** 1. row *** 
a: 1
*** 2. row ***
a: 2

 

會話A 並無主動提交2這條插入事務 , 可是在會話B 讀取到了, 這就是髒讀。

3.2 不可重複讀

不可重讀是在一個事務內讀取同一數據集合在這個事務尚未結束時,另一個事務也訪問同一數據集合,並作了一些DML操做。所以在第一個事務中的兩次讀取數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的狀況,這種狀況稱爲不可重複讀。

不可重複讀和髒讀的區別是:`髒讀示讀到未提交的數據,而不可重複讀讀到確實已近提交的數據。

3.3 丟失更新

 雖然數據庫能阻止更新問題的產生,可是在生產應用還有另外一個邏輯意義丟失更新問題,而致使該問題的並非由於數據庫自己的問題。實際上,在全部多用戶計算機系統環境下都有可能產生這個問題。好比下面的狀況: 
好比一個用戶帳號中有10000元,他用兩個網上銀行的客戶端分別進行轉帳操做,第一次轉帳9000人民幣,由於網絡和數據的關係,這時須要等待。可是這時用戶操做另外一個網上銀行客戶端,轉帳1元,若是最終兩筆操做都成功了,用戶帳號的餘款是9999 元,第一次轉的9000人民幣並無獲得更新,可是在轉帳的另外一個帳號卻會收到這9000元,這致使告終果就是錢變多,而帳不平。可是銀行了也很聰明啊,我的網銀綁定usb key的,不會發生這種狀況的。是的,經過usb key 登陸也許能夠解決這個問題。可是更重要的是在數據庫層解決這個問題,避免任何可能發生丟失更新的狀況. 
要避免丟失更新發生 ,須要讓事務在這種狀況下的操做變成串行化,而不是並行的操做。

4 鎖阻塞

由於不一樣鎖之間的兼容性關係,在有些時刻一個事務中的鎖須要等待另外一個事務中的鎖釋放它所佔用的資源,這就是阻塞 阻塞並非一件壞事,其實爲了確保事務能夠併發正常地運行。 
在InnoDB 存儲引擎中,參數innodb_lock_wait_timeout用來控制等待的時間(默認是50秒), innodb_rollback_on_timeout用來設定是否在等待超時時對進行中的事務進行回滾操做(默認是off,不回滾)。參數innodb_lock_wait_timeout能夠在mysql 數據庫運行時進行調整:

默認狀況下InnoDB 存儲引擎不會回滾超時引起的錯誤異常。其實InnoDB 存儲引擎在大部分狀況下都不會對異常進行回滾。 

 

 

查看鎖阻塞的信息:

select * from information_schema.innodb_trx\G; # 查看當前的事務信息
select * from information_schema.innodb_locks\G; # 查看當前的鎖信息
select * from information_schema.innodb_lock_waits\G; # 查看當前的鎖等待信息
能夠聯表查,查找本身想要的結果。
select * from sys.innodb_lock_waits\G; # 查看當前的鎖等待信息
show engine innodb status\G;
還能夠經過當前執行了執行了什麼語句
select * from  performance_schema.events_statements_current\G;  
show full processlist;

  

5 死鎖

死鎖是指兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象。

5.1 數據庫層面解決死鎖的兩種方式

一、解決死鎖的問題最簡單的方式是不要有等待,將任何的等待都轉化爲回滾,而且事務從新開始。 
這種沒有死鎖問題的產生。在線上環境中,可能致使併發性能的降低,甚至任何一個事務都不能進行。而這鎖帶來的問題遠比死鎖問題更爲嚴重,而這鎖帶來的問題原題遠比死鎖問題更爲嚴重,由於這很難被發現而且浪費資源。

二、解決死鎖的問題最簡單的一種方法時超時,即當兩個事務互相等待是,當一個等待時超過設置的某一閾值是,其中一個事務進行回滾,另外一個等待的事務就能繼續進行。用innodb_lock_wait_timeout用來設置超時的時間。

超時機制雖然簡單,僅經過超時後對事務進行回滾的方式來處理,或者說其根據FIFO的順序選擇回滾對象。但若超時的事務所佔權重比較大,如事務操做更新不少行(好比某程序猿用死循環來執行一些事務),佔用了較多的undo log,這是採用FIFO 的方式,就顯得不合適了,由於回滾這個事務的時間相對另外一個事務所佔用的時間可能會更多。

 

在mysql 5.7.x 和 mysql 5.6.x 對死鎖採用的方式: 
mysql 5.6.x 是用鎖等待(超時)的方式來解決, 沒有自動解決死鎖的問題:

 

 

 mysql 5.7.x 默認開啓了死鎖保護機制:

 

 

5.2 死鎖演示

若是程序是串行的,那麼不可能發生死鎖。死鎖只存在於併發的狀況,而數據庫自己就是一個併發運行的程序,所以可能會發生死鎖。

死鎖示例:

a :建立表
create table temp(id int primary key ,name varchar(10));
insert into temp values(1,'a'),(2,'b'),(3,'c');
此時表裏只有3條數據

執行步驟根據數據順序來:
1. 事務1:
start transaction;
update temp set name='aa' where id=1;

2. 事務2:
start transaction; 
update temp set name='bb' where id=2;


3. 事務1:update temp set name='aaa' where id=2;
   這時候3的步驟會有鎖等待, 立馬執行4,就會立刻產生死鎖
4. 事務2: update temp set name='bbb' where id=1;

  

 

5.3 避免死鎖發生的方法

在事務性數據庫中,死鎖是個經典的問題,但只要發生的頻率不高則死鎖問題不須要太過擔憂 
死鎖應該很是少發生,若常常發生,則系統是不可用。

查看死鎖的方法有兩種: 
經過show engine innodb status命令能夠查看最後一個死鎖的狀況 
經過innodb_print_all_deadlocks參數配置能夠將全部死鎖的信息都打印到MySQL的錯誤日誌中

減小死鎖發生的方法: 

一、儘量的保持事務小型化,減小事務執行的時間能夠減小發生影響的機率 
二、及時執行commit或者rollback,來儘快的釋放鎖 
三、當要訪問多個表數據或者要訪問相同表的不一樣行集合時,儘量的保證每次訪問的順序是相同的。好比能夠將多個語句封裝在存儲過程當中,經過調用同一個存儲過程的方法能夠減小死鎖的發生 
四、增長合適的索引以便語句執行所掃描的數據範圍足夠小 
五、儘量的少使用鎖,好比若是能夠承擔幻讀的狀況,則直接使用select語句,而不要使用select…for update語句 
六、若是沒有其餘更好的選擇,則能夠經過施加表級鎖將事務執行串行化,最大限度的限制死鎖發生

6 事務

事務的主要目的了:事務會把數據庫從一種一致狀態轉換爲另外一種一致狀態。在數據庫提交工做是,能夠確保要麼全部修改都已近保存了,要麼全部修改都不保存。

InnoDB 存儲引擎中的事務徹底符合ACID 的特性。 
原子性 (atomicity) 
一致性(consistency) 
隔離性(isolation) 
持久性(durability)

6.1 瞭解事務

事務可由一條很是簡單的sql 語句組成,也能夠有一組複雜的sql 組成。事務是訪問並更新數據庫中各類數據項的一個程序執行單元,在事務中的操做,要麼都作修改,要麼都不作這就是事務的目的。 
事務ACID 的特性

6.1.1 原子性

A (Atomicity),原子性。指整個數據庫事務是不可分割的工做單位。只有使事務中全部的數據庫操做都執行成功,纔算整個事務成功。事務中任何一個SQL 語句執行失敗,已近執行 
成功的sql 語句也必須撤銷。數據庫狀態應該退回到執行事務前的狀態。 

好比ATM 取款流程: 
一、登陸ATM 機平臺,驗證密碼。 
二、從遠程銀行數據庫中,取得帳戶的信息。 
三、用戶在ATM 輸入提取的金額。 
四、從遠程銀行的數據庫中,更新帳戶信息。 
五、ATM 機出款。 
六、用戶取錢。 
整個過程都視爲原子操做,某一個步驟失敗了, 都不能進行下一步。

 

6.1.2 一致性

C (consistency),一致性。一致性定義基本能夠理解爲是事務對數據完整性約束的遵循。這些約束可能包括主鍵約束、外鍵約束或是一些用戶自定義約束。事務執行的先後都是合法的數據狀態,不會違背任何的數據完整性,這就是「一致」的意思。事務是一致性的單位,若是事務中某個動做失敗了,系統就能夠自動撤銷事務——返回事務初始化的狀態。

6.1.3 隔離性

I (isolation),隔離性。隔離性還有其餘的稱呼,如併發控制,可串行化,鎖 等。事務的隔離性要求每一個讀寫事務的對象對其餘事務的操做對象能相互分離,即該事務提交前對其餘事務都不可見。

6.1.4 持久性

D(durability),持久性。事務一旦提交,其結果就是永久性的(寫入了磁盤),即便發生宕機等故障,數據庫也能將數據恢復。須要注意的是,只能從事務自己的角度來保證結果的永久性。 

 

例如:在事務提交後,全部的變化都是永久的,即便當數據庫由於崩潰而須要恢復時,也能保證恢復後提交的數據都不會丟失。但若不是數據庫自己發生故障,而是一些外部的緣由,如RAID卡損壞,天然災害等緣由致使數據庫發生問題,那麼全部提交的數據可能都會丟失。所以持久性保證事務系統的高可靠性,而不是高可用性。對於高可用性的實現,事務自己並不能保證,須要一些系統來共同配合來完成。

6.2 事務的實現

事務的隔離性由鎖來實現。原子性,一致性,持久性經過數據庫的redo log 和undo log 來完成,redo log 成爲重作日誌,用來保證事務的原子性和持久性。 undo log 用來保證事務的一致性。

redo 和 undo 的做用均可以視爲是一種恢復操做,redo 恢復提交事務修改的頁操做,而undo 回滾行記錄到某個特定版本。所以二者記錄的內容不一樣,redo 一般是物理日誌,記錄的是頁的物理修改操做,undo 是邏輯日誌,根據每行記錄進行記錄。

6.2.1 redo

重作日誌(redo log)用來實現事務的持久性,即事務ACID 中的 D。 其中兩部分組成: 一是內存中的重作日誌緩衝(redo log buffer),其實容易丟失的;二是重作日誌文件(redo log file),其是持久的。

InnoDB 是事務的存儲引擎,其經過Force Log at Commit 機制實現事務的持久性,即當事務提交(commit)時,必須先將該事務的全部日誌寫入到重作日誌文件進行持久化,待事務的commit 操做完成纔算完成。這裏的日誌是指重作日誌,在InnoDB 存儲引擎中,由兩部分組成,即redo log 和 undo log 。redo log 用來保證事務的持久性,undo log 用來幫助事務回滾及多版本控制(mvcc)的功能,redo log 基本上都是順序寫的,在數據庫運行不須要對redo log 的文件進行讀取操做。而undo log 是須要進行隨機讀寫的。

爲了確保每第二天志都寫入重作日誌文件,在每次都將重作日誌緩衝寫入重作日誌文件後,InnoDB存儲引擎都須要調用一次fsync 操做。因爲重作日誌文件打開並無使用O_DIRECT選項,所以重作日誌緩衝先寫入文件系統緩衝。爲了確保重作日誌寫入磁盤,必須進行一次fsync 操做。因爲fsync 的效率取決於磁盤的性能,所以磁盤的性能決定了事務的提交的性能,也就是數據庫的性能

InnoDB 存儲引擎容許用戶手工非持久性的狀況發生,以此提升數據庫的性能。 
即當事務提交時,日誌不寫入重作日誌文件,而是等待一個時間週期後再執行fsync 操做。
 

InnoDB 存儲引擎容許用戶手工非持久性的狀況發生,以此提升數據庫的性能。 

即當事務提交時,日誌不寫入重作日誌文件,而是等待一個時間週期後再執行fsync 操做。

用參數 innodb_flush_log_at_trx_commit用來控制重作日誌刷新到磁盤的策略。該參數默認值爲1 

改參數能夠設置值爲 0、一、2

0 : 表示事務提交時不進行寫入重作日誌操做,這個操做僅在master thread 中完成,而在master thread 中每1秒會進行一次重作日誌的fsync 操做。

1 :  表示每一個事務提交時進行寫入到重作日誌。

2 :  表示事務提交時將重作日誌寫入重作日誌文件,但僅寫入文件系統的緩存中,不進行fsync操做。 在這個設置下,當mysql 數據庫發生宕機(就是數據庫服務意外中止)而操做系統不發生宕機是,不會致使事務的丟失。而當操做系統宕機時,重啓數據庫後會丟失未從文件系統緩存刷新到重作日誌文件那部分事務。

6.2.2 undo

1 、基本概念 
重作日誌記錄了事務的行爲,能夠很好地經過其對頁進行」重作」操做,可是事務有時還須要進行回滾操做,這時就須要undo。 所以在對數據庫進行修改時,InnoDB 存儲引擎不但會產生redo,還會產生必定量的undo。這樣若是用戶執行的事務或語句因爲緣由失敗了,又或者用戶用一條rollback 語句請求回滾,就能夠利用這些undo 信息將數據回滾到修改以前的樣子。

redo 存放在重作日誌文件中,與redo 不一樣undo 存放在數據庫內部的一個特殊段(segment)中,這個段稱爲undo 段 。undo 段位於共享表空間內

undo 是 邏輯日誌,所以只是將數據庫邏輯地恢復到原來的樣子。全部修改都被邏輯地取消了,可是數據結構和頁自己在回滾以後可能大不相同 這是由於在多用戶併發系統中,可能會有數十,數百甚至數千個併發事務。數據庫的主要任務就是協調對數據記錄的併發訪問。好比,一個事務在修改當前一個頁中某幾條記錄,同時還有別的事務在對同一個頁中另幾條記錄進行修改。所以,不能將一個頁回滾到事務開始的樣子,由於這樣會影響其餘事務正在進行的工做。

 undo 除了回滾操做,undo 的另外一個做用是mvcc,即在InnoDB 存儲引擎中mvcc 的實現是經過undo 來完成。當用戶讀取一行記錄時,若該記錄已近被其餘事務佔用,當前事務能夠經過undo 讀取以前的行版本信息,以此實現 非鎖定讀取。

最重要的一點是,undo log 會產生redo log ,也就是undo log 的產生會伴隨着redo log 的產生,這是由於undo log 也須要持久性的保護。

二、undo 存儲管理

InnoDB 存儲引擎有rollback segment ,每一個回滾段中記錄了1024 個undo log segment , 而在每一個 undo log segment 段中進行undo 頁的申請。 

InnoDB 支持最大128 個(回滾段)rollback segment ,故其支持同時在線的事務 128 * 1024, 可是這些 rollback segment 都存儲於共享表空間中。能夠經過參數對rollback segment 作 進一步的設置。這些參數包括:

 

innodb_undo_directory 
innodb_undo_logs
innodb_undo_tablespaces

  

 

innodb_undo_directory 用於設置rollback segment 文件所在的路徑。這意味着rollback segment 能夠放在共享表空間之外的位置,便可以設置爲獨立表空間。該參數的默認值爲」.」,表示當前InnoDB存儲引擎的目錄。

innodb_undo_logs 用來設置rollback segment 的個數,默認值爲128

innodb_undo_tablespaces 用來設置構成rollback segment 文件的數量,這樣rollback segment 能夠較爲平均地分佈在多個文件。設置改參數後,會在路勁innodb_undo_directory 看到undo 爲前綴的文件,該文件就表明rollback segment 文件

數據庫初始化後,innodb_undo_tablespaces 就不再能被改動了;默認值爲0,表示不獨立設置undo的tablespace,默認記錄到ibdata中;不然,則在undo目錄下建立這麼多個undo文件,例如假定設置該值爲4,那麼就會建立命名爲undo001~undo004的undo tablespace文件,每一個文件的默認大小爲10M。修改該值會致使Innodb沒法完成初始化,數據庫沒法啓動,可是另兩個參數能夠修改;

6.2.3 purge

delete 和 update 操做可能並不直接刪除原有的數據。

 

 例如執行

delete from z where a=1;

表z 上列a 有彙集索引,列表上有輔助索引,對於上述的delete 操做,在undo log 將主鍵列等於1 的記錄delete flag 設置爲1 ,記錄並無當即刪除,記錄仍是存在B+樹種,其次,對輔助索引上a 等於1 ,b等於1 的記錄一樣沒有作任何處理,甚至沒有產生undo log 。 而真正刪除這行記錄的刪除操做其實被「延時」了,最終在purge 操做中完成。 

purge 用於最終完成delete 和 update 操做由於InnoDB 存儲引擎支持MVCC,因此記錄不能再事務提交時當即進行處理。這時其餘事務可能正在引用這行,故InnoDB 存儲引擎須要保持記錄以前的版本而是否能夠刪除該條記錄經過purge 來進行判斷。若該行記錄已不被任何其餘事務引用,那麼就能夠進行真正的delete 操做。可見,purge 操做是清理以前的delete 和 update 操做, 將上述操做 「最終」 完成。 而實際執行的操做爲delete 操做,清理以前行記錄的版本。

6.2.4 group commit

5.6 版本以前的兩次提交 

若事務爲非只讀事務,則每次事務提交時須要進行一次fsync 操做,以此保證重作日誌都已近寫入磁盤。當數據庫發生宕機時,能夠經過重作日誌進行恢復。雖然固態硬盤的出現提升了磁盤的性能,而後磁盤的rsync 性能是有限的。爲了提升磁盤fsync 的效率,數據庫提供了group commit 的功能,即一次fsync 能夠刷新確保多個事務日誌被寫入文件。  

對於InnoDB 存儲引擎來講, 事務提交時會進行兩個階段的操做: 

一、修改內存中事務對應的信息,而且將日誌寫入重作日誌緩衝。 
二、調用fsync 將確保日誌都從重作日誌緩衝寫入磁盤。

 步驟1 相對 步驟2 是一個較慢的過程,這是由於存儲引擎須要與磁盤打交道。但當有事務進行這個過程是,其餘事務能夠進行 步驟1 的 操做,正在提交的事務完成提交操做, 再次進行步驟 2 時,能夠將多個事務的重作日誌經過一次fsync 刷新到磁盤,這樣就大大減小了磁盤的壓力,從而提升了數據庫的總體性能。對於寫入或更新較爲頻繁的操做,group commit 的效果 尤其明顯。

二段提交流程

1 、InnoDB 的事務 Prepare 階段,即 SQL 已經成功執行並生成 redo 和 undo 的內存日誌;

二、binlog 提交,經過 write() 將 binlog 內存日誌數據寫入文件系統緩存;

三、fsync() 將 binlog 文件系統緩存日誌數據永久寫入磁盤;

四、InnoDB 內部提交,commit 階段在存儲引擎內提交,經過 innodb_flush_log_at_trx_commit 參數控制,使 undo 和 redo 永久寫入磁盤。

組提交 

5.6 引入了組提交,並將提交過程分紅 Flush stage、Sync stage、Commit stage 三個階段。

一、InnoDB, Prepare : SQL已經成功執行並生成了相應的redo和undo內存日誌; 
二、Binlog, Flush Stage :全部已經註冊線程都將寫入binlog緩存; 
三、Binlog, Sync Stage :binlog緩存將sync到磁盤,sync_binlog=1時該隊列中全部事務的binlog將永久寫入磁盤; 
四、 InnoDB, Commit stage: leader根據順序調用存儲引擎提交事務;

每一個 Stage 階段都有各自的隊列,從而使每一個會話的事務進行排隊,提升併發性能。 
若是當一個線程註冊到一個空隊列時,該線程就作爲該隊列的 leader,後註冊到該隊列的線程均爲 follower,後續的操做,都由 leader 控制隊列中 follower 行爲。

https://www.linuxidc.com/Linux/2018-01/150187.htm

參數binlog_max_flush_queue_time 用來控制flush 階段中等待的時間,即便以前的一組事務完成提交,當前一組的事務也不立刻進去sync階段,而是至少須要等待一段時間。這樣作的好處是group commit 的數量更多,然而這也可能會致使事務的相應時間變慢。該參數的默認值爲0,且推薦設置依然爲0。除非用戶的mysql 數據庫系統中 有着大量的鏈接,而且不斷地在進行事務的寫入或更新操做。

注:任何參數都不要隨意設置,看到別人設置參數能解決,爲何個人環境設置就報錯了,看官方的改參數注意事項,各類版本的注意事項,在去相應測試環境實驗一下。

6.3 事務控制語句

在 mysql 命令行的默認設置下,事務都是自動提交(auto commit)的,即執行sql 語句就會立刻執行commit 操做。 

用戶可使用那些事務控制語句。

start transaction | begin :顯示地開啓一個事務(推薦start transaction)

commit:會提交事務,並使得已對數據庫作的全部修改爲爲永久性的

rollback:回滾用戶當前鎖執行的事務,並撤銷正在進行的全部未提交的修改。

savepoint identifer : savepoint 容許在事務中建立一個保存點,一個事務中能夠有多個savepoint。

release savepoint identifier: 刪除一個事務的保存點,當沒有一個保存點執行這句語句時,會拋出一個異常。

rollback to[savepoint] identifer: 這個語句與savepoint 命令一塊兒使用。能夠把事務回滾到標記點,而不會滾在此標記點以前的任何工做。

set transaction: 這個語句用來設置事務的隔離級別。InnoDB 存儲引擎的事務隔離級別有: READ UNCOMMITED、READ COMMITED、REPEATABLE READ、SERIALIZABLE.

  

例:

mysql> create table u (a int primary key);
Query OK, 0 rows affected (0.01 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into u select 1;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0
mysql> savepoint u1;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into u select 2;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0
mysql> savepoint u2;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from u\G;
****** 1. row ******
a: 1
****** 2. row ******
a: 2
2 rows in set (0.00 sec)
mysql> release savepoint u1;
Query OK, 0 rows affected (0.00 sec)
# 回到了第一次插入數據的時候
mysql> insert into u select 2;
ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'
mysql> rollback to savepoint u2;
ERROR 1305 (42000): SAVEPOINT u2 does not exist
mysql> select * from u;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)

# 這時候發現了,rollback to savepoint u1了,
後面的u2 的 事務已近不存在了, 可是兩條記錄的數據還在。
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from u;
Empty set (0.00 sec)

在上面的列子中,雖然在發生重複錯誤後用戶經過rollback to save point u1命令回滾到了保存點u1,可是事務此時沒有結束。在運行命令rollback後,事務纔會完整地回滾  

InnoDB 存儲引擎中的事務都是原子的,這說明下兩種狀況:構成事務的天天語句都會提交(成爲永久),或者全部語句都回滾。這種保護還延伸到單個的語句。一條語句要麼徹底成功。要麼徹底回滾(注意,這裏說的是語句回滾)。所以一條語句失敗並拋出異常時,並不會致使先前已近執行的語句自動回滾。全部的執行都會獲得保留,必須由用戶本身來決定是否對其進行提交或回滾的操做。 

rollback to savepoint 命令並不真正地結束事務。 
commit 和 rollback 纔是真正的結束一個事務

 

6.4 隱式提交的 SQL 語句

如下這些sql 語句 會產品一個隱式的提交操做即執行完這些語句後,會有一個隱式的commit 操做:
1 、DDL 語句

ALTER DATABASE ... UPGRADE DATA DIRECTORY NAME,
ALTER EVENT,ALTER PROCEDURE,ALTER TABLE ,ALTER VIEW,
CREATE DATABASE, CREATE EVENT, CREATE TRIGGER , CREATE VIEW,
DROP DATABASE ,DROP EVENT , DROP INDEX , DROP PROCEDURE , DROP TABLE , DROP TRIGGER , DROP VIEW ,
RENAME TABLE , TRUNCATE TABLE .

二、用來隱式修改 MYSQL 架構的操做  

CREATE USER,DROP USER ,GRANT , RENAME USER ,REVOKE , SET PASSWORD.

三、管理語句  

ANALYZE TABLE,CACHE INDEX, CHECK TABLE ,
LOAD INDEX INTO CACHE,OPTIMEIZE TABLE ,REPAIR TABLE

  

注: 我發現 sql server 的數據庫有些 ddl 也是能夠回滾的。這和 InnoDB 存儲引擎,oracle 這些數據庫徹底不一樣。

truncate table 演示:

mysql> insert into u select 1;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> insert into u select 2;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> select * from u;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> truncate table u;
Query OK, 0 rows affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from u;
Empty set (0.00 sec)

  

6.5 對於事務的操做的統計

因爲InnoDB 存儲引擎是支持事務的,所以InnoDB 存儲引擎的應用須要在考慮每秒請求數(transaction per second ,TPS) 

計算 TPS 的方法時( com_commit + com_rollback)/time 。可是利用這種方法進行計算的前提是:全部的事務必須都是顯示提交的,若是存在隱式提交和回滾(默認autocommit =1 ),不會計算到com_commit 和 com_rollback 變量中。 如;

mysql 數據庫中另外還有兩個參數handler_commit 和 handler_rollback 用於事務的統計操做。能夠很好的用來統計InnoDB 存儲引擎顯式和隱式的事務提交操做。 

在InnoDB Plugin 中這兩個參數的表現有些「怪異」 
若是用戶的程序都是顯示控制事務的提交和回滾,那麼能夠經過com_commit 和 com_rollback 進行統計

6.6 事務的隔離級別

SQL 標準定義的四個隔離級別爲: 
READ UNCOMMITTED 
READ COMMITTED 
REPEATABLE READ 
SERIALIZABLE 

sql server 和oracle 默認的隔離級別是READ COMMITED

 

隔離級別 髒讀 不可重複讀 幻讀
READ UNCOMMITTED 可能 可能 可能
READ COMMITED 不可能 可能 可能
REPEATABLE READ 不可能 不可能 可能
SERIALIZABLE 不可能 不可能 不可能

 

 髒讀: 又稱無效數據的讀出,是指在數據庫訪問中,事務T1將某一值修改,而後事務T2讀取該值,此後T1由於某種緣由撤銷對該值的修改,這就致使了T2所讀取到的數據是無效的。

 不可重複讀:是指在數據庫訪問中,一個事務範圍內兩個相同的查詢卻返回了不一樣數據

 幻讀: 是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,好比這種修改涉及到表中的「所有數據行」。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入「一行新數據」

不可能重複讀和幻讀的區別: 
不少人容易搞混不可重複讀和幻讀,確實這二者有些類似。但不可重複讀重點在於update和delete,而幻讀的重點在於insert。 

在InnoDB 引擎中,可使用一下命令來設置當前會話和全局的事務的隔離級別:

mysql> help isolation;
Name: 'ISOLATION'
Description:
Syntax:
SET [GLOBAL | SESSION] TRANSACTION
    transaction_characteristic [, transaction_characteristic] ...

transaction_characteristic: {
    ISOLATION LEVEL level
  | READ WRITE
  | READ ONLY
}

level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

  

若是想在MySQL 數據啓動時就設置事務的默認隔離級別,那就須要修改mysql 的配置文件 my.cnf 在 [mysqld] 中添加以下行:

[mysqld]
transaction-isolation = REPEATABLE-READ

  

查看當前會話的事務隔離級別,可使用:

mysql> select @@tx_isolation\G;
********** 1. row **********
@@tx_isolation: REPEATABLE-READ
1 row in set, 1 warning (0.00 sec)

  

查看全局的事務隔離級別,可使用:

mysql> select @@global.tx_isolation\G;
******** 1. row ********
@@global.tx_isolation: REPEATABLE-READ
1 row in set, 1 warning (0.00 sec)

  

6.7 很差的事務的習慣

6.7.1 在循環中提交

用存儲過程模擬一下

create table t1 (a int ,b char(100));

建立load1
delimiter //
create procedure load1 (count INT UNSIGNED)
begin
declare s int unsigned default 1;
declare c char(80) default repeat('a',80);
while s <= count do
insert into t1 select null,c;
commit;
set s = s+1;
end while;
end //
delimiter ;

建立load2
delimiter //
create procedure load2 (count int unsigned)
begin
declare s int unsigned default 1;
declare c char(80) default repeat('a',80);
while s <= count do
insert into t1 select null,c;
set s = s+1;
end while;
end //
delimiter ;

建立load3
delimiter //
create procedure load3(count int unsigned)
begin
declare s int unsigned default 1;
declare c char(80) default repeat('a',80);
start transaction;
while s <= count do
insert into t1 select null,c;
set s = s+1;
end while;
commit;
end //
delimiter ;

  

比較這三個存儲過程執行時間:

mysql> call load1(20000);
Query OK, 0 rows affected (16.12 sec)

mysql> truncate table t1;
Query OK, 0 rows affected (0.01 sec)

mysql> call load2(20000);
Query OK, 1 row affected (16.06 sec)

mysql> truncate table t1;
Query OK, 0 rows affected (0.01 sec)

mysql> call load3(20000);
Query OK, 0 rows affected (0.51 sec)

  

注:mysql 默認是自動提交的,load1 和 load2 沒執行一次都會自動提交

顯然,load3 方法要快的多,這是由於每一次提交都要寫一次重作日誌,存儲過程load1 和 load2 實際寫了20000 次重作日誌文件,而對於存儲過程load3 來講,實際只寫了一次。

6.8 長事務

長事務就是執行時間較長的事務。好比對於銀行系統的數據庫,沒過一個階段可能須要更新對應帳戶的利息。若是對應帳號的數量很是大,例如對有1億用戶的表account ,須要執行如下列語句;

update accout set account_total= accoutn_total + (1+inerset_rate)

這是這個事務可能須要很是長的時間來完成。可能須要1個小時,也可能乣4,5個小時,這取決於數據庫的硬件配置。DBA和 開發人員自己能作的事情很是少。然而,因爲事務ACID 的特性,這個操做被封裝在一個事務中完成。這就產生了一個問題,在執行過程當中,當數據庫或操做系統,硬件等發生問題是,從新開始事務的代價變得不可接受。數據庫須要回滾全部已近發生的變化,而這個過程可能比產生這些變化的時間還要長。所以,對於長事務的問題,有時能夠經過轉化爲小批量的事務來進行處理。當事務發生錯誤是,只須要回滾一部分數據,而後接着上次已完成的事務繼續進行

 

注:以上全部操做全是在mysql 5.7.24 版本

相關文章
相關標籤/搜索