京東資深架構師深度解析《 MySQL 實戰(zhàn) 》

基礎架構:一條 SQL 查詢語句是如何執(zhí)行的?

MySQL 的基本架構示意圖:

MySQL 可以分為 Server 層和存儲引擎層兩部分残邀。

Server 層包括連接器皆辽、查詢緩存、分析器芥挣、優(yōu)化器驱闷、執(zhí)行器等,涵蓋 MySQL 的大多數核心服務功能空免,以及所有的內置函數(如日期空另、時間、數學和加密函數等)蹋砚,所有跨存儲引擎的功能都在這一層實現(xiàn)扼菠,比如存儲過程、觸發(fā)器都弹、視圖等娇豫。

存儲引擎層負責數據的存儲和提取匙姜。其架構模式是插件式的畅厢,支持 InnoDB、MyISAM氮昧、Memory 等多個存儲引擎】蚨牛現(xiàn)在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎袖肥。

不同存儲引擎的表數據存取方式不同咪辱,支持的功能也不同。不同的存儲引擎共用一個Server 層椎组,也就是從連接器到執(zhí)行器的部分油狂。

連接器

連接器負責跟客戶端建立連接、獲取權限、維持和管理連接专筷。

查詢緩存

MySQL 拿到一個查詢請求后弱贼,會先到查詢緩存看看,之前是不是執(zhí)行過這條語句磷蛹。之前執(zhí)行過的語句及其結果可能會以 key-value 對的形式吮旅,被直接緩存在內存中。key 是查詢的語句味咳,value 是查詢的結果庇勃。如果你的查詢能夠直接在這個緩存中找到 key,那么這個 value 就會被直接返回給客戶端槽驶。

但是大多數情況下我會建議你不要使用查詢緩存责嚷,為什么呢?因為查詢緩存往往弊大于利捺檬。

查詢緩存的失效非常頻繁再层,只要有對一個表的更新,這個表上所有的查詢緩存都會被清空堡纬。因此很可能你費勁地把結果存起來聂受,還沒使用呢,就被一個更新全清空了烤镐。對于更新壓力大的數據庫來說蛋济,查詢緩存的命中率會非常低。除非你的業(yè)務就是有一張靜態(tài)表炮叶,很長時間才會更新一次碗旅。比如,一個系統(tǒng)配置表镜悉,那這張表上的查詢才適合使用查詢緩存祟辟。

MySQL 8.0 版本直接將查詢緩存的整塊功能刪掉了,也就是說 8.0 開始徹底沒有這個功能了侣肄。

分析器

如果沒有命中查詢緩存旧困,就要開始真正執(zhí)行語句了。

分析器先會做“詞法分析”稼锅。做完了這些識別以后吼具,就要做“語法分析”。

優(yōu)化器

優(yōu)化器是在表里面有多個索引的時候矩距,決定使用哪個索引拗盒;或者在一個語句有多表關聯(lián)(join)的時候,決定各個表的連接順序锥债。

優(yōu)化器階段完成后陡蝇,這個語句的執(zhí)行方案就確定下來了痊臭,然后進入執(zhí)行器階段。

執(zhí)行器

打開表的時候登夫,執(zhí)行器就會根據表的引擎定義趣兄,去使用這個引擎提供的接口。

你會在數據庫的慢查詢日志中看到一個 rows_examined 的字段悼嫉,表示這個語句執(zhí)行過程中掃描了多少行艇潭。這個值就是在執(zhí)行器每次調用引擎獲取數據行的時候累加的。

在有些場景下戏蔑,執(zhí)行器調用一次蹋凝,在引擎內部則掃描了多行,因此引擎掃描行數跟 rows_examined 并不是完全相同的总棵。

我給你留一個問題吧鳍寂,如果表 T 中沒有字段 k,而你執(zhí)行了這個語句 select * from T where k=1, 那肯定是會報“不存在這個列”的錯誤: “Unknown column ‘k’ in ‘where clause’”情龄。你覺得這個錯誤是在我們上面提到的哪個階段報出來的呢迄汛?

答案是分析器。

日志系統(tǒng):一條 SQL 更新語句是如何執(zhí)行的骤视?

與查詢流程不一樣的是鞍爱,更新流程還涉及兩個重要的日志模塊,它們正是我們今天要討論的主角:redo log(重做日志)和 binlog(歸檔日志)专酗。

重要的日志模塊:redo log

MySQL 里經常說到的 WAL 技術睹逃,WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日志祷肯,再寫磁盤沉填。

具體來說,當有一條記錄需要更新的時候佑笋,InnoDB 引擎就會先把記錄寫到 redo log(粉板)里面翼闹,并更新內存,這個時候更新就算完成了蒋纬。同時猎荠,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤里面颠锉,而這個更新往往是在系統(tǒng)比較空閑的時候做法牲。

與此類似史汗,InnoDB 的 redo log 是固定大小的琼掠,比如可以配置為一組 4 個文件,每個文件的大小是 1GB停撞,那么這塊“粉板”總共就可以記錄 4GB 的操作瓷蛙。從頭開始寫悼瓮,寫到末尾就又回到開頭循環(huán)寫。

write pos 是當前記錄的位置艰猬,一邊寫一邊后移横堡,寫到第 3 號文件末尾后就回到 0 號文件開頭。checkpoint 是當前要擦除的位置冠桃,也是往后推移并且循環(huán)的命贴,擦除記錄前要把記錄更新到數據文件。

write pos 和 checkpoint 之間的是“粉板”上還空著的部分食听,可以用來記錄新的操作胸蛛。如果 write pos 追上 checkpoint,表示“粉板”滿了樱报,這時候不能再執(zhí)行新的更新葬项,得停下來先擦掉一些記錄,把 checkpoint 推進一下迹蛤。

有了 redo log民珍,InnoDB 就可以保證即使數據庫發(fā)生異常重啟,之前提交的記錄都不會丟失盗飒,這個能力稱為crash-safe嚷量。

重要的日志模塊:binlog

redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志逆趣,稱為 binlog(歸檔日志)津肛。

為什么會有兩份日志呢?

因為最開始 MySQL 里并沒有 InnoDB 引擎汗贫。MySQL 自帶的引擎是 MyISAM身坐,但是 MyISAM 沒有 crash-safe 的能力,binlog 日志只能用于歸檔落包。而 InnoDB 是另一個公司以插件形式引入 MySQL 的部蛇,既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來實現(xiàn) crash-safe 能力咐蝇。

