PS:文章整理的知識內容及資料均來自極客時間《SQL必知必會》專欄mysql
MySQL的InnoDB引擎支持事務,MyISAM不支持事務;
事務的4大特性:ACIDsql
持久性是經過事務日誌來保證的。日誌包括了回滾日誌和重作日誌。當咱們經過事務對數據進行修改的時候,首先會將數據庫的變化信息記錄到重作日誌中,而後再對數據庫中對應的行進行修改。這樣作的好處是,即便數據庫系統崩潰,數據庫重啓後也能找到沒有更新到數據庫系統中的重作日誌,從新執行,從而使事務具備持久性。
事務的經常使用操做語句數據庫
使用事務有兩種方式,分別爲隱式事務和顯式事務。隱式事務實際上就是自動提交,Oracle 默認不自動提交,須要手寫 COMMIT 命令,而 MySQL 默認自動提交,固然咱們能夠配置 MySQL 的參數:
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 行數據):
服務器
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 行數據):
併發
三種異常狀況的特色:
一、髒讀:讀到了其餘事務尚未提交的數據。(側重於未提交的數據)
二、不可重複讀:對某數據進行讀取,發現兩次讀取的結果不一樣,也就是說沒有讀到相同的內容。這是由於有其餘事務對這個數據同時進行了修改或刪除。(側重於數據修改,UPDATE或DELETE)
三、幻讀:事務 A 根據條件查詢獲得了 N 條數據,但此時事務 B 更改或者增長了 M 條符合事務 A 查詢條件的數據,這樣當事務 A 再次進行查詢的時候發現會有 N+M 條數據,產生了幻讀。(側重於數據新增,INSERT)隔離級別越低,意味着系統吞吐量(併發程度)越大,但同時也意味着出現異常問題的可能性會更大。在實際使用過程當中咱們每每須要在性能和正確性上進行權衡和取捨,沒有完美的解決方案,只有適合與否。高併發
模擬異常狀況就不做記錄了性能
MySQL中的鎖spa
隔離級別的實現是經過鎖來完成的,實際上加鎖是爲了保證數據的一致性,當多個線程併發訪問某個數據的時候,尤爲是針對一些敏感的數據(好比訂單、金額等),咱們就須要保證這個數據在任什麼時候刻最多隻有一個線程在進行訪問,保證數據的完整性和一致性。
樂觀鎖大可能是基於數據版本記錄機制實現,通常是給數據庫表增長一個"version"字段。讀取數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。線程
悲觀鎖依靠數據庫提供的鎖機制實現。MySQL中的共享鎖和排它鎖都是悲觀鎖。數據庫的增刪改操做默認都會加排他鎖,而查詢不會加任何鎖。3d
共享鎖指的就是對於多個不一樣的事務,對於一個資源共享同一個鎖。對某一資源加共享鎖,自身可可讀該資源,其餘人也能夠讀該資源(也能夠再加共享鎖,即共享鎖共享多個內存),但沒法修改。要想修改就必須等全部共享鎖都釋放完以後。語法:SELECT * FROM table lock in share mode;
排它鎖指的就是對於多個不一樣的事務,對同一個資源只能有一把鎖。對某一資源加排它鎖,自身能夠進行增刪改查,其餘人沒法進行加鎖操做,更沒法進行增刪改操做。語法:select * from table for update。
行鎖就是給一行數據進行加鎖,操做對象是數據表中的一行(共享鎖和排他鎖多是行鎖也多是表鎖,取決於對數據加鎖的範圍,是一行仍是整個表)。是MVCC技術用的比較多的,但在MYISAM用不了,行級鎖用mysql的儲存引擎實現而不是mysql服務器。但行級鎖對系統開銷較大,處理高併發較好。
InnoDB行鎖的3種方式:
一、記錄鎖:針對單個行記錄加鎖;
二、間隙鎖:鎖住一個範圍(索引之間的空隙),但不包括記錄自己,可防止幻讀;
三、NEXT-KEY鎖:鎖住一個範圍,包括記錄自己,至關於間隙鎖+記錄鎖,可防止幻讀
表鎖就是對一張表進行加鎖,操做對象是數據表。Mysql大多數鎖策略都支持(常見mysql innodb),是系統開銷最低但併發性最低的一個鎖策略。事務t對整個表加讀鎖,則其餘事務可讀不可寫,若加寫鎖,則其餘事務增刪改都不行。
意向鎖(Intent Lock),簡單來講就是給更大一級別的空間示意裏面是否已經上過鎖。舉個例子,若是咱們給某一行數據加上了鎖,數據庫會自動給更大一級的空間,好比數據頁或數據表加上意向鎖,告訴其餘人這個數據頁或數據表已經有人上過鎖了,這樣當其餘人想要獲取數據表的鎖的時候,只須要了解是否有人已經獲取了這個數據表的意向鎖便可,而不須要逐條記錄去判斷是否有鎖。
MySQL的MVCC(多版本併發控制)
不加鎖的簡單的 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 ...;
InnoDB 中 MVCC 的數據包括事務版本號、行記錄中的隱藏列和Undo Log。
每開啓一個事務,咱們都會從數據庫中得到一個事務 ID(也就是事務版本號),這個事務 ID 是自增加的,經過 ID 大小,咱們就能夠判斷事務的時間順序。
InnoDB 將行記錄快照保存在了 Undo Log 裏,咱們能夠在回滾段中找到它們,以下圖所示:
從圖中能看到回滾指針將數據行的全部快照記錄都經過鏈表的結構串聯了起來,每一個快照的記錄都保存了當時的 db_trx_id,也是那個時間點操做這個數據的事務 ID。這樣若是咱們想要找歷史快照,就能夠經過遍歷回滾指針的方式進行查找。
在 MVCC 機制中,多個事務對同一個行記錄進行更新會產生多個歷史快照,這些歷史快照保存在 Undo Log 裏。若是一個事務想要查詢這個行記錄,須要讀取哪一個版本的行記錄呢?這時就須要用到 Read View 了,它幫咱們解決了行的可見性問題。Read View 保存了當前事務開啓時全部活躍(尚未提交)的事務列表,換個角度你能夠理解爲 Read View 保存了不該該讓這個事務看到的其餘的事務 ID 列表。
Read VIew 幾個重要的屬性:
如圖所示,trx_ids 爲 trx二、trx三、trx5 和 trx8 的集合,活躍的最大事務 ID(low_limit_id)爲 trx8,活躍的最小事務 ID(up_limit_id)爲 trx2。
假設當前的事務 creator_trx_id 想要讀取某個行記錄,這個行記錄的事務 ID 爲 trx_id,那麼會出現如下幾種狀況:
當查詢一條記錄的時候,使用多版本併發控制技術找到對應記錄的過程:
InnoDB 中,MVCC 是經過 Undo Log + Read View 進行數據讀取,Undo Log 保存了歷史快照,而 Read View 規則幫咱們判斷當前版本的數據是否可見。
在讀已提交的隔離級別下,一樣的查詢語句都會從新獲取一次 Read View,這時若是 Read View 不一樣,就可能產生不可重複讀或者幻讀的狀況。
若是咱們同時開啓事務 A 和事務 B,先在事務 A 中進行某個條件範圍的查詢,讀取的時候採用排它鎖,在事務 B 中增長一條符合該條件範圍的數據,並進行提交,而後咱們在事務 A 中再次查詢該條件範圍的數據,就會發現結果集中多出一個符合條件的數據,這樣就出現了幻讀。出現幻讀的緣由是在讀已提交的狀況下,InnoDB 只採用了記錄鎖(Record Locking:即只鎖定對應的行記錄)。
咱們能看到當咱們想要插入球員艾利克斯·倫(身高 2.16 米)的時候,事務 B 會超時,沒法插入該數據。這是由於採用了 Next-Key 鎖,會將 height>2.08 的範圍都進行鎖定,就沒法插入符合這個範圍的數據了。而後事務 A 從新進行條件範圍的查詢,就不會出現幻讀的狀況。