Mysql基礎知識整理筆記(事務)

PS:文章整理的知識內容及資料均來自極客時間《SQL必知必會》專欄mysql

MySQL的InnoDB引擎支持事務,MyISAM不支持事務;

事務基礎

事務的4大特性:ACIDsql


  1. A,也就是原子性(Atomicity)。原子的概念就是不可分割,能夠把它理解爲組成物質的基本單位,也是咱們進行數據處理操做的基本單位,換句話說是:要麼徹底執行,要麼全都不執行;
  2. C,就是一致性(Consistency)。一致性指的就是數據庫在進行事務操做後,會由原來的一致狀態,變成另外一種一致的狀態。也就是說當事務提交後,或者當事務發生回滾後,數據庫的完整性約束不能被破壞;
  3. I,就是隔離性(Isolation)。它指的是每一個事務都是彼此獨立的,不會受到其餘事務的執行影響。也就是說一個事務在提交以前,對其餘事務都是不可見的;
  4. D,指的是持久性(Durability)。事務提交以後對數據的修改是持久性的,即便在系統出故障的狀況下,好比系統崩潰或者存儲介質發生故障,數據的修改依然是有效的。由於當事務完成,數據庫的日誌就會被更新,這時能夠經過日誌,讓系統恢復到最後一次成功的更新狀態。
持久性是經過事務日誌來保證的。日誌包括了回滾日誌和重作日誌。當咱們經過事務對數據進行修改的時候,首先會將數據庫的變化信息記錄到重作日誌中,而後再對數據庫中對應的行進行修改。這樣作的好處是,即便數據庫系統崩潰,數據庫重啓後也能找到沒有更新到數據庫系統中的重作日誌,從新執行,從而使事務具備持久性。

事務的經常使用操做語句數據庫


  1. START TRANSACTION 或者 BEGIN,做用是顯式開啓一個事務。
  2. COMMIT:提交事務。當提交事務後,對數據庫的修改是永久性的。
  3. ROLLBACK 或者 ROLLBACK TO [SAVEPOINT],意爲回滾事務。意思是撤銷正在進行的全部沒有提交的修改,或者將事務回滾到某個保存點。
  4. SAVEPOINT:在事務中建立保存點,方便後續針對保存點進行回滾。一個事務中能夠存在多個保存點。
  5. RELEASE SAVEPOINT:刪除某個保存點。
  6. SET TRANSACTION,設置事務的隔離級別。
使用事務有兩種方式,分別爲隱式事務和顯式事務。隱式事務實際上就是自動提交,Oracle 默認不自動提交,須要手寫 COMMIT 命令,而 MySQL 默認自動提交,固然咱們能夠配置 MySQL 的參數:

MySQL 中 completion_type 參數對於事務的做用

  • completion_type=0,這是默認狀況。也就是說當咱們執行 COMMIT 的時候會提交事務,在執行下一個事務時,還須要咱們使用 START TRANSACTION 或者 BEGIN 來開啓。
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO test SELECT '關羽';
COMMIT;
INSERT INTO test SELECT '張飛';
INSERT INTO test SELECT '張飛';
ROLLBACK;
SELECT * FROM test;

運行結果(1 行數據):
26d1f5a4a534eb9b1415ce867f006b80.png服務器

  • completion_type=1,這種狀況下,當咱們提交事務後,至關於執行了 COMMIT AND CHAIN,也就是開啓一個鏈式事務,即當咱們提交事務以後會開啓一個相同隔離級別的事務(隔離級別會在下一節中進行介紹)。
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
SET @@completion_type = 1;
BEGIN;
INSERT INTO test SELECT '關羽';
COMMIT;
INSERT INTO test SELECT '張飛';
INSERT INTO test SELECT '張飛';
ROLLBACK;
SELECT * FROM test;

運行結果(2 行數據):
df437e577ce75363f5eb22dc362dbff7.png併發

  • completion_type=2,這種狀況下 COMMIT=COMMIT AND RELEASE,也就是當咱們提交後,會自動與服務器斷開鏈接。