這兩種日志有以下三點不同涯鲁。

  • 1 redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現(xiàn)的有序,所有引擎都可以使用抹腿。

  • 2 redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”旭寿;binlog 是邏輯日志警绩,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”盅称。

  • 3 redo log 是循環(huán)寫的肩祥,空間固定會用完后室;binlog 是可以追加寫入的』旌荩“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個岸霹,并不會覆蓋以前的日志。

有了對這兩個日志的概念性理解,我們再來看執(zhí)行器和 InnoDB 引擎在執(zhí)行這個簡單的 update 語句時的內部流程。

  • 1 執(zhí)行器先找引擎取 ID=2 這一行鸿竖。ID 是主鍵沟启,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執(zhí)行器;否則,需要先從磁盤讀入內存皇筛,然后再返回。

  • 2 執(zhí)行器拿到引擎給的行數據坠七,把這個值加上 1水醋,比如原來是 N,現(xiàn)在就是 N+1彪置,得到新的一行數據拄踪,再調用引擎接口寫入這行新數據。

  • 3 引擎將這行新數據更新到內存中拳魁,同時將這個更新操作記錄到 redo log 里面惶桐,此時 redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了潘懊,隨時可以提交事務姚糊。

  • 4 執(zhí)行器生成這個操作的 binlog,并把 binlog 寫入磁盤授舟。

  • 5 執(zhí)行器調用引擎的提交事務接口救恨,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成释树。

update 語句執(zhí)行流程:

將 redo log 的寫入拆成了兩個步驟:prepare 和 commit肠槽,這就是"兩階段提交"。

兩階段提交

前面我們說過了奢啥,binlog 會記錄所有的邏輯操作秸仙,并且是采用“追加寫”的形式。

由于 redo log 和 binlog 是兩個獨立的邏輯如果不使用“兩階段提交”桩盲,那么數據庫的狀態(tài)就有可能和用它的日志恢復出來的庫的狀態(tài)不一致寂纪。

小結

redo log 用于保證 crash-safe 能力。innodbflushlogattrx_commit 這個參數設置成 1 的時候正驻,表示每次事務的 redo log 都直接持久化到磁盤弊攘。這個參數我建議你設置成 1,這樣可以保證 MySQL 異常重啟之后數據不丟失姑曙。

sync_binlog 這個參數設置成 1 的時候襟交,表示每次事務的 binlog 都持久化到磁盤。這個參數我也建議你設置成 1伤靠,這樣可以保證 MySQL 異常重啟之后 binlog 不丟失捣域。

事務隔離:為什么你改了我還看不見?

簡單來說宴合,事務就是要保證一組數據庫操作焕梅,要么全部成功,要么全部失敗卦洽。在 MySQL 中贞言,事務支持是在引擎層實現(xiàn)的。你現(xiàn)在知道阀蒂,MySQL 是一個支持多引擎的系統(tǒng)该窗,但并不是所有的引擎都支持事務。比如 MySQL 原生的 MyISAM 引擎就不支持事務蚤霞,這也是 MyISAM 被 InnoDB 取代的重要原因之一酗失。

隔離性與隔離級別

在談隔離級別之前,你首先要知道昧绣,你隔離得越嚴實规肴,效率就會越低。SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)夜畴、讀提交(read committed)拖刃、可重復讀(repeatable read)和串行化(serializable )。SQL 標準的事務隔離級別包括:

  • 讀未提交是指贪绘,一個事務還沒提交時序调,它做的變更就能被別的事務看到。

  • 讀提交是指兔簇,一個事務提交之后发绢,它做的變更才會被其他事務看到。

  • 可重復讀是指垄琐,一個事務執(zhí)行過程中看到的數據边酒,總是跟這個事務在啟動時看到的數據是一致的。當然在可重復讀隔離級別下狸窘,未提交變更對其他事務也是不可見的墩朦。

  • 串行化,顧名思義是對于同一行記錄翻擒,“寫”會加“寫鎖”氓涣,“讀”會加“讀鎖”牛哺。當出現(xiàn)讀寫鎖沖突的時候,后訪問的事務必須等前一個事務執(zhí)行完成劳吠,才能繼續(xù)執(zhí)行引润。

  • 若隔離級別是“讀未提交”, 則 V1 的值就是 2痒玩。這時候事務 B 雖然還沒有提交淳附,但是結果已經被 A 看到了。因此蠢古,V2奴曙、V3 也都是 2。

  • 若隔離級別是“讀提交”草讶,則 V1 是 1洽糟,V2 的值是 2。事務 B 的更新在提交后才能被 A 看到堕战。所以脊框, V3 的值也是 2。

  • 若隔離級別是“可重復讀”践啄,則 V1浇雹、V2 是 1,V3 是 2屿讽。之所以 V2 還是 1昭灵,遵循的就是這個要求:事務在執(zhí)行期間看到的數據前后必須是一致的。

  • 若隔離級別是“串行化”伐谈,則在事務 B 執(zhí)行“將 1 改成 2”的時候烂完,會被鎖住。直到事務 A 提交后诵棵,事務 B 才可以繼續(xù)執(zhí)行抠蚣。所以從 A 的角度看,V1履澳、V2 值是 1嘶窄,V3 的值是 2。

在實現(xiàn)上距贷,數據庫里面會創(chuàng)建一個視圖柄冲,訪問的時候以視圖的邏輯結果為準。在“可重復讀”隔離級別下忠蝗,這個視圖是在事務啟動時創(chuàng)建的现横,整個事務存在期間都用這個視圖。在“讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執(zhí)行的時候創(chuàng)建的戒祠。這里需要注意的是骇两,“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念姜盈;而“串行化”隔離級別下直接用加鎖的方式來避免并行訪問低千。

Oracle 數據庫的默認隔離級別其實就是“讀提交”。

事務啟動時的視圖可以認為是靜態(tài)的贩据,不受其他事務更新的影響栋操。

事務隔離的實現(xiàn)

在 MySQL 中闸餐,實際上每條記錄在更新的時候都會同時記錄一條回滾操作饱亮。記錄上的最新值,通過回滾操作舍沙,都可以得到前一個狀態(tài)的值近上。

同一條記錄在系統(tǒng)中可以存在多個版本,就是數據庫的多版本并發(fā)控制(MVCC)拂铡。

你一定會問壹无,回滾日志總不能一直保留吧,什么時候刪除呢感帅?答案是斗锭,在不需要的時候才刪除。也就是說失球,系統(tǒng)會判斷岖是,當沒有事務再需要用到這些回滾日志時,回滾日志會被刪除实苞。

什么時候才不需要了呢豺撑?就是當系統(tǒng)里沒有比這個回滾日志更早的 read-view 的時候。

