mysql 應該知道的知識

1、什麼是鎖

 

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

對於任何一種數據庫來說都需要有相應的鎖定機制,所以 MySQL 自然也不能例外。

MySQL 數據庫由於其自身架構的特點,存在多種數據存儲引擎,每種存儲引擎所針對的應用場景特點都不太一樣,爲了滿足各自特定應用場景的需求,每種存儲引擎的鎖定機制都是爲各自所面對的特定場景而優化設計,所以各存儲引擎的鎖定機制也有較大區別。

MySQL 常用存儲引擎(MyISAM,InnoDB)用了兩種類型(級別)的鎖定機制:表級鎖定,行級鎖定。

 

1)表級鎖 

表級別的鎖定是 MySQL 各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特點是實現邏輯非常簡單,帶來的系統負面影響最小。所以獲取鎖和釋放鎖的速度很快。由於表級鎖一次會將整個表鎖定,所以可以很好的避免困擾我們的死鎖問題。 

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

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

2)行級鎖 

行級鎖定最大的特點就是鎖定對象的顆粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。由於鎖定顆粒度很小,所以發生鎖定資源爭用的概率也最小,能夠給予應用程序儘可能大的併發處理能力而提高一些需要高併發應用系統的整體性能。 

雖然能夠在併發處理能力上面有較大的優勢,但是行級鎖定也因此帶來了不少弊端。由於鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖需要做的事情也更多,帶來的消耗自然也就更大了。此外,行級鎖定也最容易發生死鎖。 

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

總結如下: 

 

  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低; 

  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高;

 

例如,下圖只是對 myisam 表修改一行記錄:

 

其他 insert 操作就需要等待上個 update 語句執行完成,再執行 insert 操作,這時候就會產生表鎖。

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:意向排它鎖

 

 

  • 排它鎖(X):與任何鎖都不兼容

  • 共享鎖(S):只兼容共享鎖和意向共享鎖 

  • 意向鎖(IS,IX): 互相兼容,行級別的鎖只兼容共享鎖

 

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。

 

4、一致性非鎖定讀

 

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

唯一例外的情況,是在首次讀時間點之前的本事務未提交的修改數據可以讀取到。

在讀取提交數據隔離級別下,一致讀的每個讀取操作都會有自己的鏡像。一致讀操作不會施加任何的鎖,所以就不會阻止其他事務的修改動作。

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

 

 

一致讀在某些 DDL 語句下不生效: 

 

  • 碰到 drop table 語句時,由於 InnoDB 不能使用被 drop 的表,所以無法實現一致讀 。

  • 碰到 alter table 語句時,也無法實現一致讀 。

  • 當碰到 insert into… select,update … select 和 create table … select 語句時,在默認的事務隔離級別下,語句的執行更類似於在讀取提交數據的隔離級別下。

 

5、自增長與鎖

 

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

 

插入操作會依據這個自增長的計數器加 1 賦予自增長列。這個實現方式稱作 AUTO-INC Locking(自增鎖)。 這種自增鎖是採用一種特殊的表鎖機制,爲了提高插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增長值插入的sql 語句後立即釋放。 

 

AUTO-INC Locking 從一定程度上提高了併發插入的效率,但還是存在一些性能上的問題。

 

  • 首先,對於有自增長值的列的併發插入性能較差,事務必須等待前一個插入完成。 

  • 其次,對於 insert …select 的大數據量的插入會影響插入的性能,因爲另一個事務中插入會被阻塞。 

 

Innodb_autoinc_lock_mode 來控制自增長的模式,改參數的默認值爲1。

 

 

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

 

插入類型的分類:

 

 

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的主從數據的一致。

 

二、行鎖的幾種算法

 

  • Record Lock:單個行記錄上的鎖 。

  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身。

  • Next-Key Lock:Gap Lock + Record Lock,鎖定一個範圍,並且鎖定記錄本身。

  • Insert Intention Locks:插入意向鎖。

 

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、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 走的索引爲非唯一索引(無論是等值檢索還是範圍檢索) 

  • 事務隔離級別爲 REPEATABLE-READ,innodb_locks_unsafe_for_binlog 參數爲0,且 sql 是一個範圍的當前讀操作,這時即使不是非唯一索引也會加 gap lock

 

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

 

可以通過兩種方式來關閉 Gap Lock: 

 

  • 將事務的隔離級別設置爲 READ COMMITTED 

  • 將參數 innodb_locks_unsafe_for_binlog 設置爲 1

 

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 ,即僅鎖住索引本身,而不是範圍。

 

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 鎖。

 

三、鎖問題

 

通過鎖機制可以實現事務的隔離性要求,使得事務可以併發地工作。鎖提高了併發,但是也有有潛在的問題。不過好在因爲事務隔離性的要求,鎖只會帶來三種問題,如果可以防止這三種情況的發生,哪將不會產生併發異常。 

 

1、髒讀

 

先了解髒數據、髒頁、髒讀。 

 