MySQL事務隔離

20697e62019613bc49f7ed88872b3a5d.png

  • 隔離級別能解決的異常狀況以下表所示:

b07103c5f5486aec5e2daf1dacfd6f95.png

三種異常狀況的特色:
一、髒讀:讀到了其餘事務尚未提交的數據。(側重於未提交的數據)
二、不可重複讀:對某數據進行讀取,發現兩次讀取的結果不一樣,也就是說沒有讀到相同的內容。這是由於有其餘事務對這個數據同時進行了修改或刪除。(側重於數據修改,UPDATE或DELETE)
三、幻讀:事務 A 根據條件查詢獲得了 N 條數據,但此時事務 B 更改或者增長了 M 條符合事務 A 查詢條件的數據,這樣當事務 A 再次進行查詢的時候發現會有 N+M 條數據,產生了幻讀。(側重於數據新增,INSERT)

隔離級別越低,意味着系統吞吐量(併發程度)越大,但同時也意味着出現異常問題的可能性會更大。在實際使用過程當中咱們每每須要在性能和正確性上進行權衡和取捨,沒有完美的解決方案,只有適合與否。高併發

模擬異常狀況就不做記錄了性能

MySQL事務隔離級別的實現

MySQL中的鎖spa


隔離級別的實現是經過鎖來完成的,實際上加鎖是爲了保證數據的一致性,當多個線程併發訪問某個數據的時候,尤爲是針對一些敏感的數據(好比訂單、金額等),咱們就須要保證這個數據在任什麼時候刻最多隻有一個線程在進行訪問,保證數據的完整性和一致性。
  • 樂觀鎖

樂觀鎖大可能是基於數據版本記錄機制實現,通常是給數據庫表增長一個"version"字段。讀取數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。線程

  • 悲觀鎖

悲觀鎖依靠數據庫提供的鎖機制實現。MySQL中的共享鎖和排它鎖都是悲觀鎖。數據庫的增刪改操做默認都會加排他鎖,而查詢不會加任何鎖。3d

  • 共享鎖(讀鎖,S鎖)

共享鎖指的就是對於多個不一樣的事務,對於一個資源共享同一個鎖。對某一資源加共享鎖,自身可可讀該資源,其餘人也能夠讀該資源(也能夠再加共享鎖,即共享鎖共享多個內存),但沒法修改。要想修改就必須等全部共享鎖都釋放完以後。語法:SELECT * FROM table lock in share mode;

  • 排它鎖(寫鎖,X鎖)

排它鎖指的就是對於多個不一樣的事務,對同一個資源只能有一把鎖。對某一資源加排它鎖,自身能夠進行增刪改查,其餘人沒法進行加鎖操做,更沒法進行增刪改操做。語法:select * from table for update。

  • 行鎖

行鎖就是給一行數據進行加鎖,操做對象是數據表中的一行(共享鎖和排他鎖多是行鎖也多是表鎖,取決於對數據加鎖的範圍,是一行仍是整個表)。是MVCC技術用的比較多的,但在MYISAM用不了,行級鎖用mysql的儲存引擎實現而不是mysql服務器。但行級鎖對系統開銷較大,處理高併發較好。

InnoDB行鎖的3種方式:
一、記錄鎖:針對單個行記錄加鎖;
二、間隙鎖:鎖住一個範圍(索引之間的空隙),但不包括記錄自己,可防止幻讀;
三、NEXT-KEY鎖:鎖住一個範圍,包括記錄自己,至關於間隙鎖+記錄鎖,可防止幻讀
  • 表鎖

表鎖就是對一張表進行加鎖,操做對象是數據表。Mysql大多數鎖策略都支持(常見mysql innodb),是系統開銷最低但併發性最低的一個鎖策略。事務t對整個表加讀鎖,則其餘事務可讀不可寫,若加寫鎖,則其餘事務增刪改都不行。

  • 意向鎖