長事務意味著系統(tǒng)里面會存在很老的事務視圖黔牵。由于這些事務隨時可能訪問數據庫里面的任何數據聪轿,所以這個事務提交之前,數據庫里面它可能用到的回滾記錄都必須保留猾浦,這就會導致大量占用存儲空間陆错。

在 MySQL 5.5 及以前的版本,回滾日志是跟數據字典一起放在 ibdata 文件里的金赦,即使長事務最終提交危号,回滾段被清理,文件也不會變小素邪。

除了對回滾段的影響外莲,長事務還占用鎖資源。

事務的啟動方式

MySQL 的事務啟動方式有以下幾種:

  • 1 顯式啟動事務語句, begin 或 start transaction偷线。配套的提交語句是 commit磨确,回滾語句是 rollback。

  • 2 set autocommit=0声邦,這個命令會將這個線程的自動提交關掉乏奥。

有些客戶端連接框架會默認連接成功后先執(zhí)行一個 set autocommit=0 的命令。這就導致接下來的查詢都在事務中亥曹,如果是長連接邓了,就導致了意外的長事務。

因此媳瞪,我會建議你總是使用 set autocommit=1, 通過顯式語句的方式來啟動事務骗炉。

但是有的開發(fā)同學會糾結“多一次交互”的問題。對于一個需要頻繁使用事務的業(yè)務蛇受,第二種方式每個事務在開始時都不需要主動執(zhí)行一次 “begin”句葵,減少了語句的交互次數。

深入淺出索引

索引的出現(xiàn)其實就是為了提高數據查詢的效率兢仰,就像書的目錄一樣乍丈。

索引的常見模型

索引的出現(xiàn)是為了提高查詢效率,但是實現(xiàn)索引的方式卻有很多種把将,所以這里也就引入了索引模型的概念轻专。簡單的數據結構,它們分別是哈希表察蹲、有序數組和搜索樹请垛。

哈希表是一種以鍵 - 值(key-value)存儲數據的結構,我們只要輸入待查找的值即 key递览,就可以找到其對應的值即 Value叼屠。

不可避免地,多個 key 值經過哈希函數的換算绞铃,會出現(xiàn)同一個值的情況镜雨。處理這種情況的一種方法是,拉出一個鏈表儿捧。

有序數組索引只適用于靜態(tài)存儲引擎荚坞。

二叉搜索樹的特點是:每個節(jié)點的左兒子小于父節(jié)點,父節(jié)點又小于右兒子菲盾。

當然為了維持 O(log(N)) 的查詢復雜度颓影,你就需要保持這棵樹是平衡二叉樹。為了做這個保證懒鉴,更新的時間復雜度也是 O(log(N))诡挂。

樹可以有二叉碎浇,也可以有多叉。多叉樹就是每個節(jié)點有多個兒子璃俗,兒子之間的大小保證從左到右遞增奴璃。二叉樹是搜索效率最高的,但是實際上大多數的數據庫存儲卻并不使用二叉樹城豁。其原因是苟穆,索引不止存在內存中,還要寫到磁盤上唱星。

為了讓一個查詢盡量少地讀磁盤雳旅,就必須讓查詢過程訪問盡量少的數據塊。那么间聊,我們就不應該使用二叉樹攒盈,而是要使用“N 叉”樹。這里甸饱,“N 叉”樹中的“N”取決于數據塊的大小沦童。

以 InnoDB 的一個整數字段索引為例仑濒,這個 N 差不多是 1200叹话。這棵樹高是 4 的時候,就可以存 1200 的 3 次方個值墩瞳,這已經 17 億了驼壶。

N 叉樹由于在讀寫上的性能優(yōu)點,以及適配磁盤的訪問模式喉酌,已經被廣泛應用在數據庫引擎中了热凹。

在 MySQL 中,索引是在存儲引擎層實現(xiàn)的泪电,所以并沒有統(tǒng)一的索引標準般妙,即不同存儲引擎的索引的工作方式并不一樣。而即使多個存儲引擎支持同一種類型的索引相速,其底層的實現(xiàn)也可能不同碟渺。

InnoDB 的索引模型

在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的突诬,這種存儲方式的表稱為索引組織表苫拍。InnoDB 使用了 B+ 樹索引模型,所以數據都是存儲在 B+ 樹中的旺隙。

每一個索引在 InnoDB 里面對應一棵 B+ 樹绒极。索引類型分為主鍵索引和非主鍵索引。

主鍵索引的葉子節(jié)點存的是整行數據蔬捷。在 InnoDB 里垄提,主鍵索引也被稱為聚簇索引(clustered index)。

非主鍵索引的葉子節(jié)點內容是主鍵的值。在 InnoDB 里铡俐,非主鍵索引也被稱為二級索引(secondary index)摘昌。

基于主鍵索引和普通索引的查詢有什么區(qū)別?

  • 如果語句是 select * from T where ID=500高蜂,即主鍵查詢方式聪黎,則只需要搜索 ID 這棵 B+ 樹;

  • 如果語句是 select * from T where k=5备恤,即普通索引查詢方式稿饰,則需要先搜索 k 索引樹,得到 ID 的值為 500露泊,再到 ID 索引樹搜索一次喉镰。這個過程稱為回表。

也就是說惭笑,基于非主鍵索引的查詢需要多掃描一棵索引樹侣姆。

索引維護

而更糟的情況是,如果 R5 所在的數據頁已經滿了沉噩,根據 B+ 樹的算法捺宗,這時候需要申請一個新的數據頁,然后挪動部分數據過去川蒙。這個過程稱為頁分裂蚜厉。

當相鄰兩個頁由于刪除了數據,利用率很低之后畜眨,會將數據頁做合并昼牛。合并的過程,可以認為是分裂過程的逆過程康聂。

假設你的表中確實有一個唯一字段贰健,比如字符串類型的身份證號,那應該用身份證號做主鍵恬汁,還是用自增字段做主鍵呢伶椿?

由于每個非主鍵索引的葉子節(jié)點上都是主鍵的值。如果用身份證號做主鍵蕊连,那么每個二級索引的葉子節(jié)點占用約 20 個字節(jié)悬垃,而如果用整型做主鍵,則只要 4 個字節(jié)甘苍,如果是長整型(bigint)則是 8 個字節(jié)尝蠕。

顯然,主鍵長度越小载庭,普通索引的葉子節(jié)點就越小看彼,普通索引占用的空間也就越小廊佩。

有沒有什么場景適合用業(yè)務字段直接做主鍵的呢?還是有的靖榕。比如标锄,有些業(yè)務的場景需求是這樣的:

  • 1 只有一個索引;

  • 2 該索引必須是唯一索引茁计。