髒頁:指的是在緩衝池中已近被修改的頁,但是還沒有刷新到磁盤中,即數據庫實例內存中的頁和磁盤中的頁數據是不一致的,當然在刷新到磁盤之前,日誌都已經被寫入到了重做日誌文件中。 

 

髒數據:是指事務對緩衝池中行記錄的修改,並且還沒有被提交。

 

對於髒頁的讀取,是非常正常的。髒頁是因爲數據庫實例內存和磁盤的異步造成的,這並不影響數據的一致性(或者說兩者最終會達到一致性,即當髒頁都刷到磁盤)。並且因爲髒頁的刷新是異步的,不影響數據庫的可用性,因此可以帶來性能的提高。

 

髒數據:是指未提交的數據,如果讀到髒數據,即一個事務可以讀到另外一個事務中未提交的數據,則顯然違反了數據庫的隔離性。 

 

髒讀:指的就是在不同的事務下,當前事務可以讀到另外事務未提交的數據,簡單來說就是可以讀到髒數據。

 

髒讀示例:

 

create table t (a int primary key);

insert into t values (1);

 

 

 

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

 

2、不可重複讀

 

不可重讀是在一個事務內讀取同一數據集合。在這個事務還沒有結束時,另外一個事務也訪問同一數據集合,並做了一些 DML 操作。因此在第一個事務中的兩次讀取數據之間,由於第二個事務的修改,那麼第一個事務兩次讀到的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的情況,這種情況稱爲不可重複讀。

 

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

 

3、丟失更新

 

雖然數據庫能阻止更新問題的產生,但是在生產應用還有另一個邏輯意義丟失更新問題,而導致該問題的並不是因爲數據庫本身的問題。實際上,在所有多用戶計算機系統環境下都有可能產生這個問題。比如下面的情況: 

 

比如一個用戶賬號中有 10000 元,他用兩個網上銀行的客戶端分別進行轉賬操作,第一次轉賬 9000 人民幣,因爲網絡和數據的關係,這時需要等待。

 

但是這時用戶操作另一個網上銀行客戶端,轉賬 1 元,如果最終兩筆操作都成功了,用戶賬號的餘款是 9999 元,第一次轉的 9000 人民幣並沒有得到更新,但是在轉賬的另一個賬號卻會收到這 9000 元,這導致了結果就是錢變多,而賬不平。

 

但是銀行了也很聰明啊,個人網銀綁定 usb key 的,不會發生這種情況的。是的,通過 usb key 登錄也許可以解決這個問題。但是更重要的是在數據庫層解決這個問題,避免任何可能發生丟失更新的情況。

 

要避免丟失更新發生 ,需要讓事務在這種情況下的操作變成串行化,而不是並行的操作。

 

四、鎖阻塞

 

因爲不同鎖之間的兼容性關係,在有些時刻一個事務中的鎖需要等待另一個事務中的鎖釋放它所佔用的資源,這就是阻塞。阻塞並不是一件壞事,其實爲了確保事務可以併發正常地運行。

 

在 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;

 

五、死鎖

 

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

 

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 默認開啓了死鎖保護機制:

 

 

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;

 

 

3、避免死鎖發生的方法

 

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

 

查看死鎖的方法有兩種: 

 

  • 通過 show engine innodb status 命令可以查看最後一個死鎖的情況。

  • 通過 innodb_print_all_deadlocks 參數配置可以將所有死鎖的信息都打印到 MySQL 的錯誤日誌中。

 

減少死鎖發生的方法: 

 

  • 儘可能的保持事務小型化,減少事務執行的時間可以減少發生影響的概率。

  • 及時執行 commit 或者 rollback,來儘快的釋放鎖。

  • 當要訪問多個表數據或者要訪問相同表的不同行集合時,儘可能的保證每次訪問的順序是相同的。比如可以將多個語句封裝在存儲過程中,通過調用同一個存儲過程的方法可以減少死鎖的發生。

  • 增加合適的索引以便語句執行所掃描的數據範圍足夠小。

  • 儘可能的少使用鎖,比如如果可以承擔幻讀的情況,則直接使用 select 語句,而不要使用 select…for update 語句。

  • 如果沒有其他更好的選擇,則可以通過施加表級鎖將事務執行串行化,最大限度的限制死鎖發生。

 

六、事務

 

事務的主要目的了:事務會把數據庫從一種一致狀態轉換爲另一種一致狀態。在數據庫提交工作是,可以確保要麼所有修改都已近保存了,要麼所有修改都不保存。

 

InnoDB 存儲引擎中的事務完全符合 ACID 的特性:

 

  • 原子性 (atomicity) 

  • 一致性(consistency) 

  • 隔離性(isolation) 

  • 持久性(durability)

 

1、瞭解事務

 

事務可由一條非常簡單的 SQL 語句組成,也可以有一組複雜的 SQL 組成。事務是訪問並更新數據庫中各種數據項的一個程序執行單元,在事務中的操作,要麼都做修改,要麼都不做這就是事務的目的。 

 