意向鎖(Intent Lock),簡單來講就是給更大一級別的空間示意裏面是否已經上過鎖。舉個例子,若是咱們給某一行數據加上了鎖,數據庫會自動給更大一級的空間,好比數據頁或數據表加上意向鎖,告訴其餘人這個數據頁或數據表已經有人上過鎖了,這樣當其餘人想要獲取數據表的鎖的時候,只須要了解是否有人已經獲取了這個數據表的意向鎖便可,而不須要逐條記錄去判斷是否有鎖。

MySQL的MVCC(多版本併發控制)


  1. 經過 MVCC 可讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就能夠提高事務併發處理能力。
  2. 下降了死鎖的機率。這是由於 MVCC 採用了樂觀鎖的方式,讀取數據時並不須要加鎖,對於寫操做,也只鎖定必要的行。
  3. 解決一致性讀的問題。一致性讀也被稱爲快照讀,當咱們查詢數據庫在某個時間點的快照時,只能看到這個時間點以前事務提交更新的結果,而不能看到這個時間點以後事務提交的更新結果。

快照讀和當前讀

  • 快照讀

不加鎖的簡單的 SELECT 都屬於快照讀:

SELECT * FROM table WHERE ...
  • 當前讀

當前讀就是讀取最新數據,而不是歷史版本的數據。加鎖的 SELECT,或者對數據進行增刪改都會進行當前讀:

SELECT * FROM table LOCK IN SHARE MODE;
SELECT * FROM table FOR UPDATE;
INSERT INTO table values ...;
DELETE FROM table WHERE ...;
UPDATE table SET ...;

MVCC 的核心:Undo Log(MV) + Read View(CC)

InnoDB 中 MVCC 的數據包括事務版本號行記錄中的隱藏列Undo Log

  • 事務版本號

每開啓一個事務,咱們都會從數據庫中得到一個事務 ID(也就是事務版本號),這個事務 ID 是自增加的,經過 ID 大小,咱們就能夠判斷事務的時間順序。

  • 行記錄中的隱藏列
  1. db_row_id:隱藏的行 ID,用來生成默認彙集索引。若是咱們建立數據表的時候沒有指定彙集索引,這時 InnoDB 就會用這個隱藏 ID 來建立彙集索引。採用彙集索引的方式能夠提高數據的查找效率。
  2. db_trx_id:操做這個數據的事務 ID,也就是最後一個對該數據進行插入或更新的事務 ID。
  3. db_roll_ptr:回滾指針,也就是指向這個記錄的 Undo Log 信息。

clipboard.png

  • Undo Log

InnoDB 將行記錄快照保存在了 Undo Log 裏,咱們能夠在回滾段中找到它們,以下圖所示:

clipboard.png

從圖中能看到回滾指針將數據行的全部快照記錄都經過鏈表的結構串聯了起來,每一個快照的記錄都保存了當時的 db_trx_id,也是那個時間點操做這個數據的事務 ID。這樣若是咱們想要找歷史快照,就能夠經過遍歷回滾指針的方式進行查找。

  • Read View

在 MVCC 機制中,多個事務對同一個行記錄進行更新會產生多個歷史快照,這些歷史快照保存在 Undo Log 裏。若是一個事務想要查詢這個行記錄,須要讀取哪一個版本的行記錄呢?這時就須要用到 Read View 了,它幫咱們解決了行的可見性問題。Read View 保存了當前事務開啓時全部活躍(尚未提交)的事務列表,換個角度你能夠理解爲 Read View 保存了不該該讓這個事務看到的其餘的事務 ID 列表。

Read VIew 幾個重要的屬性:

  1. trx_ids,系統當前正在活躍的事務 ID 集合。
  2. low_limit_id,活躍的事務中最大的事務 ID。
  3. up_limit_id,活躍的事務中最小的事務 ID。
  4. creator_trx_id,建立這個 Read View 的事務 ID。