你一定看出來了料皇,這就是典型的 KV 場景。

由于沒有其他索引星压,所以也就不用考慮其他索引的葉子節(jié)點大小的問題践剂。

回到主鍵索引樹搜索的過程逊脯,我們稱為回表。

覆蓋索引

如果執(zhí)行的語句是 select ID from T where k between 3 and 5淑际,這時只需要查 ID 的值锄贼,而 ID 的值已經在 k 索引樹上了惹盼,因此可以直接提供查詢結果枉昏,不需要回表。也就是說道川,在這個查詢里面帅戒,索引 k 已經“覆蓋了”我們的查詢需求,我們稱為覆蓋索引伴奥。

由于覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能隧膘,所以使用覆蓋索引是一個常用的性能優(yōu)化手段腔呜。

最左前綴原則

B+ 樹這種索引結構冲九,可以利用索引的“最左前綴”谤草,來定位記錄。索引項是按照索引定義里面出現(xiàn)的字段順序排序的莺奸。

在建立聯(lián)合索引的時候丑孩,如何安排索引內的字段順序。

第一原則是憾筏,如果通過調整順序嚎杨,可以少維護一個索引,那么這個順序往往就是需要優(yōu)先考慮采用的氧腰。

索引下推

在 MySQL 5.6 之前,只能從 ID3 開始一個個回表刨肃。到主鍵索引上找出數據行古拴,再對比字段值。

MySQL 5.6 引入的索引下推優(yōu)化(index condition pushdown)真友, 可以在索引遍歷過程中黄痪,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄盔然,減少回表次數桅打。

圖3
圖4

在圖 3 和 4 這兩個圖里面是嗜,每一個虛線箭頭表示回表一次。

圖 3 中挺尾,在 (name,age) 索引里面我特意去掉了 age 的值鹅搪,這個過程 InnoDB 并不會去看 age 的值,只是按順序把“name 第一個字是’張’”的記錄一條條取出來回表遭铺。因此丽柿,需要回表 4 次。

圖 4 跟圖 3 的區(qū)別是魂挂,InnoDB 在 (name,age) 索引內部就判斷了 age 是否等于 10甫题,對于不等于 10 的記錄,直接判斷并跳過涂召。在我們的這個例子中坠非,只需要對 ID4、ID5 這兩條記錄回表取數據判斷果正,就只需要回表 2 次麻顶。

全局鎖和表鎖:給表加個字段怎么有這么多阻礙?

數據庫鎖設計的初衷是處理并發(fā)問題舱卡。當出現(xiàn)并發(fā)訪問的時候辅肾,數據庫需要合理地控制資源的訪問規(guī)則。而鎖就是用來實現(xiàn)這些訪問規(guī)則的重要數據結構轮锥。

根據加鎖的范圍矫钓,MySQL 里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類舍杜。

全局鎖

顧名思義新娜,全局鎖就是對整個數據庫實例加鎖。MySQL 提供了一個加全局讀鎖的方法既绩,命令是 Flush tables with read lock (FTWRL)概龄。當你需要讓整個庫處于只讀狀態(tài)的時候,可以使用這個命令饲握,之后其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)私杜、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句救欧。

全局鎖的典型使用場景是衰粹,做全庫邏輯備份利虫。

但是讓整庫都只讀钦无,聽上去就很危險:

  • 如果你在主庫上備份,那么在備份期間都不能執(zhí)行更新贞远,業(yè)務基本上就得停擺蹬刷;

  • 如果你在從庫上備份瓢捉,那么備份期間從庫不能執(zhí)行主庫同步過來的 binlog频丘,會導致主從延遲。

一致性讀是好泡态,但前提是引擎要支持這個隔離級別搂漠。比如,對于 MyISAM 這種不支持事務的引擎兽赁,如果備份過程中有更新状答,總是只能取到最新的數據,那么就破壞了備份的一致性刀崖。這時惊科,我們就需要使用 FTWRL 命令了。

如果有的表使用了不支持事務的引擎亮钦,那么備份就只能通過 FTWRL 方法馆截。這往往是 DBA 要求業(yè)務開發(fā)人員使用 InnoDB 替代 MyISAM 的原因之一。

業(yè)務的更新不只是增刪改數據(DML)蜂莉,還有可能是加字段等修改表結構的操作(DDL)蜡娶。不論是哪種方法,一個庫被全局鎖上以后映穗,你要對里面任何一個表做加字段操作窖张,都是會被鎖住的。

表級鎖

MySQL 里面表級別的鎖有兩種:一種是表鎖蚁滋,一種是元數據鎖(meta data lock宿接,MDL)。

表鎖的語法是 lock tables … read/write辕录。與 FTWRL 類似睦霎,可以用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放走诞。需要注意副女,lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象蚣旱。

舉個例子, 如果在某個線程 A 中執(zhí)行 lock tables t1 read, t2 write; 這個語句碑幅,則其他線程寫 t1、讀寫 t2 的語句都會被阻塞姻锁。同時枕赵,線程 A 在執(zhí)行 unlock tables 之前,也只能執(zhí)行讀 t1位隶、讀寫 t2 的操作。連寫 t1 都不允許开皿,自然也不能訪問其他表涧黄。

而對于 InnoDB 這種支持行鎖的引擎篮昧,一般不使用 lock tables 命令來控制并發(fā),畢竟鎖住整個表的影響面還是太大笋妥。

另一類表級的鎖是 MDL(metadata lock)懊昨。MDL 不需要顯式使用,在訪問一個表的時候會被自動加上春宣。MDL 的作用是酵颁,保證讀寫的正確性。你可以想象一下月帝,如果一個查詢正在遍歷一個表中的數據躏惋,而執(zhí)行期間另一個線程對這個表結構做變更,刪了一列嚷辅,那么查詢線程拿到的結果跟表結構對不上簿姨,肯定是不行的。

因此簸搞,在 MySQL 5.5 版本中引入了 MDL扁位,當對一個表做增刪改查操作的時候,加 MDL 讀鎖趁俊;當要對表做結構變更操作的時候域仇,加 MDL 寫鎖。

  • 讀鎖之間不互斥寺擂,因此你可以有多個線程同時對一張表增刪改查暇务。

  • 讀寫鎖之間、寫鎖之間是互斥的沽讹,用來保證變更表結構操作的安全性般卑。因此,如果有兩個線程要同時給一個表加字段爽雄,其中一個要等另一個執(zhí)行完才能開始執(zhí)行蝠检。

你肯定知道,給一個表加字段挚瘟,或者修改字段叹谁,或者加索引,需要掃描全表的數據乘盖。在對大表操作的時候焰檩,你肯定會特別小心,以免對線上服務造成影響订框。