事務 ACID 的特性:

 

A (Atomicity),原子性:

 

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

 

比如 ATM 取款流程: 

 

  • 登錄 ATM 機平臺,驗證密碼。 

  • 從遠程銀行數據庫中,取得賬戶的信息。 

  • 用戶在 ATM 輸入提取的金額。 

  • 從遠程銀行的數據庫中,更新賬戶信息。 

  • ATM 機出款。 

  • 用戶取錢。 

 

整個過程都視爲原子操作,某一個步驟失敗了,都不能進行下一步。

 

C (consistency),一致性:

 

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

 

I (isolation),隔離性:

 

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

 

D(durability),持久性:

 

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

 

例如:在事務提交後,所有的變化都是永久的,即使當數據庫因爲崩潰而需要恢復時,也能保證恢復後提交的數據都不會丟失。

 

但若不是數據庫本身發生故障,而是一些外部的原因,如 RAID 卡損壞,自然災害等原因導致數據庫發生問題,那麼所有提交的數據可能都會丟失。

 

因此持久性保證事務系統的高可靠性,而不是高可用性。對於高可用性的實現,事務本身並不能保證,需要一些系統來共同配合來完成。

 

2、事務的實現

 

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

 

redo 和 undo 的作用都可以視爲是一種恢復操作,redo 恢復提交事務修改的頁操作,而 undo 回滾行記錄到某個特定版本。因此兩者記錄的內容不同,redo 通常是物理日誌,記錄的是頁的物理修改操作,undo 是邏輯日誌,根據每行記錄進行記錄。

 

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_flush_log_at_trx_commit 用來控制重做日誌刷新到磁盤的策略。該參數默認值爲1。

 

改參數可以設置值爲 0、1、2

 

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

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

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

 

2)undo

 

重做日誌記錄了事務的行爲,可以很好地通過其對頁進行“重做”操作,但是事務有時還需要進行回滾操作,這時就需要 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無法完成初始化,數據庫無法啓動,但是另兩個參數可以修改。

 

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 操作,清理之前行記錄的版本。

 

4)group commit

 

5.6 版本之前的兩次提交 :

 

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

 

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

 

  • 修改內存中事務對應的信息,並且將日誌寫入重做日誌緩衝。 

  • 調用fsync 將確保日誌都從重做日誌緩衝寫入磁盤。

 

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

 

二段提交流程:

 

 

  • 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 數據庫系統中有着大量的連接,並且不斷地在進行事務的寫入或更新操作。

 

 

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

 

3、事務控制語句

 

在 MySQLl 命令行的默認設置下,事務都是自動提交(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 纔是真正的結束一個事務

 

4、隱式提交的SQL語句

 

以下這些 SQL 語句會產品一個隱式的提交操作即執行完這些語句後,會有一個隱式的 commit 操作:

 

DDL 語句:

 

ALTER DATABASE ... UPGRADE DATA DIRECTORY NAME,<br data-filtered="filtered">ALTER EVENT,ALTER PROCEDURE,ALTER TABLE ,ALTER VIEW, <br data-filtered="filtered">CREATE DATABASE, CREATE EVENT, CREATE TRIGGER , CREATE VIEW, <br data-filtered="filtered">DROP DATABASE ,DROP EVENT , DROP INDEX , DROP PROCEDURE , DROP TABLE , DROP TRIGGER , DROP VIEW ,<br data-filtered="filtered">RENAME TABLE , TRUNCATE TABLE .

 

用來隱式修改 MySQL 架構的操作: 

 

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

 

管理語句:

 

ANALYZE TABLE,CACHE INDEX, CHECK TABLE ,<br data-filtered="filtered">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)

 

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、事務的隔離級別

 

SQL 標準定義的四個隔離級別爲: 

 

  • READ UNCOMMITTED 

  • READ COMMITTED 

  • REPEATABLE READ 

  • SERIALIZABLE 

 

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

 

 

  • 髒讀:又稱無效數據的讀出,是指在數據庫訪問中,事務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)

 

7、不好的事務的習慣

 

在循環中提交

 

用存儲過程模擬一下:

 

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 來說,實際只寫了一次。

 

8、長事務

 

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

 

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

 

這是這個事務可能需要非常長的時間來完成。可能需要 1 個小時,也可能 4、5 個小時,這取決於數據庫的硬件配置。DBA 和開發人員本身能做的事情非常少。

 

然而,由於事務 ACID 的特性,這個操作被封裝在一個事務中完成。這就產生了一個問題,在執行過程中,當數據庫或操作系統,硬件等發生問題是,重新開始事務的代價變得不可接受。

 

數據庫需要回滾所有已近發生的變化,而這個過程可能比產生這些變化的時間還要長。因此,對於長事務的問題,有時可以通過轉化爲小批量的事務來進行處理。當事務發生錯誤是,只需要回滾一部分數據,然後接着上次已完成的事務繼續進行。

 

注:以上所有操作全是在 MySQL 5.7.24 版本

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章