如圖所示,trx_ids 爲 trx二、trx三、trx5 和 trx8 的集合,活躍的最大事務 ID(low_limit_id)爲 trx8,活躍的最小事務 ID(up_limit_id)爲 trx2。

clipboard.png

假設當前的事務 creator_trx_id 想要讀取某個行記錄,這個行記錄的事務 ID 爲 trx_id,那麼會出現如下幾種狀況:

  1. 若是 trx_id < 活躍的最小事務 ID(up_limit_id),也就是說這個行記錄在這些活躍的事務建立以前就已經提交了,那麼這個行記錄對該事務是可見的。
  2. 若是 trx_id > 活躍的最大事務 ID(low_limit_id),這說明該行記錄在這些活躍的事務建立以後才建立,那麼這個行記錄對當前事務不可見。
  3. 若是 up_limit_id < trx_id < low_limit_id,說明該行記錄所在的事務 trx_id 在目前 creator_trx_id 這個事務建立的時候,可能還處於活躍的狀態,所以咱們須要在 trx_ids 集合中進行遍歷,若是 trx_id 存在於 trx_ids 集合中,證實這個事務 trx_id 還處於活躍狀態,不可見。不然,若是 trx_id 不存在於 trx_ids 集合中,證實事務 trx_id 已經提交了,該行記錄可見。

當查詢一條記錄的時候,使用多版本併發控制技術找到對應記錄的過程:

  1. 首先獲取事務本身的版本號,也就是事務 ID(creator_trx_id);
  2. 使用creator_trx_id獲取 Read View;
  3. 查詢獲得的數據,而後與 Read View 中的事務版本號進行比較;
  4. 若是不符合 Read View 規則,就須要從 Undo Log 中獲取歷史快照;
  5. 最後返回符合規則的數據。
InnoDB 中,MVCC 是經過 Undo Log + Read View 進行數據讀取,Undo Log 保存了歷史快照,而 Read View 規則幫咱們判斷當前版本的數據是否可見。
  • 在隔離級別爲讀已提交(Read Commit)時,一個事務中的每一次 SELECT 查詢都會獲取一次 Read View。如表所示:

clipboard.png

在讀已提交的隔離級別下,一樣的查詢語句都會從新獲取一次 Read View,這時若是 Read View 不一樣,就可能產生不可重複讀或者幻讀的狀況。

  • 當隔離級別爲可重複讀的時候,就避免了不可重複讀,這是由於一個事務只在第一次 SELECT 的時候會獲取一次 Read View,然後面全部的 SELECT 都會複用這個 Read View,以下表所示:

clipboard.png

InnoDB解決幻讀的方法:NEXT-KEY鎖 + MVCC

  • 在讀已提交的狀況下,即便採用了 MVCC 方式也會出現幻讀。

若是咱們同時開啓事務 A 和事務 B,先在事務 A 中進行某個條件範圍的查詢,讀取的時候採用排它鎖,在事務 B 中增長一條符合該條件範圍的數據,並進行提交,而後咱們在事務 A 中再次查詢該條件範圍的數據,就會發現結果集中多出一個符合條件的數據,這樣就出現了幻讀。出現幻讀的緣由是在讀已提交的狀況下,InnoDB 只採用了記錄鎖(Record Locking:即只鎖定對應的行記錄)。

clipboard.png

  • 在隔離級別爲可重複讀時,InnoDB 會採用 Next-Key 鎖的機制,幫咱們解決幻讀問題。

咱們能看到當咱們想要插入球員艾利克斯·倫(身高 2.16 米)的時候,事務 B 會超時,沒法插入該數據。這是由於採用了 Next-Key 鎖,會將 height>2.08 的範圍都進行鎖定,就沒法插入符合這個範圍的數據了。而後事務 A 從新進行條件範圍的查詢,就不會出現幻讀的狀況。

clipboard.png

相關文章
相關標籤/搜索