我們可以看到 session A 先啟動析苫,這時候會對表 t 加一個 MDL 讀鎖。由于 session B 需要的也是 MDL 讀鎖,因此可以正常執(zhí)行衩侥。

之后 session C 會被 blocked国旷,是因為 session A 的 MDL 讀鎖還沒有釋放,而 session C 需要 MDL 寫鎖茫死,因此只能被阻塞跪但。

如果只有 session C 自己被阻塞還沒什么關系,但是之后所有要在表 t 上新申請 MDL 讀鎖的請求也會被 session C 阻塞峦萎。前面我們說了屡久,所有對表的增刪改查操作都需要先申請 MDL 讀鎖,就都被鎖住爱榔,等于這個表現(xiàn)在完全不可讀寫了被环。

事務中的 MDL 鎖,在語句執(zhí)行開始時申請搓蚪,但是語句結束后并不會馬上釋放蛤售,而會等到整個事務提交后再釋放。

如何安全地給小表加字段妒潭?

首先我們要解決長事務悴能,事務不提交,就會一直占著 MDL 鎖雳灾。

小結

全局鎖主要用在邏輯備份過程中漠酿。對于全部是 InnoDB 引擎的庫,我建議你選擇使用–single-transaction 參數谎亩,對應用會更友好炒嘲。

表鎖一般是在數據庫引擎不支持行鎖的時候才會被用到的。如果你發(fā)現(xiàn)你的應用程序里有 lock tables 這樣的語句匈庭,你需要追查一下夫凸,比較可能的情況是:

  • 要么是你的系統(tǒng)現(xiàn)在還在用 MyISAM 這類不支持事務的引擎,那要安排升級換引擎阱持;

  • 要么是你的引擎升級了夭拌,但是代碼還沒升級。我見過這樣的情況衷咽,最后業(yè)務開發(fā)就是把 lock tables 和 unlock tables 改成 begin 和 commit鸽扁,問題就解決了。

MDL 會直到事務提交才釋放镶骗,在做表結構變更的時候桶现,你一定要小心不要導致鎖住線上查詢和更新。


我給你留一個問題吧鼎姊,備份一般都會在備庫上執(zhí)行骡和,你在用–single-transaction 方法做邏輯備份的過程中相赁,如果主庫上的一個小表做了一個 DDL,比如給一個表上加了一列即横。這時候噪生,從備庫上會看到什么現(xiàn)象呢裆赵?

Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Q2:START TRANSACTION  WITH CONSISTENT SNAPSHOT东囚;
/* other tables */
Q3:SAVEPOINT sp;
/* 時刻 1 */
Q4:show create table `t1`;
/* 時刻 2 */
Q5:SELECT * FROM `t1`;
/* 時刻 3 */
Q6:ROLLBACK TO SAVEPOINT sp;
/* 時刻 4 */
/* other tables */

參考答案如下:

如果在 Q4 語句執(zhí)行之前到達,現(xiàn)象:沒有影響战授,備份拿到的是 DDL 后的表結構页藻。

如果在“時刻 2”到達,則表結構被改過植兰,Q5 執(zhí)行的時候份帐,報 Table definition has changed, please retry transaction,現(xiàn)象:mysqldump 終止楣导;

如果在“時刻 2”和“時刻 3”之間到達废境,mysqldump 占著 t1 的 MDL 讀鎖,binlog 被阻塞筒繁,現(xiàn)象:主從延遲噩凹,直到 Q6 執(zhí)行完成。

從“時刻 4”開始毡咏,mysqldump 釋放了 MDL 讀鎖驮宴,現(xiàn)象:沒有影響,備份拿到的是 DDL 前的表結構呕缭。

行鎖功過:怎么減少行鎖對性能的影響堵泽?

MySQL 的行鎖是在引擎層由各個引擎自己實現(xiàn)的。但并不是所有的引擎都支持行鎖恢总,比如 MyISAM 引擎就不支持行鎖迎罗。不支持行鎖意味著并發(fā)控制只能使用表鎖。InnoDB 是支持行鎖的片仿,這也是 MyISAM 被 InnoDB 替代的重要原因之一纹安。(innodb行級鎖是通過鎖索引記錄實現(xiàn)的。)

顧名思義滋戳,行鎖就是針對數據表中行記錄的鎖钻蔑。這很好理解,比如事務 A 更新了一行奸鸯,而這時候事務 B 也要更新同一行咪笑,則必須等事務 A 的操作完成后才能進行更新。

從兩階段鎖說起

在下面的操作序列中娄涩,事務 B 的 update 語句執(zhí)行時會是什么現(xiàn)象呢窗怒?假設字段 id 是表 t 的主鍵映跟。

你可以驗證一下:實際上事務 B 的 update 語句會被阻塞,直到事務 A 執(zhí)行 commit 之后扬虚,事務 B 才能繼續(xù)執(zhí)行努隙。

知道了這個答案,你一定知道了事務 A 持有的兩個記錄的行鎖辜昵,都是在 commit 的時候才釋放的荸镊。

也就是說,在 InnoDB 事務中堪置,行鎖是在需要的時候才加上的躬存,但并不是不需要了就立刻釋放,而是要等到事務結束時才釋放舀锨。這個就是兩階段鎖協(xié)議岭洲。

死鎖和死鎖檢測

當并發(fā)系統(tǒng)中不同線程出現(xiàn)循環(huán)資源依賴,涉及的線程都在等待別的線程釋放資源時坎匿,就會導致這幾個線程都進入無限等待的狀態(tài)盾剩,稱為死鎖。

這時候替蔬,事務 A 在等待事務 B 釋放 id=2 的行鎖告私,而事務 B 在等待事務 A 釋放 id=1 的行鎖。 事務 A 和事務 B 在互相等待對方的資源釋放进栽,就是進入了死鎖狀態(tài)德挣。當出現(xiàn)死鎖以后,有兩種策略:

  • 一種策略是快毛,直接進入等待格嗅,直到超時。這個超時時間可以通過參數 innodblockwait_timeout 來設置唠帝。

  • 另一種策略是屯掖,發(fā)起死鎖檢測,發(fā)現(xiàn)死鎖后襟衰,主動回滾死鎖鏈條中的某一個事務贴铜,讓其他事務得以繼續(xù)執(zhí)行。將參數 innodbdeadlockdetect 設置為 on瀑晒,表示開啟這個邏輯绍坝。

