MYSQL
內(nèi)部模塊
連接器(JDBC、ODBC等) =>
[MYSQL 內(nèi)部
[Connection Pool] (授權(quán)饵骨、線程復(fù)用翘悉、連接限制、內(nèi)存檢測(cè)等)
=>
[SQL Interface] (DML居触、DDL、Views等) [Parser] (Query Translation老赤、Object privilege) [Optimizer] (Access Paths轮洋、 統(tǒng)計(jì)分析) [Caches & Buffers]
=>
[Pluggable Storage Engines]
]
=> [File]
一條SQL執(zhí)行過(guò)程
先看看一條查詢SQL
- (這里提供一下官方對(duì)各存儲(chǔ)引擎的文檔說(shuō)明 Mysql存儲(chǔ)引擎)
一條 update SQL執(zhí)行
update的執(zhí)行 從客戶端 => ··· => 執(zhí)行引擎
是一樣的流程,都要先查到這條數(shù)據(jù)抬旺,然后再去更新弊予。要想理解 UPDATE
流程我們先來(lái)看看,Innodb的架構(gòu)模型开财。
Innodb 架構(gòu)
上一張 MYSQL 官方InnoDB架構(gòu)圖:
內(nèi)存結(jié)構(gòu)
這里有個(gè)關(guān)鍵點(diǎn)汉柒,當(dāng)我們?nèi)ゲ樵償?shù)據(jù)時(shí)候會(huì)先 拿著我們當(dāng)前查詢的 頁(yè)
去 緩沖池
中查詢 當(dāng)前頁(yè)
是否在緩沖池
中。如果在责鳍,則直接獲取碾褂。
當(dāng)是update
時(shí),則會(huì)直接修改 Buffer
中的值历葛。這個(gè)時(shí)候正塌,緩沖池中的數(shù)據(jù)就和磁盤(pán)中存儲(chǔ)的數(shù)據(jù)不一致
了,稱(chēng)為臟頁(yè)
恤溶。每隔一段時(shí)間乓诽,Innodb存儲(chǔ)引擎就會(huì)把臟頁(yè)數(shù)據(jù)
刷入磁盤(pán)。
一般來(lái)說(shuō)當(dāng)更新一條數(shù)據(jù)咒程,我們需要將數(shù)據(jù)給讀取到buffer
中修改鸠天,然后寫(xiě)回磁盤(pán),其中有一次 IO
操作帐姻。
圖中緩沖池中有一塊區(qū)域叫做:change buffer
稠集。
當(dāng)更新一個(gè)沒(méi)有 unique index
的數(shù)據(jù)時(shí),直接將修改的數(shù)據(jù)放到 change buffer
卖宠,然后通過(guò) merge
操作完成更新巍杈,從而減少了 IO
操作。
- 為什么要沒(méi)有唯一索引的數(shù)據(jù)更新時(shí)才能這樣呢扛伍,因?yàn)槲覀兏聰?shù)據(jù)后筷畦,可能更新后的數(shù)據(jù)和已經(jīng)存在的數(shù)據(jù)有重復(fù),所以必須從磁盤(pán)中
把所有數(shù)據(jù)讀出來(lái)比對(duì)
才行。 - 所以當(dāng)我們的數(shù)據(jù)是
寫(xiě)多讀少
的時(shí)候鳖宾,就可以通過(guò) 增加innodb_change_buffer_max_size
來(lái)調(diào)整change buffer
在buffer pool
中所占的比例吼砂,默認(rèn)25(即:25%)
問(wèn)題又來(lái)了,merge是如何運(yùn)作的
有四種情況:
- 有其他訪問(wèn)鼎文,訪問(wèn)到了當(dāng)前頁(yè)的數(shù)據(jù)渔肩,就會(huì)合并到磁盤(pán)
- 后臺(tái)線程定時(shí)
- 系統(tǒng)正常shut down之前
-
redo log
寫(xiě)滿的時(shí)候
一、redo log是什么
談到redo拇惋,就要談到innodb的 crash safe
周偎,使用 WAL 的方式實(shí)現(xiàn)(write Ahead Logging,在寫(xiě)之前先記錄日志)
這樣就可以在撑帖,當(dāng)數(shù)據(jù)庫(kù)崩潰的后蓉坎,直接從 redo log
中恢復(fù)數(shù)據(jù),保證數(shù)據(jù)的正確性
- redo log 默認(rèn)存儲(chǔ)在兩個(gè)文件中
ib_logfile0
ib_logfile1
胡嘿,這兩個(gè)文件都是固定大小的
蛉艾。為什么需要固定大小衷敌?這是因?yàn)?code>redo log的順序讀取
的特性造成的勿侯,必須是連續(xù)的存儲(chǔ)空間
二、隨機(jī)讀寫(xiě)與順序讀寫(xiě)
看一張圖
一般我們的數(shù)據(jù)都是分散在磁盤(pán)上的:
機(jī)械硬盤(pán):
- 定位到磁道
- 等待旋轉(zhuǎn)到對(duì)應(yīng)扇區(qū)
- 開(kāi)始讀寫(xiě)
固態(tài)缴罗;
- 直接定位到閃存芯片(這也是為啥固態(tài)比機(jī)械快)
- 開(kāi)始讀寫(xiě)
而我們?nèi)ゴ鎯?chǔ)時(shí)助琐,是通過(guò)文件系統(tǒng)
與磁盤(pán)打交道的,而他們打交道的方式就有兩個(gè)瞒爬。隨機(jī)讀寫(xiě)
和順序讀寫(xiě)
- 隨機(jī)讀寫(xiě)存儲(chǔ)的數(shù)據(jù)是分布在不同的
塊
(默認(rèn) 1block=8扇區(qū)=4K) - 而順序存儲(chǔ)弓柱,顧名思義,數(shù)據(jù)是分布在
一串連續(xù)的塊
中侧但,這樣讀取速度就大大提升了
三矢空、回到我們架構(gòu)圖
看到buffer pool
中的Log Buffer
,其就是用來(lái)寫(xiě) redo log 之前存在的緩沖區(qū)
在這里禀横,redo log具體的執(zhí)行策略有三種:
- 不用寫(xiě)
Log Buffer
屁药,只需要每秒寫(xiě)redo log 磁盤(pán)數(shù)據(jù)一次,性能高柏锄,但會(huì)造成數(shù)據(jù) 1s 內(nèi)的一致性問(wèn)題酿箭。適用于強(qiáng)實(shí)時(shí)性
,弱一致性
趾娃,比如評(píng)論區(qū)評(píng)論
- 寫(xiě)
Log Buffer
缭嫡,同時(shí)寫(xiě)入磁盤(pán),性能最差抬闷,一致性最高妇蛀。 適用于弱實(shí)時(shí)性
耕突,強(qiáng)一致性
,比如支付場(chǎng)景
- 寫(xiě)
Log Buffer
,同時(shí)寫(xiě)到os buffer
(其會(huì)每秒調(diào)用fsync
將數(shù)據(jù)刷入磁盤(pán))评架,性能好眷茁,安全性也高。這個(gè)是實(shí)時(shí)性適中
一致性適中
的纵诞,比如訂單類(lèi)
上祈。
我們通過(guò)innodb_flush_log_at_trx_commit
就可以設(shè)置執(zhí)行策略。默認(rèn)為 1
內(nèi)存結(jié)構(gòu)小結(jié)
- Buffer Pool 用于加速讀
- Change Buffer 用于沒(méi)有非唯一索引的加速寫(xiě)
- Log Buffer 用于加速redo log寫(xiě)
-
自適應(yīng)Hash索引
主要用于加快查詢頁(yè)
浙芙。在查詢時(shí)登刺,Innodb通過(guò)監(jiān)視索引搜索的機(jī)制來(lái)判斷當(dāng)前查詢是否能走Hash索引
。比如LIKE運(yùn)算符和% 通配符就不能走茁裙。
硬盤(pán)結(jié)構(gòu)
一塘砸、System Tablespace
存儲(chǔ)在一個(gè)叫ibdata1
的文件中,其中包含:
- InnoDB Data Dictionary晤锥,存儲(chǔ)了元數(shù)據(jù),比如表結(jié)構(gòu)信息廊宪、索引等
- Doublewrite Buffer 當(dāng)
Buffer Pool
寫(xiě)入數(shù)據(jù)頁(yè)時(shí)矾瘾,不是直接寫(xiě)入到文件,而是先寫(xiě)入到這個(gè)區(qū)域箭启。這樣做的好處的是壕翩,一但操作系統(tǒng),文件系統(tǒng)或者mysql掛掉傅寡,可以直接從這個(gè)Buffer
中獲取數(shù)據(jù)放妈。 - Change Buffer 當(dāng)Mysql shut down的時(shí)候,修改就會(huì)被存儲(chǔ)在磁盤(pán)這里
- Undo Logs 記錄事務(wù)修改操作
二荐操、File-Per-Table Tablespaces
每一張表都有一張 .ibd
的文件芜抒,存儲(chǔ)數(shù)據(jù)和索引。
- 有了
每表文件表空間
可以使得ALTER TABLE
與TRUNCATE TABLE
性能得到很好的提升托启。比如ALTER TABLE
宅倒,相較于對(duì)駐留在共享表空間中的表,在修改表時(shí)屯耸,會(huì)進(jìn)行表復(fù)制操作
拐迁,這可能會(huì)增加表空間占用的磁盤(pán)空間量
。此類(lèi)操作可能需要與表中的數(shù)據(jù)以及索引一樣多的額外空間疗绣。該空間不會(huì)像每表文件表空間
那樣釋放回操作系統(tǒng)线召。 - 可以在單獨(dú)的存儲(chǔ)設(shè)備上創(chuàng)建每表文件表空間數(shù)據(jù)文件,以進(jìn)行I / O優(yōu)化多矮,空間管理或備份缓淹。這就意味著表數(shù)據(jù)與結(jié)構(gòu)容易在不同數(shù)據(jù)庫(kù)中遷移。
- 當(dāng)發(fā)生數(shù)據(jù)損壞,備份或二進(jìn)制日志不可用或無(wú)法重新啟動(dòng)MySQL服務(wù)器實(shí)例時(shí)割卖,存儲(chǔ)在單個(gè)表空間數(shù)據(jù)文件中的表可以節(jié)省時(shí)間并提高成功恢復(fù)的機(jī)會(huì)前酿。
當(dāng)然有優(yōu)點(diǎn)就有缺陷:
- 存儲(chǔ)空間的利用率低,會(huì)存在碎片鹏溯,在
Drop table
的時(shí)候會(huì)影響性能(除非你自己管理了碎片) - 因?yàn)槊總€(gè)表分成各自的表文件罢维,操作系統(tǒng)不能同時(shí)進(jìn)行
fsync
一次性刷入數(shù)據(jù)到文件中 - mysqld會(huì)持續(xù)保持每個(gè)表文件的
文件句柄
, 以提供維持對(duì)文件的持續(xù)訪問(wèn)
三丙挽、General Tablespaces
- 通用表空間又叫
共享表空間
肺孵,他可以存儲(chǔ)多個(gè)表
的數(shù)據(jù) - 如果存儲(chǔ)相同數(shù)量的表,消耗的存儲(chǔ)比
每表表空間
小
- 在MySQL 5.7.24中棄用了將表分區(qū)放置在常規(guī)表空間中的支持颜阐,并且在將來(lái)的MySQL版本中將不再支持平窘。
四、Temporary Tablespaces
存儲(chǔ)在一個(gè)叫 ibtmp1
的文件中凳怨。正常情況下Mysql啟動(dòng)的時(shí)候會(huì)創(chuàng)建臨時(shí)表空間瑰艘,停止的時(shí)候會(huì)刪除臨時(shí)表空間。并且它能夠自動(dòng)擴(kuò)容肤舞。
五紫新、Undo Tablespaces
- 提供修改操作的
原子性
,即當(dāng)修改到一半李剖,出現(xiàn)異常芒率,可以通過(guò)Undo 日志回滾。 - 它存儲(chǔ)了篙顺,事務(wù)開(kāi)始前的原始數(shù)據(jù)與這次的修改操作偶芍。
- Undo log 存在于回滾段(rollback segment)中,回滾段又存在
系統(tǒng)表空間``撤銷(xiāo)表空間``臨時(shí)表空間
中德玫,如架構(gòu)圖所示匪蟀。
Redo Log
前面已經(jīng)介紹過(guò)
總結(jié)一下,我們執(zhí)行一句update SQL 會(huì)發(fā)生什么
- 查詢到我們要修改的那條數(shù)據(jù)化焕,我們這里稱(chēng)做
origin
萄窜,返給執(zhí)行器 - 在執(zhí)行器中,修改數(shù)據(jù)撒桨,稱(chēng)為
modification
- 將
modification
刷入內(nèi)存查刻,Buffer Pool
的Change Buffer
- 引擎層:記錄undo log (實(shí)現(xiàn)事務(wù)原子性)
- 引擎層:記錄redo log (崩潰恢復(fù)使用)
- 服務(wù)層:記錄bin log(記錄DDL)
- 返回更新成功結(jié)果
- 數(shù)據(jù)等待被工作線程刷入磁盤(pán)
Bin log
說(shuō)了 Undo
、Redo
也順便說(shuō)一下Bin log
.
- 這一個(gè)log和
innodb
引擎沒(méi)有多大關(guān)系凤类,我們前面說(shuō)的那兩種日志穗泵,都在是innodb引擎層的。而Bin log
是處于服務(wù)層
的谜疤。所以他能被各個(gè)引擎所通用 - 他的主要作用是什么呢佃延?首先现诀,
Bin log
是以事件的形式,記錄了各個(gè)DDL DML
語(yǔ)句履肃,它是一種邏輯意義上的日志仔沿。 - 能夠?qū)崿F(xiàn)
主從復(fù)制
,從
服務(wù)器拿到主
服務(wù)器的bin log
日志尺棋,然后執(zhí)行封锉。 - 做
數(shù)據(jù)恢復(fù)
,拿到某個(gè)時(shí)間段的日志膘螟,重新執(zhí)行一遍成福。
華麗的分割線
索引篇
跟隨一條SQL語(yǔ)句完成全局預(yù)覽后,我們來(lái)看看回過(guò)頭來(lái)讓SQL變得更加豐富荆残,添加一個(gè)索引
試試
Innodb中將文件存儲(chǔ)分為了四個(gè)級(jí)別
Pages, Extents, Segments, and Tablespaces
它們的關(guān)系是:
- 默認(rèn)的
extent
大小為1M
即64
個(gè)16KB
的Page
奴艾。平常我們文件系統(tǒng)所說(shuō)的頁(yè)大小是4KB
,包含8
個(gè)512Byte
的扇區(qū)内斯。
插入數(shù)據(jù)
如果我們?cè)谝粋€(gè)有序的字段上蕴潦,建立索引,然后插入數(shù)據(jù)俘闯。
在存儲(chǔ)的時(shí)候品擎,innodb就會(huì)按著順序一個(gè)個(gè)存儲(chǔ)到 頁(yè)
上,存滿一個(gè)頁(yè)再去申請(qǐng)新的頁(yè)备徐,然后接著存。
但如果我們的字段是無(wú)序的甚颂,存儲(chǔ)的位置就會(huì)在不同的頁(yè)上蜜猾。當(dāng)我們的數(shù)據(jù)存儲(chǔ)到一個(gè)已經(jīng)被 存滿
的頁(yè)
上時(shí),就會(huì)造成頁(yè)分裂
振诬,從而形成碎片
蹭睡。
存儲(chǔ)結(jié)構(gòu) B樹(shù)變體 B+樹(shù)
幾種不同的索引組織形式
- 聚簇索引,如上面
B+樹(shù)
圖所示赶么,子節(jié)點(diǎn)上存儲(chǔ)行數(shù)據(jù)
肩豁,并且索引的排列的順序
和索引鍵值順序
一致的話就是聚簇索引
。主鍵索引就是聚簇索引辫呻,除了主鍵索引清钥,其他所以都是輔助索引
- 輔助索引,如果我們創(chuàng)建了一個(gè)
輔助索引
放闺,它的葉子節(jié)點(diǎn)上只存儲(chǔ)自己的值
和主鍵索引的值
祟昭。這就意味著,如果我們通過(guò)輔助索引查詢所有數(shù)據(jù)怖侦,就會(huì)先去查找輔助索引
中的主鍵鍵值
篡悟,然后再去主鍵索引
里面谜叹,查到相關(guān)數(shù)據(jù)
。這個(gè)過(guò)程稱(chēng)為回表
-
rowid
如果沒(méi)有主鍵索引
怎么辦呢搬葬?- 沒(méi)有主鍵荷腊,但是有一個(gè) Unique key 而且都不是 null的,則會(huì)根據(jù)這個(gè) key來(lái)創(chuàng)建
聚簇索引
急凰。 - 那上面兩種都沒(méi)有呢女仰,別擔(dān)心,innodb自己維護(hù)了一個(gè)叫
rowid
的東西香府,根據(jù)這個(gè)id來(lái)創(chuàng)建聚簇索引
- 沒(méi)有主鍵荷腊,但是有一個(gè) Unique key 而且都不是 null的,則會(huì)根據(jù)這個(gè) key來(lái)創(chuàng)建
索引如何起作用
搞清楚什么是索引董栽,結(jié)構(gòu)是什么之后。
我們來(lái)看看企孩,什么時(shí)候我們要用到索引锭碳,理解了這些能更好的幫助我們創(chuàng)建正確高效的索引
離散度低不建索引,也就是數(shù)據(jù)之間相差不大的就沒(méi)必要建立索引勿璃。(因?yàn)榻⑺饕芘祝诓樵兊臅r(shí)候,innodb大多數(shù)據(jù)都是相同的补疑,我走索引 和全表沒(méi)什么差別就會(huì)直接
全表查詢
)歧沪。比如 性別字段。這樣反而浪費(fèi)了大量的存儲(chǔ)空間莲组。-
聯(lián)合字段索引诊胞,比如
idx(name, class_name)
- 當(dāng)執(zhí)行
select * from stu where class_name = xx and name = lzw
查詢時(shí),也能走idx
這個(gè)索引的锹杈,因?yàn)閮?yōu)化器將SQL優(yōu)化為了name = lzw and class_name = xx
- 當(dāng)需要有
select ··· where name = lzw
的時(shí)候撵孤,不需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的name
索引,會(huì)直接走idx
這個(gè)索引 -
覆蓋索引
竭望。如果我們此次查詢的所有數(shù)據(jù)
全都包含在索引里面了邪码,就不需要再回表
去查詢了。比如:select class_name from stu where name =lzw
- 當(dāng)執(zhí)行
-
索引條件下推(index_condition_pushdown)
- 有這樣一條SQL咬清,
select * from stu where name = lzw and class_name like '%xx'
- 如果沒(méi)有
索引條件下推
闭专,因?yàn)楹竺媸?like '%xx'
的查詢條件,所以這里首先根據(jù)name
走idx聯(lián)合索引
查詢到幾條數(shù)據(jù)后旧烧,再回表
查詢到全量row數(shù)據(jù)
影钉,然后在server層
進(jìn)行 like 過(guò)濾找到數(shù)據(jù) - 如果有,則直接在
引擎層
對(duì)like也進(jìn)行過(guò)濾了粪滤,相當(dāng)于把server層
這個(gè)過(guò)濾操作下推到引擎層
了斧拍。如圖所示:
- 有這樣一條SQL咬清,
建立索引注意事項(xiàng)
- 在where、order杖小、join的on 使用次數(shù)多的時(shí)候肆汹,加上索引
- 離散度高的字段才能建立索引
- 聯(lián)合索引把離散度高的放前面(因?yàn)槭紫雀鶕?jù)第一個(gè)字段匹配愚墓,能迅速定位數(shù)據(jù)位置。)
- 頻繁更新的字段不能建索引(造成
頁(yè)分裂
昂勉,索引按順序存儲(chǔ)协饲,如果存儲(chǔ)頁(yè)滿了闷愤,再去插入就會(huì)造成頁(yè)分裂) - 使用比如replace严望、sum亭病、count等
函數(shù)
的時(shí)候不會(huì)使用索引,所以沒(méi)必要額外建 - 出現(xiàn)隱式轉(zhuǎn)化的時(shí)候攒至,比如字符串轉(zhuǎn)int厚者,也用不到索引
- 特別長(zhǎng)的字段,可以截取前面幾位創(chuàng)建索引(可以通過(guò)
select count(distinct left(name, 10))/count(*)
來(lái)看離散度迫吐,決定到底提取前幾位)
- tips: 執(zhí)行一個(gè)SQL库菲,不能確切說(shuō)他是否能不能用到索引,畢竟這一切都是
優(yōu)化器決定的
志膀。比如你使用了Cost Base Optimizer
基于開(kāi)銷(xiāo)的優(yōu)化器熙宇,那種開(kāi)銷(xiāo)小就用哪種優(yōu)化。
又一個(gè)華麗的分割線
鎖篇
四大特性
- 原子性(通過(guò)Undo log實(shí)現(xiàn))
- 一致性
- 隔離性
- 持久性(崩潰恢復(fù)溉浙,Redo log + double write 實(shí)現(xiàn))
讀一致性問(wèn)題應(yīng)該由數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別來(lái)解決 (SQL92 標(biāo)準(zhǔn))
前提烫止,在一個(gè)事務(wù)中:
- 臟讀(讀到了別人還沒(méi)有commit的數(shù)據(jù),然后別人又回滾掉了)
- 不可重復(fù)讀(第一次讀取到了數(shù)據(jù)戳稽,然后別人修改commit了馆蠕,再次去讀取就讀到了別人已經(jīng)commit的數(shù)據(jù))
- 幻讀(在范圍查詢的時(shí)候,讀到別人新添加的數(shù)據(jù))
SQL92 標(biāo)準(zhǔn)規(guī)定:
(并發(fā)度從左到右惊奇,依次降低)
是否存在 | Read Uncommited | Read Committed | Repeatable Read | Serializable |
臟讀 | ?? | × | × | × |
不可重復(fù)度 | ?? | ?? | × | × |
幻讀 | ?? | ?? | ??(Innodb中×) | × |
- tips: Innodb中荆几,Repeatable Read的幻讀,也不可能存在赊时,是因?yàn)樗约航鉀Q了
Innodb中如何解決 可重復(fù)讀(RR) 中產(chǎn)生幻讀的情況
鎖模型
- LBCC (Lock Based Concurrency Control) 讀之前加個(gè)鎖,但這樣可能會(huì)導(dǎo)致性能問(wèn)題 => 讀的時(shí)候加鎖導(dǎo)致其他事務(wù)都不能讀寫(xiě)了行拢,性能低下
- MVCC(Multi Version Concurrency Control) 讀的時(shí)候記錄當(dāng)時(shí)快照祖秒,別人來(lái)讀取快照就行 => 性能消耗,存儲(chǔ)消耗
這兩種方案在Innodb中結(jié)合使用舟奠。這里簡(jiǎn)要說(shuō)明一下 RR 的 MVCC實(shí)現(xiàn)
竭缝,圖中 回滾id 初始值不應(yīng)該為0而是NULL,這里為了方便寫(xiě)成0
-
RC的MVCC實(shí)現(xiàn)是對(duì) 同一個(gè)事務(wù)的多個(gè)讀 創(chuàng)建一個(gè)版本
而RR 是 同一個(gè)事務(wù)任何一條都創(chuàng)建一個(gè)版本
通過(guò)MVCC
與LBCC
的結(jié)合沼瘫,InnoDB能解決對(duì)于不加鎖
條件下的 幻讀的情況抬纸。而不必像 Serializable
一樣,必須讓事務(wù)串行
進(jìn)行耿戚,無(wú)任何并發(fā)
湿故。
下面我們來(lái)深入研究一下InnoDB鎖
是如何實(shí)現(xiàn) RR
事務(wù)隔離級(jí)別的
鎖深入 MVCC在Innodb的實(shí)現(xiàn)
一阿趁、Innodb 的鎖
- Shared and Exclusive Locks 共享和排它鎖 =>(S、X)
- Intention Locks 意向鎖 => 這里指的是兩把鎖坛猪,其實(shí)就是
表級(jí)別
的 共享和排它鎖 => (IS脖阵、IX)
上面這四把鎖
是最基本鎖的類(lèi)型
- Record Locks 記錄鎖
- Gap Locks 間隙鎖
- Next-key Locks 臨鎖
這三把鎖,理解成對(duì)于上面四把鎖
實(shí)現(xiàn)的三種算法方式墅茉,我們這里暫且把它們稱(chēng)為:高階鎖
- Insert Intention Locks 插入鎖
- AUTO-INC Locks 自增鍵鎖
- Predicate Locks for Spatial Indexes 專(zhuān)用于給Spatial Indexes用的
上面三把是額外擴(kuò)展的鎖
二命黔、讀寫(xiě)鎖深入解釋
- 要使用共享鎖,在語(yǔ)句后面加上
lock in share mode
就斤。排它鎖默認(rèn)Insert悍募、Update、Delete
會(huì)使用洋机。顯示使用在語(yǔ)句后加for update
坠宴。 - 意向鎖都是由數(shù)據(jù)庫(kù)自己維護(hù)的。(主要作用是給表
打一個(gè)標(biāo)記
槐秧,記錄這個(gè)表是否被鎖住了) => 如果沒(méi)有這個(gè)鎖啄踊,別的事務(wù)想鎖住這張表的時(shí)候,就要去全表掃描是否有鎖刁标,效率太低颠通。所以才會(huì)有意向鎖的存在。
補(bǔ)充:Mysql中鎖膀懈,到底鎖的是什么
鎖的是索引顿锰,那么這個(gè)時(shí)候可能有人要問(wèn)了:那如果我不創(chuàng)建索引呢?
索引的存在启搂,我們上面講過(guò)了硼控,這里再回顧一下,有下面幾種情況
- 你建了一個(gè) Primary key胳赌, 就是聚集索引 (存儲(chǔ)的是
完整的數(shù)據(jù)
) - 沒(méi)有主鍵牢撼,但是有一個(gè) Unique key 而是都不是 null的,則會(huì)根據(jù)這個(gè) key來(lái)創(chuàng)建
聚簇索引
- 那上面兩種都沒(méi)有呢疑苫,別擔(dān)心熏版,innodb自己維護(hù)了一個(gè)叫
rowid
的東西,根據(jù)這個(gè)id來(lái)創(chuàng)建聚簇索引
所以一個(gè)表里面捍掺,必然會(huì)存在一個(gè)索引撼短,所以鎖當(dāng)然總有索引拿來(lái)鎖住了。
當(dāng)要給一張你沒(méi)有顯示創(chuàng)建索引
的表挺勿,進(jìn)行加鎖查詢
時(shí)曲横,數(shù)據(jù)庫(kù)其實(shí)是不知道到底要查哪些數(shù)據(jù)的,整張表可能都會(huì)用到不瓶。所以索性就鎖整張表
禾嫉。
- 如果是給
輔助索引
加寫(xiě)鎖灾杰,比如select * from where name = ’xxx‘ for update
最后要回表
查主鍵上的信息,所以這個(gè)時(shí)候除了鎖輔助索引
還要鎖主鍵索引
三夭织、高階鎖深入解釋
首先上三個(gè)概念吭露,有這么一組數(shù)據(jù):主鍵是 1,3尊惰,6讲竿,9
在存儲(chǔ)時(shí)候有如下:x 1 x 3 x x 6 x x x 9 x···
記錄鎖,鎖的是每個(gè)記錄弄屡,也就是 1题禀,3,6膀捷,9
間隙鎖迈嘹,鎖的是記錄間隙,每個(gè) x
全庸,(-∞,1), (1,3), (3,6), (6,9), (9,+∞)
臨鎖秀仲,鎖的是 (-∞,1], (1,3], (3,6], (6,9], (9,+∞] 左開(kāi)右閉的區(qū)間
首先這三種鎖都是 排它鎖
, 并且 臨鍵鎖 = 記錄鎖 + 間隙鎖
- 當(dāng)
select * from xxx where id = 3 for update
時(shí)壶笼,產(chǎn)生記錄鎖 - 當(dāng)
select * from xxx where id = 5 for update
時(shí)神僵,產(chǎn)生間隙鎖 => 鎖住了(3,6),這里要格外注意一點(diǎn):間隙鎖之間是不沖突的覆劈。 - 當(dāng)
select * from xxx where id = 5 for update
時(shí)保礼,產(chǎn)生臨鍵鎖 => 鎖住了(3,6], mysql默認(rèn)使用臨鍵鎖,如果不滿足 1 责语,2 情況 則他的行鎖的都是臨鍵鎖
- 回到開(kāi)始的問(wèn)題炮障,在這里
Record Lock 行鎖
防止別的事務(wù)修改或刪除,Gap Lock 間隙鎖
防止別的事務(wù)新增坤候,Gap Lock 和 Record Lock
結(jié)合形成的Next-Key鎖
共同解決RR級(jí)別
在寫(xiě)數(shù)據(jù)時(shí)的幻讀問(wèn)題胁赢。
說(shuō)到了鎖那么必然逃不過(guò)要說(shuō)一下死鎖
發(fā)生死鎖后的檢查
-
show status like 'innodb_row_lock_%'
- Innodb_row_lock_current_waits 當(dāng)前正在有多少等待鎖
- Innodb_row_lock_time 一共等待了多少時(shí)間
- Innodb_row_lock_time_avg 平均等多少時(shí)間
- Innodb_row_lock_time_max 最大等多久
- Innodb_row_lock_waits 一共出現(xiàn)過(guò)多少次等待
-
select * from information_schema.INNODB_TRX
能查看到當(dāng)前正在運(yùn)行和被鎖住的事務(wù) -
show full processlist
=select * from information_schema.processlist
能查詢出是哪個(gè)用戶
在哪臺(tái)機(jī)器host的哪個(gè)端口上
連接哪個(gè)數(shù)據(jù)庫(kù)
執(zhí)行什么指令
的狀態(tài)與時(shí)間
死鎖預(yù)防
- 保證訪問(wèn)數(shù)據(jù)的順序
- 避免where的時(shí)候不用索引(這樣會(huì)鎖表,不僅死鎖更容易產(chǎn)生白筹,而且性能更加低下)
- 一個(gè)非常大的事務(wù)徘键,拆成多個(gè)小的事務(wù)
- 盡量使用等值查詢(就算用范圍查詢也要限定一個(gè)區(qū)間,而不要只開(kāi)不閉遍蟋,比如 id > 1 就鎖住后面所有)
分庫(kù)分表
動(dòng)態(tài)選擇數(shù)據(jù)源
編碼層 -- 實(shí)現(xiàn) AbstracRoutingDataSource
=>
框架層 -- 實(shí)現(xiàn) Mybatis Plugin
=>
驅(qū)動(dòng)層 -- Sharding-JDBC(配置多個(gè)數(shù)據(jù)源,根據(jù)自定義實(shí)現(xiàn)的策略對(duì)數(shù)據(jù)進(jìn)行分庫(kù)分表存儲(chǔ))核心流程,SQL解析=>執(zhí)行優(yōu)化=>SQL數(shù)據(jù)庫(kù)路由=>SQL改變(比如分表螟凭,改表名)=>SQL執(zhí)行=>結(jié)果歸并)
=>
代理層 -- Mycat(將所有與數(shù)據(jù)庫(kù)的連接獨(dú)立出來(lái)虚青。全部由Mycat連接,其他服務(wù)訪問(wèn)Mycat獲取數(shù)據(jù))
=>
服務(wù)層 -- 特殊的SQL版本
MYSQL如何做優(yōu)化
說(shuō)到底我們學(xué)習(xí)這么多知識(shí)都是為了能更好使用MYSQL螺男,那就讓我們來(lái)實(shí)操一下棒厘,建立一個(gè)完整的優(yōu)化體系
要想獲得更好的查詢性能纵穿,可以從這張查詢執(zhí)行過(guò)程
入手
一、客戶端連接池
添加連接池奢人,避免每次都新建谓媒、銷(xiāo)毀連接
那我們的連接池是不是越多越好呢?
有興趣的盆友可以看看這篇文章:About Pool Sizing
我大概總結(jié)一下:
- 我們并發(fā)的執(zhí)行SQL何乎,并不會(huì)因?yàn)檫B接數(shù)量增多而變快句惯。為什么呢?如果我有10000連接同時(shí)并發(fā)執(zhí)行支救,難道不比你10個(gè)連接執(zhí)行快得多嗎? 答案是否定的抢野,不僅不快反而越來(lái)越慢。
- 在計(jì)算機(jī)中各墨,我們都知道只有
CPU
才能真正去執(zhí)行線程
指孤。而操作系統(tǒng)因?yàn)橛?code>時(shí)間分片的技術(shù),讓我們以為一個(gè)CPU內(nèi)核
執(zhí)行了多個(gè)線程
贬堵。 - 但其實(shí)上一個(gè)
CPU
在某個(gè)時(shí)間段
只能執(zhí)行一個(gè)線程
恃轩,所以無(wú)論我們?cè)趺丛黾硬l(fā),CPU
還是只能在這個(gè)時(shí)間段里處理這么多數(shù)據(jù)黎做。 - 那就算
CPU
處理不了這么多數(shù)據(jù)叉跛,又怎么會(huì)變慢?因?yàn)?code>時(shí)間分片引几,當(dāng)多個(gè)線程看起來(lái)在"同時(shí)執(zhí)行"
昧互,其實(shí)他們之間的上下文切換
十分耗時(shí) - 所以,一旦線程的數(shù)量超過(guò)了CPU核心的數(shù)量伟桅,再增加線程數(shù)系統(tǒng)就只會(huì)更慢敞掘,而不是更快。
- 在計(jì)算機(jī)中各墨,我們都知道只有
- 當(dāng)然楣铁,這只是其中最核心的原因玖雁,磁盤(pán)同樣也會(huì)對(duì)速度有影響,同時(shí)也對(duì)我們連接數(shù)配置有影響盖腕。
- 比如我們用的機(jī)械硬盤(pán)赫冬,我們要通過(guò)旋轉(zhuǎn),尋址到某個(gè)位置溃列,再進(jìn)行
I/O
操作劲厌,這個(gè)時(shí)候,CPU
就可以把時(shí)間听隐,分片給其他線程
补鼻,以提升處理效率和速度 - 所以,如果你用的是機(jī)械硬盤(pán),我們通撤绶叮可以多添加一些連接數(shù)咨跌,保持高并發(fā)
- 但如果你用的是 SSD 呢,因?yàn)?code>I/O等待時(shí)間非常短硼婿,所以我們就不能添加過(guò)多連接數(shù)
- 比如我們用的機(jī)械硬盤(pán)赫冬,我們要通過(guò)旋轉(zhuǎn),尋址到某個(gè)位置溃列,再進(jìn)行
- 通過(guò)來(lái)說(shuō)你需要遵循這么一個(gè)公式:
線程數(shù) = ((核心數(shù) * 2) + 有效磁盤(pán)數(shù))
锌半。比如一臺(tái)i7 4core 1hard disk
的機(jī)器,就是 4 * 2 + 1 = 9 - 看到這個(gè)公式不知道大家是不是很眼熟寇漫,這不僅適用于數(shù)據(jù)庫(kù)連接刊殉,也適用于任何
很多CPU計(jì)算和I/O的場(chǎng)景
比如:設(shè)置最大線程數(shù)等
二、數(shù)據(jù)庫(kù)整體設(shè)計(jì)方案
第三方緩存
如果并發(fā)非常大猪腕,就不能讓他們?nèi)虻綌?shù)據(jù)庫(kù)上冗澈,在客戶端連接數(shù)據(jù)庫(kù)查詢時(shí),添加如Redis
這種三方緩存
集群方式部署數(shù)據(jù)庫(kù)
既然我們一個(gè)數(shù)據(jù)庫(kù)承受不了巨大的并發(fā)陋葡,那為什么不多添加幾臺(tái)機(jī)器呢亚亲?
主從復(fù)制原理圖
從圖中我們不難看出、Mysql主從復(fù)制 讀寫(xiě)分離
異步復(fù)制
的特性腐缤。
- tips: 在把
Binary Log
寫(xiě)入relay log
之后捌归,slave
都會(huì)把最新讀取到的Binary Log Position
記錄到master info
上,下一次就直接從這個(gè)位置去取岭粤。
不同方式的主從復(fù)制
上面這種異步
的主從復(fù)制惜索,很明顯的一個(gè)問(wèn)題就是,更新不及時(shí)的問(wèn)題剃浇。當(dāng)寫(xiě)入一個(gè)數(shù)據(jù)后巾兆,馬上有用戶讀取,讀取的還是之前的數(shù)據(jù)虎囚,也就是存在著延時(shí)角塑。
要解決延時(shí)的問(wèn)題,就需要引入 事務(wù)
- 全同步復(fù)制淘讥,事務(wù)方式執(zhí)行圃伶,主節(jié)點(diǎn)先寫(xiě)入,然后讓所有slave寫(xiě)蒲列,必須要所有 從節(jié)點(diǎn) 把數(shù)據(jù)寫(xiě)完窒朋,才返回寫(xiě)成功,這樣的話會(huì)大大影響寫(xiě)入的性能
- 半同步復(fù)制蝗岖,只要有一個(gè)salve寫(xiě)入數(shù)據(jù)侥猩,就算成功。(如果需要半同步復(fù)制抵赢,主從節(jié)點(diǎn)都需要安裝semisync_mater.so和 semisync_slave.so插件)
- GTID(global transaction identities)復(fù)制欺劳,主庫(kù)并行復(fù)制的時(shí)候洛退,從庫(kù)也并行復(fù)制,解決主從同步復(fù)制延遲杰标,實(shí)現(xiàn)自動(dòng)的
failover
動(dòng)作,即主節(jié)點(diǎn)掛掉彩匕,選舉從節(jié)點(diǎn)后腔剂,能快速自動(dòng)避免數(shù)據(jù)丟失。
集群高可用方案
- 主從 HAPrxoy + keeplive
- NDB
- Glaera Cluster for MySQL
- MHA(Master-Mater replication manager for MySQL)驼仪,MMM(MySQL Master High Available)
- MGR(MySQL Group Replication) => MySQL Cluster
分表
對(duì)數(shù)據(jù)進(jìn)行分類(lèi)劃分掸犬,分成不同表,減少對(duì)單一表造成過(guò)多鎖操作
影響性能
表結(jié)構(gòu)
- 設(shè)計(jì)合理字段類(lèi)型
- 設(shè)計(jì)合理字段長(zhǎng)度
三绪爸、優(yōu)化器與執(zhí)行引擎
慢日志
開(kāi)啟show_query_log
湾碎,執(zhí)行時(shí)間超過(guò)變量long_query_time
的SQL會(huì)被記錄下來(lái)。
可以使用mysqldumpslow /var/lib/mysql/mysql-slow.log
奠货,還有很多插件可以提供比這個(gè)更優(yōu)雅的分析介褥,這里就不詳細(xì)講了。
explain分析SQL
任何SQL在寫(xiě)完之后都應(yīng)該explain
一下
1. 驅(qū)動(dòng)表 - 比如濫用left/right join
導(dǎo)致性能低下
- 使用
left/right join
會(huì)直接指定驅(qū)動(dòng)表递惋,在MYSQL中柔滔,默認(rèn)使用Nest loop join
進(jìn)行表關(guān)聯(lián)(即通過(guò)驅(qū)動(dòng)表
的結(jié)果集作為循環(huán)基礎(chǔ)數(shù)據(jù),然后通過(guò)此集合中的每一條數(shù)據(jù)篩選下一個(gè)關(guān)聯(lián)表的數(shù)據(jù)萍虽,最后合并結(jié)果睛廊,得出我們常說(shuō)的臨時(shí)表
)。 - 如果
驅(qū)動(dòng)表
的數(shù)據(jù)是百萬(wàn)千萬(wàn)
級(jí)別的杉编,可想而知這聯(lián)表查詢得有多慢超全。但是反過(guò)來(lái),如果以小表
作為驅(qū)動(dòng)表
邓馒,借助千萬(wàn)級(jí)表
的索引
查詢就能變得很快嘶朱。 - 如果你不確定到底該用誰(shuí)來(lái)作為
驅(qū)動(dòng)表
,那么請(qǐng)交給優(yōu)化器來(lái)決定绒净,比如:select xxx from table1, table2, table3 where ···
见咒,優(yōu)化器會(huì)將查詢記錄行數(shù)少的表作為驅(qū)動(dòng)表。 - 如果你就是想自己指定
驅(qū)動(dòng)表
挂疆,那么請(qǐng)拿好Explain
武器改览,在Explain
的結(jié)果中,第一個(gè)就是基礎(chǔ)驅(qū)動(dòng)表
- 排序缤言。同樣的宝当,對(duì)不同
表
排序也是有很大的性能差異,我們盡量對(duì)驅(qū)動(dòng)表
進(jìn)行排序胆萧,而不要對(duì)臨時(shí)表庆揩,也就是合并后的結(jié)果集
進(jìn)行排序俐东。即執(zhí)行計(jì)劃中出現(xiàn)了using temporary
,就需要進(jìn)行優(yōu)化订晌。
2. 執(zhí)行計(jì)劃各參數(shù)含義
- select_type(查詢的類(lèi)型):
普通查詢
和復(fù)雜查詢
(聯(lián)合查詢虏辫、子查詢等)-
SIMPLE
,查詢不包含子查詢或者UNION -
PRIMARY
锈拨,如果查詢包含復(fù)雜查詢
的子結(jié)構(gòu)砌庄,那么就需要用到主鍵查詢 -
SUBQUERY
,在select
或者where
中包含子查詢
-
DERIVED
奕枢,在from
中包含子查詢 -
UNION RESULT
娄昆,從union
表查詢子查詢
-
- table 使用到的表名
- type(訪問(wèn)類(lèi)型),找到所需行的方式缝彬,從上往下萌焰,查詢速度
越來(lái)越快
-
const或者system
常量級(jí)別的掃描,查詢表最快的一種谷浅,system是const的一種特殊情況(表中只有一條數(shù)據(jù)) -
eq_ref
唯一性索引掃描 -
ref
非唯一性索引掃描 -
range
索引的范圍掃描扒俯,比如 between、<壳贪、>等范圍查詢 -
index
(index full)掃描全部索引樹(shù) -
ALL
掃描全表 -
NULL
陵珍,不需要訪問(wèn)表或者索引
-
- possible_keys,給出使用哪個(gè)索引能找到表中的記錄违施。這里被列出的索引
不一定使用
- key:到底
哪一個(gè)索引被真正使用
到了互纯。如果沒(méi)有則為NULL - key_len:使用的索引所占用的字節(jié)數(shù)
- ref:哪個(gè)字段或者常數(shù)和
索引(key)
一起被使用 - rows:一共掃描了多少行
- filtered(百分比):有多少數(shù)據(jù)在server層還進(jìn)行了過(guò)濾
- Extra:額外信息
-
only index
信息只需要從索引中查出,可能用到了覆蓋索引磕蒲,查詢非沉袅剩快 -
using where
如果查詢沒(méi)有使用索引,這里會(huì)在server
層過(guò)濾再使用where
來(lái)過(guò)濾結(jié)果集 -
impossible where
啥也沒(méi)查出來(lái) -
using filesort
辣往,只要沒(méi)有通過(guò)索引來(lái)排序兔院,而是使用了其他排序的方式就是 filesort -
using temporary
(需要通過(guò)臨時(shí)表來(lái)對(duì)結(jié)果集進(jìn)行暫時(shí)存儲(chǔ),然后再進(jìn)行計(jì)算站削。)一般來(lái)說(shuō)這種情況都是進(jìn)行了DISTINCT坊萝、排序、分組
-
using index condition
索引下推许起,上文講過(guò)十偶,就是把server層
這個(gè)過(guò)濾操作下推到引擎層
-
四、存儲(chǔ)引擎
- 當(dāng)僅僅是
插入與查詢
比較多的時(shí)候园细,可以使用MyISAM
存儲(chǔ)引擎 - 當(dāng)只是使用臨時(shí)數(shù)據(jù)惦积,可以使用
memory
- 當(dāng)
插入、更新猛频、查詢
等并發(fā)數(shù)很多時(shí)狮崩,可以使用InnoDB
總結(jié)
從五個(gè)層次回答MYSQL優(yōu)化蛛勉,由上至下
- SQL與索引
- 存儲(chǔ)引擎與表結(jié)構(gòu)
- 數(shù)據(jù)庫(kù)架構(gòu)
- MySQL配置
- 硬件與操作系統(tǒng)
除此之外,查數(shù)據(jù)慢睦柴,要不僅僅拘留于一味的 "優(yōu)化" 數(shù)據(jù)庫(kù)诽凌,而是要從業(yè)務(wù)應(yīng)用層面去分析。比如對(duì)數(shù)據(jù)進(jìn)行緩存坦敌,對(duì)請(qǐng)求進(jìn)行限流等皿淋。
我們下篇文章見(jiàn)