在 InnoDB 中,innodblockwait_timeout 的默認值是 50s苔悦,意味著如果采用第一個策略轩褐,當出現(xiàn)死鎖以后,第一個被鎖住的線程要過 50s 才會超時退出玖详,然后其他線程才有可能繼續(xù)執(zhí)行把介。對于在線服務來說勤讽,這個等待時間往往是無法接受的。

所以拗踢,超時時間設置太短的話脚牍,會出現(xiàn)很多誤傷。

所以巢墅,正常情況下我們還是要采用第二種策略诸狭,即:主動死鎖檢測,而且 innodbdeadlockdetect 的默認值本身就是 on砂缩。主動死鎖檢測在發(fā)生死鎖的時候作谚,是能夠快速發(fā)現(xiàn)并進行處理的,但是它也是有額外負擔的庵芭。

每個新來的被堵住的線程,都要判斷會不會由于自己的加入導致了死鎖雀监,這是一個時間復雜度是 O(n) 的操作双吆。假設有 1000 個并發(fā)線程要同時更新同一行,那么死鎖檢測操作就是 100 萬這個量級的会前。雖然最終檢測的結果是沒有死鎖好乐,但是這期間要消耗大量的 CPU 資源。

問題的癥結在于瓦宜,死鎖檢測要耗費大量的 CPU 資源蔚万。

一種頭痛醫(yī)頭的方法,就是如果你能確保這個業(yè)務一定不會出現(xiàn)死鎖临庇,可以臨時把死鎖檢測關掉反璃。

另一個思路是控制并發(fā)度。根據上面的分析假夺,你會發(fā)現(xiàn)如果并發(fā)能夠控制住淮蜈,比如同一行同時最多只有 10 個線程在更新,那么死鎖檢測的成本很低已卷,就不會出現(xiàn)這個問題梧田。一個直接的想法就是,在客戶端做并發(fā)控制侧蘸。但是裁眯,你會很快發(fā)現(xiàn)這個方法不太可行,因為客戶端很多讳癌。我見過一個應用穿稳,有 600 個客戶端,這樣即使每個客戶端控制到只有 5 個并發(fā)線程析桥,匯總到數據庫服務端以后司草,峰值并發(fā)數也可能要達到 3000艰垂。

因此,這個并發(fā)控制要做在數據庫服務端埋虹。

小結

調整語句順序并不能完全避免死鎖猜憎。所以我們引入了死鎖和死鎖檢測的概念,以及提供了三個方案搔课,來減少死鎖對數據庫的影響胰柑。減少死鎖的主要方向,就是控制訪問相同資源的并發(fā)事務量爬泥。


我給你留一個問題吧柬讨,如果你要刪除一個表里面的前 10000 行數據,有以下三種方法可以做到:

  • 第一種袍啡,直接執(zhí)行 delete from T limit 10000;

  • 第二種踩官,在一個連接中循環(huán)執(zhí)行 20 次 delete from T limit 500;

  • 第三種,在 20 個連接中同時執(zhí)行 delete from T limit 500境输。

你會選擇哪一種方法呢蔗牡?為什么呢?

確實是這樣的嗅剖,第二種方式是相對較好的辩越。

第一種方式(即:直接執(zhí)行 delete from T limit 10000)里面,單個語句占用時間長信粮,鎖的時間也比較長黔攒;而且大事務還會導致主從延遲。

第三種方式(即:在 20 個連接中同時執(zhí)行 delete from T limit 500)强缘,會人為造成鎖沖突督惰。

事務到底是隔離的還是不隔離的?

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

begin/start transaction 命令并不是一個事務的起點欺旧,在執(zhí)行到它們之后的第一個操作 InnoDB 表的語句姑丑,事務才真正啟動。如果你想要馬上啟動一個事務辞友,可以使用 start transaction with consistent snapshot 這個命令栅哀。

在這個例子中,事務 C 沒有顯式地使用 begin/commit称龙,表示這個 update 語句本身就是一個事務留拾,語句完成的時候會自動提交。事務 B 在更新了行之后查詢 ; 事務 A 在一個只讀事務中查詢鲫尊,并且時間順序上是在事務 B 的查詢之后痴柔。

這時,如果我告訴你事務 B 查到的 k 的值是 3疫向,而事務 A 查到的 k 的值是 1咳蔚。

在 MySQL 里豪嚎,有兩個“視圖”的概念:

  • 一個是 view。它是一個用查詢語句定義的虛擬表谈火,在調用的時候執(zhí)行查詢語句并生成結果侈询。創(chuàng)建視圖的語法是 create view … ,而它的查詢方法與表一樣糯耍。

  • 另一個是 InnoDB 在實現(xiàn) MVCC 時用到的一致性讀視圖扔字,即 consistent read view,用于支持 RC(Read Committed温技,讀提交)和 RR(Repeatable Read革为,可重復讀)隔離級別的實現(xiàn)。

它沒有物理結構舵鳞,作用是事務執(zhí)行期間用來定義“我能看到什么數據”震檩。

“快照”在 MVCC 里是怎么工作的?

在可重復讀隔離級別下系任,事務在啟動的時候就“拍了個快照”恳蹲。注意,這個快照是基于整庫的俩滥。

InnoDB 里面每個事務有一個唯一的事務 ID,叫作 transaction id贺奠。它是在事務開始的時候向 InnoDB 的事務系統(tǒng)申請的霜旧,是按申請順序嚴格遞增的。

而每行數據也都是有多個版本的儡率。每次事務更新數據的時候挂据,都會生成一個新的數據版本,并且把 transaction id 賦值給這個數據版本的事務 ID儿普,記為 row trx_id崎逃。同時,舊的數據版本要保留眉孩,并且在新的數據版本中个绍,能夠有信息可以直接拿到它。

也就是說浪汪,數據表中的一行記錄巴柿,其實可能有多個版本 (row),每個版本有自己的 row trx_id死遭。

圖中虛線框里是同一行數據的 4 個版本广恢,當前最新版本是 V4,k 的值是 22呀潭,它是被 transaction id 為 25 的事務更新的钉迷,因此它的 row trx_id 也是 25至非。

實際上,圖 2 中的三個虛線箭頭糠聪,就是 undo log荒椭;而 V1、V2枷颊、V3 并不是物理上真實存在的戳杀,而是每次需要的時候根據當前版本和 undo log 計算出來的。比如夭苗,需要 V2 的時候信卡,就是通過 V4 依次執(zhí)行 U3、U2 算出來题造。

按照可重復讀的定義傍菇,一個事務啟動的時候,能夠看到所有已經提交的事務結果界赔。但是之后丢习,這個事務執(zhí)行期間,其他事務的更新對它不可見淮悼。

因此咐低,一個事務只需要在啟動的時候聲明說,“以我啟動的時刻為準袜腥,如果一個數據版本是在我啟動之前生成的见擦,就認;如果是我啟動以后才生成的羹令,我就不認鲤屡,我必須要找到它的上一個版本”。

當然福侈,如果“上一個版本”也不可見酒来,那就得繼續(xù)往前找。

在實現(xiàn)上肪凛, InnoDB 為每個事務構造了一個數組堰汉,用來保存這個事務啟動瞬間,當前正在“活躍”的所有事務 ID显拜『獍拢“活躍”指的就是,啟動了但還沒提交远荠。

數組里面事務 ID 的最小值記為低水位矮固,當前系統(tǒng)里面已經創(chuàng)建過的事務 ID 的最大值加 1 記為高水位。

這個視圖數組和高水位,就組成了當前事務的一致性視圖(read-view)档址。

而數據版本的可見性規(guī)則盹兢,就是基于數據的 row trx_id 和這個一致性視圖的對比結果得到的。

這樣守伸,對于當前事務的啟動瞬間來說绎秒,一個數據版本的 row trx_id,有以下幾種可能:

如果落在綠色部分尼摹,表示這個版本是已提交的事務或者是當前事務自己生成的见芹,這個數據是可見的;

如果落在紅色部分蠢涝,表示這個版本是由將來啟動的事務生成的玄呛,是肯定不可見的;

如果落在黃色部分和二,那就包括兩種情況:

  • a. 若 row trx_id 在數組中徘铝,表示這個版本是由還沒提交的事務生成的,不可見惯吕;

  • b. 若 row trx_id 不在數組中惕它,表示這個版本是已經提交了的事務生成的,可見废登。

因為之后的更新淹魄,生成的版本一定屬于上面的 2 或者 3(a) 的情況,而對它來說堡距,這些新的數據版本是不存在的揭北,所以這個事務的快照,就是“靜態(tài)”的了吏颖。

所以你現(xiàn)在知道了,InnoDB 利用了“所有數據都有多個版本”的這個特性恨樟,實現(xiàn)了“秒級創(chuàng)建快照”的能力半醉。

這里澜共,我們不妨做如下假設:

  • 事務 A 開始前牲览,系統(tǒng)里面只有一個活躍事務 ID 是 99;

  • 事務 A峰尝、B养晋、C 的版本號分別是 100衬吆、101、102绳泉,且當前系統(tǒng)里只有這四個事務逊抡;

  • 三個事務開始前,(1,1)這一行數據的 row trx_id 是 90零酪。

這樣冒嫡,事務 A 的視圖數組就是 [99,100], 事務 B 的視圖數組是 [99,100,101], 事務 C 的視圖數組是 [99,100,101,102]拇勃。

從圖中可以看到,第一個有效更新是事務 C孝凌,把數據從 (1,1) 改成了 (1,2)方咆。這時候,這個數據的最新版本的 row trx_id 是 102蟀架,而 90 這個版本已經成為了歷史版本瓣赂。

第二個有效更新是事務 B,把數據從 (1,2) 改成了 (1,3)片拍。這時候煌集,這個數據的最新版本(即 row trx_id)是 101,而 102 又成為了歷史版本穆碎。

你可能注意到了牙勘,在事務 A 查詢的時候,其實事務 B 還沒有提交所禀,但是它生成的 (1,3) 這個版本已經變成當前版本了方面。但這個版本對事務 A 必須是不可見的,否則就變成臟讀了色徘。

好恭金,現(xiàn)在事務 A 要來讀數據了,它的視圖數組是 [99,100]褂策。當然了横腿,讀數據都是從當前版本讀起的。所以斤寂,事務 A 查詢語句的讀數據流程是這樣的:

  • 找到 (1,3) 的時候耿焊,判斷出 row trx_id=101,比高水位大遍搞,處于紅色區(qū)域罗侯,不可見;

  • 接著溪猿,找到上一個歷史版本钩杰,一看 row trx_id=102,比高水位大诊县,處于紅色區(qū)域讲弄,不可見;

  • 再往前找依痊,終于找到了(1,1)避除,它的 row trx_id=90,比低水位小,處于綠色區(qū)域驹饺,可見钳枕。

這樣執(zhí)行下來,雖然期間這一行數據被修改過赏壹,但是事務 A 不論在什么時候查詢鱼炒,看到這行數據的結果都是一致的,所以我們稱之為一致性讀蝌借。

一個數據版本昔瞧,對于一個事務視圖來說,除了自己的更新總是可見以外菩佑,有三種情況:

  • 版本未提交自晰,不可見;

  • 版本已提交稍坯,但是是在視圖創(chuàng)建后提交的酬荞,不可見;

  • 版本已提交瞧哟,而且是在視圖創(chuàng)建前提交的混巧,可見。

現(xiàn)在勤揩,我們用這個規(guī)則來判斷圖 4 中的查詢結果咧党,事務 A 的查詢語句的視圖數組是在事務 A 啟動的時候生成的,這時候:

  • (1,3) 還沒提交陨亡,屬于情況 1傍衡,不可見;

  • (1,2) 雖然提交了负蠕,但是是在視圖數組創(chuàng)建之后提交的蛙埂,屬于情況 2,不可見遮糖;

  • (1,1) 是在視圖數組創(chuàng)建之前提交的箱残,可見。

更新邏輯

你看圖 5 中止吁,事務 B 的視圖數組是先生成的,之后事務 C 才提交燎悍,不是應該看不見 (1,2) 嗎敬惦,怎么能算出 (1,3) 來?

是的谈山,如果事務 B 在更新之前查詢一次數據俄删,這個查詢返回的 k 的值確實是 1。

但是,當它要去更新數據的時候畴椰,就不能再在歷史版本上更新了臊诊,否則事務 C 的更新就丟失了。因此斜脂,事務 B 此時的 set k=k+1 是在(1,2)的基礎上進行的操作抓艳。

所以,這里就用到了這樣一條規(guī)則:更新數據都是先讀后寫的帚戳,而這個讀玷或,只能讀當前的值,稱為“當前讀”(current read)片任。

因此偏友,在更新的時候,當前讀拿到的數據是 (1,2)对供,更新后生成了新版本的數據 (1,3)位他,這個新版本的 row trx_id 是 101。

其實产场,除了 update 語句外鹅髓,select 語句如果加鎖,也是當前讀涝动。

再往前一步迈勋,假設事務 C 不是馬上提交的,而是變成了下面的事務 C’醋粟,會怎么樣呢靡菇?

事務 C’的不同是,更新后并沒有馬上提交米愿,在它提交前厦凤,事務 B 的更新語句先發(fā)起了。前面說過了育苟,雖然事務 C’還沒提交较鼓,但是 (1,2) 這個版本也已經生成了,并且是當前的最新版本违柏。那么,事務 B 的更新語句會怎么處理呢漱竖?

事務 C’沒提交禽篱,也就是說 (1,2) 這個版本上的寫鎖還沒釋放玛界。而事務 B 是當前讀后添,必須要讀最新版本吕朵,而且必須加鎖猎醇,因此就被鎖住了,必須等到事務 C’釋放這個鎖努溃,才能繼續(xù)它的當前讀硫嘶。

事務的可重復讀的能力是怎么實現(xiàn)的?

可重復讀的核心就是一致性讀(consistent read)梧税;而事務更新數據的時候沦疾,只能用當前讀。如果當前的記錄的行鎖被其他事務占用的話第队,就需要進入鎖等待哮塞。

而讀提交的邏輯和可重復讀的邏輯類似,它們最主要的區(qū)別是:

  • 在可重復讀隔離級別下凳谦,只需要在事務開始的時候創(chuàng)建一致性視圖忆畅,之后事務里的其他查詢都共用這個一致性視圖;

  • 在讀提交隔離級別下尸执,每一個語句執(zhí)行前都會重新算出一個新的視圖家凯。

那么,我們再看一下如失,在讀提交隔離級別下绊诲,事務 A 和事務 B 的查詢語句查到的 k,分別應該是多少呢褪贵?

下面是讀提交時的狀態(tài)圖掂之,可以看到這兩個查詢語句的創(chuàng)建視圖數組的時機發(fā)生了變化,就是圖中的 read view 框脆丁。

這時世舰,事務 A 的查詢語句的視圖數組是在執(zhí)行這個語句的時候創(chuàng)建的,時序上 (1,2)槽卫、(1,3) 的生成時間都在創(chuàng)建這個視圖數組的時刻之前冯乘。但是,在這個時刻:

  • (1,3) 還沒提交晒夹,屬于情況 1裆馒,不可見;

  • (1,2) 提交了丐怯,屬于情況 3喷好,可見。

所以读跷,這時候事務 A 查詢語句返回的是 k=2梗搅。

顯然地,事務 B 查詢結果 k=3效览。

小結

InnoDB 的行數據有多個版本无切,每個數據版本有自己的 row trxid,每個事務或者語句有自己的一致性視圖丐枉。普通查詢語句是一致性讀哆键,一致性讀會根據 row trxid 和一致性視圖確定數據版本的可見性。

  • 對于可重復讀瘦锹,查詢只承認在事務啟動前就已經提交完成的數據籍嘹;

  • 對于讀提交,查詢只承認在語句啟動前就已經提交完成的數據弯院;

而當前讀辱士,總是讀取已經提交完成的最新版本。

當然听绳,MySQL 8.0 已經可以把表結構放在 InnoDB 字典里了颂碘,也許以后會支持表結構的可重復讀。

在此我向大家推薦一個架構學習交流群椅挣。交流學習群號:833145934 里面資深架構師會分享一些整理好的錄制視頻錄像和BATJ面試題:有Spring头岔,MyBatis,Netty源碼分析贴妻,高并發(fā)切油、高性能、分布式名惩、微服務架構的原理澎胡,JVM性能優(yōu)化、分布式架構等這些成為架構師必備的知識體系娩鹉。還能領取免費的學習資源攻谁,目前受益良多。

注:文章來源于網絡
出處:http://www.linkedkeeper.com/1304.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末弯予,一起剝皮案震驚了整個濱河市戚宦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锈嫩,老刑警劉巖受楼,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垦搬,死亡現(xiàn)場離奇詭異,居然都是意外死亡艳汽,警方通過查閱死者的電腦和手機猴贰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來河狐,“玉大人米绕,你說我怎么就攤上這事〔鲆眨” “怎么了栅干?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捐祠。 經常有香客問我碱鳞,道長,這世上最難降的妖魔是什么雏赦? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任劫笙,我火速辦了婚禮,結果婚禮上星岗,老公的妹妹穿的比我還像新娘填大。我一直安慰自己,他們只是感情好俏橘,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布允华。 她就那樣靜靜地躺著,像睡著了一般寥掐。 火紅的嫁衣襯著肌膚如雪靴寂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天召耘,我揣著相機與錄音百炬,去河邊找鬼。 笑死污它,一個胖子當著我的面吹牛剖踊,可吹牛的內容都是我干的。 我是一名探鬼主播衫贬,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼德澈,長吁一口氣:“原來是場噩夢啊……” “哼固惯!你這毒婦竟也來了?” 一聲冷哼從身側響起葬毫,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤屡穗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忽肛,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年源祈,在試婚紗的時候發(fā)現(xiàn)自己被綠了煎源。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡手销,死狀恐怖图张,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情祸轮,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布柄错,位于F島的核電站苦酱,受9級特大地震影響,放射性物質發(fā)生泄漏疫萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一恒削、第九天 我趴在偏房一處隱蔽的房頂上張望帝际。 院中可真熱鬧,春花似錦蹲诀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涌矢。三九已至快骗,卻和暖如春娜庇,著一層夾襖步出監(jiān)牢的瞬間方篮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工匕得, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巾表,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓考阱,卻偏偏與公主長得像惠猿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子偶妖,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容

  • 今天看到一位朋友寫的mysql筆記總結趾访,覺得寫的很詳細很用心态秧,這里轉載一下扼鞋,供大家參考下,也希望大家能關注他原文地...
    信仰與初衷閱讀 4,730評論 0 30
  • 一捐友、MySQL優(yōu)化 MySQL優(yōu)化從哪些方面入手: (1)存儲層(數據) 構建良好的數據結構溃槐。可以大大的提升我們S...
    寵辱不驚丶歲月靜好閱讀 2,427評論 1 8
  • 索引 數據庫中的查詢操作非常普遍拂共,索引就是提升查找速度的一種手段 索引的類型 從數據結構角度分 1.B+索引:傳統(tǒng)...
    一凡呀閱讀 2,914評論 0 8
  • MySQL不權威總結 歡迎閱讀 本文并非事無巨細的mysql學習資料,而是選擇其中重要势告、困難抚恒、易錯的部分進行系統(tǒng)地...
    liufxlucky365閱讀 2,589評論 0 26
  • 今天高考成績,朋友圈好多哥哥姐姐們在曬家里弟弟妹妹的成績柑爸,畫一個“鴨蛋”祝你們的夢都圓滿盒音,“鴨蛋”又寓意歸零,希望...
    又雙叒叕雙閱讀 243評論 0 1