2萬(wàn)字60道MySQL經(jīng)典面試題總結(jié)(附答案)
計(jì)算機(jī)基礎(chǔ)、Java基礎(chǔ)、多線程、JVM、數(shù)據(jù)庫(kù)、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服務(wù)、設(shè)計(jì)模式、架構(gòu)、校招社招分享等核心知識(shí)點(diǎn),歡迎star~
Github地址:https://github.com/Tyson0314/Java-learning
什么是MySQL
MySQL是一個(gè)關(guān)系型數(shù)據(jù)庫(kù),它采用表的形式來(lái)存儲(chǔ)數(shù)據(jù)。你可以理解成是Excel表格,既然是表的形式存儲(chǔ)數(shù)據(jù),就有表結(jié)構(gòu)(行和列)。行代表每一行數(shù)據(jù),列代表該行中的每個(gè)值。列上的值是有數(shù)據(jù)類型的,比如:整數(shù)、字符串、日期等等。
數(shù)據(jù)庫(kù)的三大范式
第一范式1NF
確保數(shù)據(jù)庫(kù)表字段的原子性。
比如字段 userInfo
: 廣東省 10086'
,依照第一范式必須拆分成 userInfo
: 廣東省
userTel
:10086
兩個(gè)字段。
第二范式2NF
首先要滿足第一范式,另外包含兩部分內(nèi)容,一是表必須有一個(gè)主鍵;二是非主鍵列必須完全依賴于主鍵,而不能只依賴于主鍵的一部分。
舉個(gè)例子。假定選課關(guān)系表為student_course
(student_no, student_name, age, course_name, grade, credit),主鍵為(student_no, course_name)。其中學(xué)分完全依賴于課程名稱,姓名年齡完全依賴學(xué)號(hào),不符合第二范式,會(huì)導(dǎo)致數(shù)據(jù)冗余(學(xué)生選n門課,姓名年齡有n條記錄)、插入異常(插入一門新課,因?yàn)闆]有學(xué)號(hào),無(wú)法保存新課記錄)等問(wèn)題。
應(yīng)該拆分成三個(gè)表:學(xué)生:student
(stuent_no, student_name, 年齡);課程:course
(course_name, credit);選課關(guān)系:student_course_relation
(student_no, course_name, grade)。
第三范式3NF
首先要滿足第二范式,另外非主鍵列必須直接依賴于主鍵,不能存在傳遞依賴。即不能存在:非主鍵列 A 依賴于非主鍵列 B,非主鍵列 B 依賴于主鍵的情況。
假定學(xué)生關(guān)系表為Student(student_no, student_name, age, academy_id, academy_telephone),主鍵為"學(xué)號(hào)",其中學(xué)院id依賴于學(xué)號(hào),而學(xué)院地點(diǎn)和學(xué)院電話依賴于學(xué)院id,存在傳遞依賴,不符合第三范式。
可以把學(xué)生關(guān)系表分為如下兩個(gè)表:學(xué)生:(student_no, student_name, age, academy_id);學(xué)院:(academy_id, academy_telephone)。
2NF和3NF的區(qū)別?
2NF依據(jù)是非主鍵列是否完全依賴于主鍵,還是依賴于主鍵的一部分。
3NF依據(jù)是非主鍵列是直接依賴于主鍵,還是直接依賴于非主鍵。
事務(wù)的四大特性?
事務(wù)特性ACID:原子性(Atomicity
)、一致性(Consistency
)、隔離性(Isolation
)、持久性(Durability
)。
原子性是指事務(wù)包含的所有操作要么全部成功,要么全部失敗回滾。
一致性是指一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后都必須處于一致性狀態(tài)。比如a與b賬戶共有1000塊,兩人之間轉(zhuǎn)賬之后無(wú)論成功還是失敗,它們的賬戶總和還是1000。
隔離性。跟隔離級(jí)別相關(guān),如
read committed
,一個(gè)事務(wù)只能讀到已經(jīng)提交的修改。持久性是指一個(gè)事務(wù)一旦被提交了,那么對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的改變就是永久性的,即便是在數(shù)據(jù)庫(kù)系統(tǒng)遇到故障的情況下也不會(huì)丟失提交事務(wù)的操作。
事務(wù)隔離級(jí)別有哪些?
先了解下幾個(gè)概念:臟讀、不可重復(fù)讀、幻讀。
臟讀是指在一個(gè)事務(wù)處理過(guò)程里讀取了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)。
不可重復(fù)讀是指在對(duì)于數(shù)據(jù)庫(kù)中的某行記錄,一個(gè)事務(wù)范圍內(nèi)多次查詢卻返回了不同的數(shù)據(jù)值,這是由于在查詢間隔,另一個(gè)事務(wù)修改了數(shù)據(jù)并提交了。
幻讀是當(dāng)某個(gè)事務(wù)在讀取某個(gè)范圍內(nèi)的記錄時(shí),另外一個(gè)事務(wù)又在該范圍內(nèi)插入了新的記錄。對(duì)幻讀的正確理解是一個(gè)事務(wù)內(nèi)的讀取操作的結(jié)論不能支撐之后業(yè)務(wù)的執(zhí)行。假設(shè)事務(wù)要新增一條記錄,主鍵為id,在新增之前執(zhí)行了select,沒有發(fā)現(xiàn)id為xxx的記錄,但插入時(shí)出現(xiàn)主鍵沖突,這就屬于幻讀,讀取不到記錄卻發(fā)現(xiàn)主鍵沖突是因?yàn)橛涗泴?shí)際上已經(jīng)被其他的事務(wù)插入了,但當(dāng)前事務(wù)不可見。
不可重復(fù)讀和臟讀的區(qū)別是,臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù),而不可重復(fù)讀則是讀取了前一事務(wù)提交的數(shù)據(jù)。
事務(wù)隔離就是為了解決上面提到的臟讀、不可重復(fù)讀、幻讀這幾個(gè)問(wèn)題。
MySQL數(shù)據(jù)庫(kù)為我們提供的四種隔離級(jí)別:
Serializable (串行化):通過(guò)強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問(wèn)題。
Repeatable read (可重復(fù)讀):MySQL的默認(rèn)事務(wù)隔離級(jí)別,它確保同一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行,解決了不可重復(fù)讀的問(wèn)題。
Read committed (讀已提交):一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變??杀苊馀K讀的發(fā)生。
Read uncommitted (讀未提交):所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。
查看隔離級(jí)別:
select @@transaction_isolation;
設(shè)置隔離級(jí)別:
set session transaction isolation level read uncommitted;
生產(chǎn)環(huán)境數(shù)據(jù)庫(kù)一般用的什么隔離級(jí)別呢?
生產(chǎn)環(huán)境大多使用RC。為什么不是RR呢?
可重復(fù)讀(Repeatable Read),簡(jiǎn)稱為RR 讀已提交(Read Commited),簡(jiǎn)稱為RC
緣由一:在RR隔離級(jí)別下,存在間隙鎖,導(dǎo)致出現(xiàn)死鎖的幾率比RC大的多! 緣由二:在RR隔離級(jí)別下,條件列未命中索引會(huì)鎖表!而在RC隔離級(jí)別下,只鎖行!
也就是說(shuō),RC的并發(fā)性高于RR。
并且大部分場(chǎng)景下,不可重復(fù)讀問(wèn)題是可以接受的。畢竟數(shù)據(jù)都已經(jīng)提交了,讀出來(lái)本身就沒有太大問(wèn)題!
編碼和字符集的關(guān)系
我們平時(shí)可以在編輯器上輸入各種中文英文字母,但這些都是給人讀的,不是給計(jì)算機(jī)讀的,其實(shí)計(jì)算機(jī)真正保存和傳輸數(shù)據(jù)都是以二進(jìn)制0101的格式進(jìn)行的。
那么就需要有一個(gè)規(guī)則,把中文和英文字母轉(zhuǎn)化為二進(jìn)制。其中d對(duì)應(yīng)十六進(jìn)制下的64,它可以轉(zhuǎn)換為01二進(jìn)制的格式。于是字母和數(shù)字就這樣一一對(duì)應(yīng)起來(lái)了,這就是ASCII編碼格式。
它用一個(gè)字節(jié),也就是8位
來(lái)標(biāo)識(shí)字符,基礎(chǔ)符號(hào)有128個(gè),擴(kuò)展符號(hào)也是128個(gè)。也就只能表示下英文字母和數(shù)字。
這明顯不夠用。于是,為了標(biāo)識(shí)中文,出現(xiàn)了GB2312的編碼格式。為了標(biāo)識(shí)希臘語(yǔ),出現(xiàn)了greek編碼格式,為了標(biāo)識(shí)俄語(yǔ),整了cp866編碼格式。
為了統(tǒng)一它們,于是出現(xiàn)了Unicode編碼格式,它用了2~4個(gè)字節(jié)來(lái)表示字符,這樣理論上所有符號(hào)都能被收錄進(jìn)去,并且它還完全兼容ASCII的編碼,也就是說(shuō),同樣是字母d,在ASCII用64表示,在Unicode里還是用64來(lái)表示。
但不同的地方是ASCII編碼用1個(gè)字節(jié)來(lái)表示,而Unicode用則兩個(gè)字節(jié)來(lái)表示。
同樣都是字母d,unicode比ascii多使用了一個(gè)字節(jié),如下:
D ? ASCII: ? ? ? ? ? 01100100
D Unicode: ?00000000 01100100
可以看到,上面的unicode編碼,前面的都是0,其實(shí)用不上,但還占了個(gè)字節(jié),有點(diǎn)浪費(fèi)。如果我們能做到該隱藏時(shí)隱藏,這樣就能省下不少空間,按這個(gè)思路,就是就有了UTF-8編碼。
總結(jié)一下,按照一定規(guī)則把符號(hào)和二進(jìn)制碼對(duì)應(yīng)起來(lái),這就是編碼。而把n多這種已經(jīng)編碼的字符聚在一起,就是我們常說(shuō)的字符集。
比如utf-8字符集就是所有utf-8編碼格式的字符的合集。
想看下mysql支持哪些字符集??梢詧?zhí)行 show charset;
utf8和utf8mb4的區(qū)別
上面提到utf-8是在unicode的基礎(chǔ)上做的優(yōu)化,既然unicode有辦法表示所有字符,那utf-8也一樣可以表示所有字符,為了避免混淆,我在后面叫它大utf8。
mysql支持的字符集中有utf8和utf8mb4。
先說(shuō)utf8mb4編碼,mb4就是most bytes 4的意思,從上圖最右邊的Maxlen
可以看到,它最大支持用4個(gè)字節(jié)來(lái)表示字符,它幾乎可以用來(lái)表示目前已知的所有的字符。
再說(shuō)mysql字符集里的utf8,它是數(shù)據(jù)庫(kù)的默認(rèn)字符集。但注意,此utf8非彼utf8,我們叫它小utf8字符集。為什么這么說(shuō),因?yàn)閺腗axlen可以看出,它最多支持用3個(gè)字節(jié)去表示字符,按utf8mb4的命名方式,準(zhǔn)確點(diǎn)應(yīng)該叫它utf8mb3。
utf8 就像是閹割版的utf8mb4,只支持部分字符。比如emoji
表情,它就不支持。
而mysql支持的字符集里,第三列,collation,它是指字符集的比較規(guī)則。
比如,"debug"和"Debug"是同一個(gè)單詞,但它們大小寫不同,該不該判為同一個(gè)單詞呢。
這時(shí)候就需要用到collation了。
通過(guò)SHOW COLLATION WHERE Charset = 'utf8mb4';
可以查看到utf8mb4
下支持什么比較規(guī)則。

如果collation = utf8mb4_general_ci
,是指使用utf8mb4字符集的前提下,挨個(gè)字符進(jìn)行比較(general
),并且不區(qū)分大小寫(_ci,case insensitice
)。
這種情況下,"debug"和"Debug"是同一個(gè)單詞。
如果改成collation=utf8mb4_bin
,就是指挨個(gè)比較二進(jìn)制位大小。
于是"debug"和"Debug"就不是同一個(gè)單詞。
那utf8mb4對(duì)比utf8有什么劣勢(shì)嗎?
我們知道數(shù)據(jù)庫(kù)表里,字段類型如果是char(2)
的話,里面的2
是指字符個(gè)數(shù),也就是說(shuō)不管這張表用的是什么編碼的字符集,都能放上2個(gè)字符。
而char又是固定長(zhǎng)度,為了能放下2個(gè)utf8mb4的字符,char會(huì)默認(rèn)保留2*4(maxlen=4)= 8
個(gè)字節(jié)的空間。
如果是utf8mb3,則會(huì)默認(rèn)保留 2 * 3 (maxlen=3) = 6
個(gè)字節(jié)的空間。也就是說(shuō),在這種情況下,utf8mb4會(huì)比utf8mb3多使用一些空間。
索引
什么是索引?
索引是存儲(chǔ)引擎用于提高數(shù)據(jù)庫(kù)表的訪問(wèn)速度的一種數(shù)據(jù)結(jié)構(gòu)。它可以比作一本字典的目錄,可以幫你快速找到對(duì)應(yīng)的記錄。
索引一般存儲(chǔ)在磁盤的文件中,它是占用物理空間的。
索引的優(yōu)缺點(diǎn)?
優(yōu)點(diǎn):
加快數(shù)據(jù)查找的速度
為用來(lái)排序或者是分組的字段添加索引,可以加快分組和排序的速度
加快表與表之間的連接
缺點(diǎn):
建立索引需要占用物理空間
會(huì)降低表的增刪改的效率,因?yàn)槊看螌?duì)表記錄進(jìn)行增刪改,需要進(jìn)行動(dòng)態(tài)維護(hù)索引,導(dǎo)致增刪改時(shí)間變長(zhǎng)
索引的作用?
數(shù)據(jù)是存儲(chǔ)在磁盤上的,查詢數(shù)據(jù)時(shí),如果沒有索引,會(huì)加載所有的數(shù)據(jù)到內(nèi)存,依次進(jìn)行檢索,讀取磁盤次數(shù)較多。有了索引,就不需要加載所有數(shù)據(jù),因?yàn)锽+樹的高度一般在2-4層,最多只需要讀取2-4次磁盤,查詢速度大大提升。
什么情況下需要建索引?
經(jīng)常用于查詢的字段
經(jīng)常用于連接的字段建立索引,可以加快連接的速度
經(jīng)常需要排序的字段建立索引,因?yàn)樗饕呀?jīng)排好序,可以加快排序查詢速度
什么情況下不建索引?
where
條件中用不到的字段不適合建立索引表記錄較少。比如只有幾百條數(shù)據(jù),沒必要加索引。
需要經(jīng)常增刪改。需要評(píng)估是否適合加索引
參與列計(jì)算的列不適合建索引
區(qū)分度不高的字段不適合建立索引,如性別,只有男/女/未知三個(gè)值。加了索引,查詢效率也不會(huì)提高。
索引的數(shù)據(jù)結(jié)構(gòu)
索引的數(shù)據(jù)結(jié)構(gòu)主要有B+樹和哈希表,對(duì)應(yīng)的索引分別為B+樹索引和哈希索引。InnoDB引擎的索引類型有B+樹索引和哈希索引,默認(rèn)的索引類型為B+樹索引。
B+樹索引
B+ 樹是基于B 樹和葉子節(jié)點(diǎn)順序訪問(wèn)指針進(jìn)行實(shí)現(xiàn),它具有B樹的平衡性,并且通過(guò)順序訪問(wèn)指針來(lái)提高區(qū)間查詢的性能。
在 B+ 樹中,節(jié)點(diǎn)中的 key
從左到右遞增排列,如果某個(gè)指針的左右相鄰 key
分別是 keyi 和 keyi+1,則該指針指向節(jié)點(diǎn)的所有 key
大于等于 keyi 且小于等于 keyi+1。

進(jìn)行查找操作時(shí),首先在根節(jié)點(diǎn)進(jìn)行二分查找,找到key
所在的指針,然后遞歸地在指針?biāo)赶虻墓?jié)點(diǎn)進(jìn)行查找。直到查找到葉子節(jié)點(diǎn),然后在葉子節(jié)點(diǎn)上進(jìn)行二分查找,找出key
所對(duì)應(yīng)的數(shù)據(jù)項(xiàng)。
MySQL 數(shù)據(jù)庫(kù)使用最多的索引類型是BTREE
索引,底層基于B+樹數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)。
mysql> show index from blog\G;
*************************** 1. row ***************************
? ? ? ?Table: blog
? Non_unique: 0
? ? Key_name: PRIMARY
Seq_in_index: 1
?Column_name: blog_id
? ?Collation: A
?Cardinality: 4
? ? Sub_part: NULL
? ? ? Packed: NULL
? ? ? ? Null:
? Index_type: BTREE
? ? ?Comment:
Index_comment:
? ? ?Visible: YES
? Expression: NULL
哈希索引
哈希索引是基于哈希表實(shí)現(xiàn)的,對(duì)于每一行數(shù)據(jù),存儲(chǔ)引擎會(huì)對(duì)索引列進(jìn)行哈希計(jì)算得到哈希碼,并且哈希算法要盡量保證不同的列值計(jì)算出的哈希碼值是不同的,將哈希碼的值作為哈希表的key值,將指向數(shù)據(jù)行的指針作為哈希表的value值。這樣查找一個(gè)數(shù)據(jù)的時(shí)間復(fù)雜度就是O(1),一般多用于精確查找。
Hash索引和B+樹索引的區(qū)別?
哈希索引不支持排序,因?yàn)楣1硎菬o(wú)序的。
哈希索引不支持范圍查找。
哈希索引不支持模糊查詢及多列索引的最左前綴匹配。
因?yàn)楣1碇袝?huì)存在哈希沖突,所以哈希索引的性能是不穩(wěn)定的,而B+樹索引的性能是相對(duì)穩(wěn)定的,每次查詢都是從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)。
為什么B+樹比B樹更適合實(shí)現(xiàn)數(shù)據(jù)庫(kù)索引?
由于B+樹的數(shù)據(jù)都存儲(chǔ)在葉子結(jié)點(diǎn)中,葉子結(jié)點(diǎn)均為索引,方便掃庫(kù),只需要掃一遍葉子結(jié)點(diǎn)即可,但是B樹因?yàn)槠浞种ЫY(jié)點(diǎn)同樣存儲(chǔ)著數(shù)據(jù),我們要找到具體的數(shù)據(jù),需要進(jìn)行一次中序遍歷按序來(lái)掃,所以B+樹更加適合在區(qū)間查詢的情況,而在數(shù)據(jù)庫(kù)中基于范圍的查詢是非常頻繁的,所以通常B+樹用于數(shù)據(jù)庫(kù)索引。
B+樹的節(jié)點(diǎn)只存儲(chǔ)索引key值,具體信息的地址存在于葉子節(jié)點(diǎn)的地址中。這就使以頁(yè)為單位的索引中可以存放更多的節(jié)點(diǎn)。減少更多的I/O支出。
B+樹的查詢效率更加穩(wěn)定,任何關(guān)鍵字的查找必須走一條從根結(jié)點(diǎn)到葉子結(jié)點(diǎn)的路。所有關(guān)鍵字查詢的路徑長(zhǎng)度相同,導(dǎo)致每一個(gè)數(shù)據(jù)的查詢效率相當(dāng)。
索引有什么分類?
1、主鍵索引:名為primary的唯一非空索引,不允許有空值。
2、唯一索引:索引列中的值必須是唯一的,但是允許為空值。唯一索引和主鍵索引的區(qū)別是:唯一索引字段可以為null且可以存在多個(gè)null值,而主鍵索引字段不可以為null。唯一索引的用途:唯一標(biāo)識(shí)數(shù)據(jù)庫(kù)表中的每條記錄,主要是用來(lái)防止數(shù)據(jù)重復(fù)插入。創(chuàng)建唯一索引的SQL語(yǔ)句如下:
ALTER TABLE table_name
ADD CONSTRAINT constraint_name UNIQUE KEY(column_1,column_2,...);
3、組合索引:在表中的多個(gè)字段組合上創(chuàng)建的索引,只有在查詢條件中使用了這些字段的左邊字段時(shí),索引才會(huì)被使用,使用組合索引時(shí)需遵循最左前綴原則。
4、全文索引:只能在CHAR
、VARCHAR
和TEXT
類型字段上使用全文索引。
5、普通索引:普通索引是最基本的索引,它沒有任何限制,值可以為空。
什么是最左匹配原則?
如果 SQL 語(yǔ)句中用到了組合索引中的最左邊的索引,那么這條 SQL 語(yǔ)句就可以利用這個(gè)組合索引去進(jìn)行匹配。當(dāng)遇到范圍查詢(>
、<
、between
、like
)就會(huì)停止匹配,后面的字段不會(huì)用到索引。
對(duì)(a,b,c)
建立索引,查詢條件使用 a/ab/abc 會(huì)走索引,使用 bc 不會(huì)走索引。
對(duì)(a,b,c,d)
建立索引,查詢條件為a = 1 and b = 2 and c > 3 and d = 4
,那么a、b和c三個(gè)字段能用到索引,而d無(wú)法使用索引。因?yàn)橛龅搅朔秶樵儭?/p>
如下圖,對(duì)(a, b) 建立索引,a 在索引樹中是全局有序的,而 b 是全局無(wú)序,局部有序(當(dāng)a相等時(shí),會(huì)根據(jù)b進(jìn)行排序)。直接執(zhí)行b = 2
這種查詢條件無(wú)法使用索引。

當(dāng)a的值確定的時(shí)候,b是有序的。例如a = 1
時(shí),b值為1,2是有序的狀態(tài)。當(dāng)a = 2
時(shí)候,b的值為1,4也是有序狀態(tài)。 當(dāng)執(zhí)行a = 1 and b = 2
時(shí)a和b字段能用到索引。而執(zhí)行a > 1 and b = 2
時(shí),a字段能用到索引,b字段用不到索引。因?yàn)閍的值此時(shí)是一個(gè)范圍,不是固定的,在這個(gè)范圍內(nèi)b值不是有序的,因此b字段無(wú)法使用索引。
什么是聚集索引?
InnoDB使用表的主鍵構(gòu)造主鍵索引樹,同時(shí)葉子節(jié)點(diǎn)中存放的即為整張表的記錄數(shù)據(jù)。聚集索引葉子節(jié)點(diǎn)的存儲(chǔ)是邏輯上連續(xù)的,使用雙向鏈表連接,葉子節(jié)點(diǎn)按照主鍵的順序排序,因此對(duì)于主鍵的排序查找和范圍查找速度比較快。
聚集索引的葉子節(jié)點(diǎn)就是整張表的行記錄。InnoDB 主鍵使用的是聚簇索引。聚集索引要比非聚集索引查詢效率高很多。
對(duì)于InnoDB
來(lái)說(shuō),聚集索引一般是表中的主鍵索引,如果表中沒有顯示指定主鍵,則會(huì)選擇表中的第一個(gè)不允許為NULL
的唯一索引。如果沒有主鍵也沒有合適的唯一索引,那么InnoDB
內(nèi)部會(huì)生成一個(gè)隱藏的主鍵作為聚集索引,這個(gè)隱藏的主鍵長(zhǎng)度為6個(gè)字節(jié),它的值會(huì)隨著數(shù)據(jù)的插入自增。
什么是覆蓋索引?
select
的數(shù)據(jù)列只用從索引中就能夠取得,不需要回表進(jìn)行二次查詢,也就是說(shuō)查詢列要被所使用的索引覆蓋。對(duì)于innodb
表的二級(jí)索引,如果索引能覆蓋到查詢的列,那么就可以避免對(duì)主鍵索引的二次查詢。
不是所有類型的索引都可以成為覆蓋索引。覆蓋索引要存儲(chǔ)索引列的值,而哈希索引、全文索引不存儲(chǔ)索引列的值,所以MySQL使用b+樹索引做覆蓋索引。
對(duì)于使用了覆蓋索引的查詢,在查詢前面使用explain
,輸出的extra列會(huì)顯示為using index
。
比如user_like
用戶點(diǎn)贊表,組合索引為(user_id, blog_id)
,user_id
和blog_id
都不為null
。
explain select blog_id from user_like where user_id = 13;
explain
結(jié)果的Extra
列為Using index
,查詢的列被索引覆蓋,并且where篩選條件符合最左前綴原則,通過(guò)索引查找就能直接找到符合條件的數(shù)據(jù),不需要回表查詢數(shù)據(jù)。
explain select user_id from user_like where blog_id = 1;
explain
結(jié)果的Extra
列為Using where; Using index
, 查詢的列被索引覆蓋,where篩選條件不符合最左前綴原則,無(wú)法通過(guò)索引查找找到符合條件的數(shù)據(jù),但可以通過(guò)索引掃描找到符合條件的數(shù)據(jù),也不需要回表查詢數(shù)據(jù)。

索引的設(shè)計(jì)原則?
對(duì)于經(jīng)常作為查詢條件的字段,應(yīng)該建立索引,以提高查詢速度
為經(jīng)常需要排序、分組和聯(lián)合操作的字段建立索引
索引列的區(qū)分度越高,索引的效果越好。比如使用性別這種區(qū)分度很低的列作為索引,效果就會(huì)很差。
避免給"大字段"建立索引。盡量使用數(shù)據(jù)量小的字段作為索引。因?yàn)?code>MySQL在維護(hù)索引的時(shí)候是會(huì)將字段值一起維護(hù)的,那這樣必然會(huì)導(dǎo)致索引占用更多的空間,另外在排序的時(shí)候需要花費(fèi)更多的時(shí)間去對(duì)比。
盡量使用短索引,對(duì)于較長(zhǎng)的字符串進(jìn)行索引時(shí)應(yīng)該指定一個(gè)較短的前綴長(zhǎng)度,因?yàn)檩^小的索引涉及到的磁盤I/O較少,查詢速度更快。 ?
索引不是越多越好,每個(gè)索引都需要額外的物理空間,維護(hù)也需要花費(fèi)時(shí)間。
頻繁增刪改的字段不要建立索引。假設(shè)某個(gè)字段頻繁修改,那就意味著需要頻繁的重建索引,這必然影響MySQL的性能
利用最左前綴原則。
索引什么時(shí)候會(huì)失效?
導(dǎo)致索引失效的情況:
對(duì)于組合索引,不是使用組合索引最左邊的字段,則不會(huì)使用索引
以%開頭的like查詢?nèi)?code>%abc,無(wú)法使用索引;非%開頭的like查詢?nèi)?code>abc%,相當(dāng)于范圍查詢,會(huì)使用索引
查詢條件中列類型是字符串,沒有使用引號(hào),可能會(huì)因?yàn)轭愋筒煌l(fā)生隱式轉(zhuǎn)換,使索引失效
判斷索引列是否不等于某個(gè)值時(shí)
對(duì)索引列進(jìn)行運(yùn)算
查詢條件使用
or
連接,也會(huì)導(dǎo)致索引失效
什么是前綴索引?
有時(shí)需要在很長(zhǎng)的字符列上創(chuàng)建索引,這會(huì)造成索引特別大且慢。使用前綴索引可以避免這個(gè)問(wèn)題。
前綴索引是指對(duì)文本或者字符串的前幾個(gè)字符建立索引,這樣索引的長(zhǎng)度更短,查詢速度更快。
創(chuàng)建前綴索引的關(guān)鍵在于選擇足夠長(zhǎng)的前綴以保證較高的索引選擇性。索引選擇性越高查詢效率就越高,因?yàn)檫x擇性高的索引可以讓MySQL在查找時(shí)過(guò)濾掉更多的數(shù)據(jù)行。
建立前綴索引的方式:
// email列創(chuàng)建前綴索引
ALTER TABLE table_name ADD KEY(column_name(prefix_length));
索引下推
參考我的另一篇文章:
常見的存儲(chǔ)引擎有哪些?
MySQL中常用的四種存儲(chǔ)引擎分別是: MyISAM、InnoDB、MEMORY、ARCHIVE。MySQL 5.5版本后默認(rèn)的存儲(chǔ)引擎為InnoDB
。
InnoDB存儲(chǔ)引擎
InnoDB是MySQL默認(rèn)的事務(wù)型存儲(chǔ)引擎,使用最廣泛,基于聚簇索引建立的。InnoDB內(nèi)部做了很多優(yōu)化,如能夠自動(dòng)在內(nèi)存中創(chuàng)建自適應(yīng)hash索引,以加速讀操作。
優(yōu)點(diǎn):支持事務(wù)和崩潰修復(fù)能力;引入了行級(jí)鎖和外鍵約束。
缺點(diǎn):占用的數(shù)據(jù)空間相對(duì)較大。
適用場(chǎng)景:需要事務(wù)支持,并且有較高的并發(fā)讀寫頻率。
MyISAM存儲(chǔ)引擎
數(shù)據(jù)以緊密格式存儲(chǔ)。對(duì)于只讀數(shù)據(jù),或者表比較小、可以容忍修復(fù)操作,可以使用MyISAM引擎。MyISAM會(huì)將表存儲(chǔ)在兩個(gè)文件中,數(shù)據(jù)文件.MYD
和索引文件.MYI
。
優(yōu)點(diǎn):訪問(wèn)速度快。
缺點(diǎn):MyISAM不支持事務(wù)和行級(jí)鎖,不支持崩潰后的安全恢復(fù),也不支持外鍵。
適用場(chǎng)景:對(duì)事務(wù)完整性沒有要求;表的數(shù)據(jù)都會(huì)只讀的。
MEMORY存儲(chǔ)引擎
MEMORY引擎將數(shù)據(jù)全部放在內(nèi)存中,訪問(wèn)速度較快,但是一旦系統(tǒng)奔潰的話,數(shù)據(jù)都會(huì)丟失。
MEMORY引擎默認(rèn)使用哈希索引,將鍵的哈希值和指向數(shù)據(jù)行的指針保存在哈希索引中。
優(yōu)點(diǎn):訪問(wèn)速度較快。
缺點(diǎn):
哈希索引數(shù)據(jù)不是按照索引值順序存儲(chǔ),無(wú)法用于排序。
不支持部分索引匹配查找,因?yàn)楣K饕鞘褂盟饕械娜績(jī)?nèi)容來(lái)計(jì)算哈希值的。
只支持等值比較,不支持范圍查詢。
當(dāng)出現(xiàn)哈希沖突時(shí),存儲(chǔ)引擎需要遍歷鏈表中所有的行指針,逐行進(jìn)行比較,直到找到符合條件的行。
ARCHIVE存儲(chǔ)引擎
ARCHIVE存儲(chǔ)引擎非常適合存儲(chǔ)大量獨(dú)立的、作為歷史記錄的數(shù)據(jù)。ARCHIVE提供了壓縮功能,擁有高效的插入速度,但是這種引擎不支持索引,所以查詢性能較差。
MyISAM和InnoDB的區(qū)別?
存儲(chǔ)結(jié)構(gòu)的區(qū)別。每個(gè)MyISAM在磁盤上存儲(chǔ)成三個(gè)文件。文件的名字以表的名字開始,擴(kuò)展名指出文件類型。 .frm文件存儲(chǔ)表定義。數(shù)據(jù)文件的擴(kuò)展名為.MYD (MYData)。索引文件的擴(kuò)展名是.MYI (MYIndex)。InnoDB所有的表都保存在同一個(gè)數(shù)據(jù)文件中(也可能是多個(gè)文件,或者是獨(dú)立的表空間文件),InnoDB表的大小只受限于操作系統(tǒng)文件的大小,一般為2GB。
存儲(chǔ)空間的區(qū)別。MyISAM支持支持三種不同的存儲(chǔ)格式:靜態(tài)表(默認(rèn),但是注意數(shù)據(jù)末尾不能有空格,會(huì)被去掉)、動(dòng)態(tài)表、壓縮表。當(dāng)表在創(chuàng)建之后并導(dǎo)入數(shù)據(jù)之后,不會(huì)再進(jìn)行修改操作,可以使用壓縮表,極大的減少磁盤的空間占用。InnoDB需要更多的內(nèi)存和存儲(chǔ),它會(huì)在主內(nèi)存中建立其專用的緩沖池用于高速緩沖數(shù)據(jù)和索引。
可移植性、備份及恢復(fù)。MyISAM數(shù)據(jù)是以文件的形式存儲(chǔ),所以在跨平臺(tái)的數(shù)據(jù)轉(zhuǎn)移中會(huì)很方便。在備份和恢復(fù)時(shí)可單獨(dú)針對(duì)某個(gè)表進(jìn)行操作。對(duì)于InnoDB,可行的方案是拷貝數(shù)據(jù)文件、備份 binlog,或者用mysqldump,在數(shù)據(jù)量達(dá)到幾十G的時(shí)候就相對(duì)麻煩了。
是否支持行級(jí)鎖。MyISAM 只支持表級(jí)鎖,用戶在操作myisam表時(shí),select,update,delete,insert語(yǔ)句都會(huì)給表自動(dòng)加鎖,如果加鎖以后的表滿足insert并發(fā)的情況下,可以在表的尾部插入新的數(shù)據(jù)。而InnoDB 支持行級(jí)鎖和表級(jí)鎖,默認(rèn)為行級(jí)鎖。行鎖大幅度提高了多用戶并發(fā)操作的性能。
是否支持事務(wù)和崩潰后的安全恢復(fù)。 MyISAM 不提供事務(wù)支持。而InnoDB 提供事務(wù)支持,具有事務(wù)、回滾和崩潰修復(fù)能力。
是否支持外鍵。MyISAM不支持,而InnoDB支持。
是否支持MVCC。MyISAM不支持,InnoDB支持。應(yīng)對(duì)高并發(fā)事務(wù),MVCC比單純的加鎖更高效。
是否支持聚集索引。MyISAM不支持聚集索引,InnoDB支持聚集索引。
全文索引。MyISAM支持 FULLTEXT類型的全文索引。InnoDB不支持FULLTEXT類型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。
表主鍵。MyISAM允許沒有任何索引和主鍵的表存在,索引都是保存行的地址。對(duì)于InnoDB,如果沒有設(shè)定主鍵或者非空唯一索引,就會(huì)自動(dòng)生成一個(gè)6字節(jié)的主鍵(用戶不可見)。
表的行數(shù)。MyISAM保存有表的總行數(shù),如果
select count(*) from table
;會(huì)直接取出該值。InnoDB沒有保存表的總行數(shù),如果使用select count(*) from table;就會(huì)遍歷整個(gè)表,消耗相當(dāng)大,但是在加了where條件后,MyISAM和InnoDB處理的方式都一樣。
MySQL有哪些鎖?
按鎖粒度分類,有行級(jí)鎖、表級(jí)鎖和頁(yè)級(jí)鎖。
行級(jí)鎖是mysql中鎖定粒度最細(xì)的一種鎖。表示只針對(duì)當(dāng)前操作的行進(jìn)行加鎖。行級(jí)鎖能大大減少數(shù)據(jù)庫(kù)操作的沖突,其加鎖粒度最小,但加鎖的開銷也最大。行級(jí)鎖的類型主要有三類:
Record Lock,記錄鎖,也就是僅僅把一條記錄鎖上;
Gap Lock,間隙鎖,鎖定一個(gè)范圍,但是不包含記錄本身;
Next-Key Lock:Record Lock + Gap Lock 的組合,鎖定一個(gè)范圍,并且鎖定記錄本身。
表級(jí)鎖是mysql中鎖定粒度最大的一種鎖,表示對(duì)當(dāng)前操作的整張表加鎖,它實(shí)現(xiàn)簡(jiǎn)單,資源消耗較少,被大部分mysql引擎支持。最常使用的MyISAM與InnoDB都支持表級(jí)鎖定。
頁(yè)級(jí)鎖是 MySQL 中鎖定粒度介于行級(jí)鎖和表級(jí)鎖中間的一種鎖。表級(jí)鎖速度快,但沖突多,行級(jí)沖突少,但速度慢。因此,采取了折衷的頁(yè)級(jí)鎖,一次鎖定相鄰的一組記錄。
按鎖級(jí)別分類,有共享鎖、排他鎖和意向鎖。
共享鎖又稱讀鎖,是讀取操作創(chuàng)建的鎖。其他用戶可以并發(fā)讀取數(shù)據(jù),但任何事務(wù)都不能對(duì)數(shù)據(jù)進(jìn)行修改(獲取數(shù)據(jù)上的排他鎖),直到已釋放所有共享鎖。
排他鎖又稱寫鎖、獨(dú)占鎖,如果事務(wù)T對(duì)數(shù)據(jù)A加上排他鎖后,則其他事務(wù)不能再對(duì)A加任何類型的封鎖。獲準(zhǔn)排他鎖的事務(wù)既能讀數(shù)據(jù),又能修改數(shù)據(jù)。
意向鎖是表級(jí)鎖,其設(shè)計(jì)目的主要是為了在一個(gè)事務(wù)中揭示下一行將要被請(qǐng)求鎖的類型。InnoDB 中的兩個(gè)表鎖:
意向共享鎖(IS):表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入共享鎖,也就是說(shuō)一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖;
意向排他鎖(IX):類似上面,表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入排他鎖,說(shuō)明事務(wù)在一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
意向鎖是 InnoDB 自動(dòng)加的,不需要用戶干預(yù)。
對(duì)于INSERT、UPDATE和DELETE,InnoDB 會(huì)自動(dòng)給涉及的數(shù)據(jù)加排他鎖;對(duì)于一般的SELECT語(yǔ)句,InnoDB 不會(huì)加任何鎖,事務(wù)可以通過(guò)以下語(yǔ)句顯式加共享鎖或排他鎖。
共享鎖:SELECT … LOCK IN SHARE MODE;
排他鎖:SELECT … FOR UPDATE;
MVCC 實(shí)現(xiàn)原理?
MVCC(Multiversion concurrency control
) 就是同一份數(shù)據(jù)保留多版本的一種方式,進(jìn)而實(shí)現(xiàn)并發(fā)控制。在查詢的時(shí)候,通過(guò)read view
和版本鏈找到對(duì)應(yīng)版本的數(shù)據(jù)。
作用:提升并發(fā)性能。對(duì)于高并發(fā)場(chǎng)景,MVCC比行級(jí)鎖開銷更小。
MVCC 實(shí)現(xiàn)原理如下:
MVCC 的實(shí)現(xiàn)依賴于版本鏈,版本鏈?zhǔn)峭ㄟ^(guò)表的三個(gè)隱藏字段實(shí)現(xiàn)。
DB_TRX_ID
:當(dāng)前事務(wù)id,通過(guò)事務(wù)id的大小判斷事務(wù)的時(shí)間順序。DB_ROLL_PTR
:回滾指針,指向當(dāng)前行記錄的上一個(gè)版本,通過(guò)這個(gè)指針將數(shù)據(jù)的多個(gè)版本連接在一起構(gòu)成undo log
版本鏈。DB_ROW_ID
:主鍵,如果數(shù)據(jù)表沒有主鍵,InnoDB會(huì)自動(dòng)生成主鍵。
每條表記錄大概是這樣的:

使用事務(wù)更新行記錄的時(shí)候,就會(huì)生成版本鏈,執(zhí)行過(guò)程如下:
用排他鎖鎖住該行;
將該行原本的值拷貝到
undo log
,作為舊版本用于回滾;修改當(dāng)前行的值,生成一個(gè)新版本,更新事務(wù)id,使回滾指針指向舊版本的記錄,這樣就形成一條版本鏈。
下面舉個(gè)例子方便大家理解。
1、初始數(shù)據(jù)如下,其中DB_ROW_ID
和DB_ROLL_PTR
為空。

2、事務(wù)A對(duì)該行數(shù)據(jù)做了修改,將age
修改為12,效果如下:

3、之后事務(wù)B也對(duì)該行記錄做了修改,將age
修改為8,效果如下:

4、此時(shí)undo log有兩行記錄,并且通過(guò)回滾指針連在一起。
接下來(lái)了解下read view的概念。
read view
可以理解成將數(shù)據(jù)在每個(gè)時(shí)刻的狀態(tài)拍成“照片”記錄下來(lái)。在獲取某時(shí)刻t的數(shù)據(jù)時(shí),到t時(shí)間點(diǎn)拍的“照片”上取數(shù)據(jù)。
在read view
內(nèi)部維護(hù)一個(gè)活躍事務(wù)鏈表,表示生成read view
的時(shí)候還在活躍的事務(wù)。這個(gè)鏈表包含在創(chuàng)建read view
之前還未提交的事務(wù),不包含創(chuàng)建read view
之后提交的事務(wù)。
不同隔離級(jí)別創(chuàng)建read view的時(shí)機(jī)不同。
read committed:每次執(zhí)行select都會(huì)創(chuàng)建新的read_view,保證能讀取到其他事務(wù)已經(jīng)提交的修改。
repeatable read:在一個(gè)事務(wù)范圍內(nèi),第一次select時(shí)更新這個(gè)read_view,以后不會(huì)再更新,后續(xù)所有的select都是復(fù)用之前的read_view。這樣可以保證事務(wù)范圍內(nèi)每次讀取的內(nèi)容都一樣,即可重復(fù)讀。
read view的記錄篩選方式
前提:DATA_TRX_ID
表示每個(gè)數(shù)據(jù)行的最新的事務(wù)ID;up_limit_id
表示當(dāng)前快照中的最先開始的事務(wù);low_limit_id
表示當(dāng)前快照中的最慢開始的事務(wù),即最后一個(gè)事務(wù)。

如果
DATA_TRX_ID
<up_limit_id
:說(shuō)明在創(chuàng)建read view
時(shí),修改該數(shù)據(jù)行的事務(wù)已提交,該版本的記錄可被當(dāng)前事務(wù)讀取到。如果
DATA_TRX_ID
>=low_limit_id
:說(shuō)明當(dāng)前版本的記錄的事務(wù)是在創(chuàng)建read view
之后生成的,該版本的數(shù)據(jù)行不可以被當(dāng)前事務(wù)訪問(wèn)。此時(shí)需要通過(guò)版本鏈找到上一個(gè)版本,然后重新判斷該版本的記錄對(duì)當(dāng)前事務(wù)的可見性。如果
up_limit_id
<=DATA_TRX_ID
<low_limit_i
:需要在活躍事務(wù)鏈表中查找是否存在ID為
DATA_TRX_ID
的值的事務(wù)。如果存在,因?yàn)樵诨钴S事務(wù)鏈表中的事務(wù)是未提交的,所以該記錄是不可見的。此時(shí)需要通過(guò)版本鏈找到上一個(gè)版本,然后重新判斷該版本的可見性。
如果不存在,說(shuō)明事務(wù)trx_id 已經(jīng)提交了,這行記錄是可見的。
總結(jié):InnoDB 的MVCC
是通過(guò) read view
和版本鏈實(shí)現(xiàn)的,版本鏈保存有歷史版本記錄,通過(guò)read view
判斷當(dāng)前版本的數(shù)據(jù)是否可見,如果不可見,再?gòu)陌姹炬溨姓业缴弦粋€(gè)版本,繼續(xù)進(jìn)行判斷,直到找到一個(gè)可見的版本。
快照讀和當(dāng)前讀
表記錄有兩種讀取方式。
快照讀:讀取的是快照版本。普通的
SELECT
就是快照讀。通過(guò)mvcc來(lái)進(jìn)行并發(fā)控制的,不用加鎖。當(dāng)前讀:讀取的是最新版本。
UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE
是當(dāng)前讀。
快照讀情況下,InnoDB通過(guò)mvcc
機(jī)制避免了幻讀現(xiàn)象。而mvcc
機(jī)制無(wú)法避免當(dāng)前讀情況下出現(xiàn)的幻讀現(xiàn)象。因?yàn)楫?dāng)前讀每次讀取的都是最新數(shù)據(jù),這時(shí)如果兩次查詢中間有其它事務(wù)插入數(shù)據(jù),就會(huì)產(chǎn)生幻讀。
下面舉個(gè)例子說(shuō)明下:
1、首先,user表只有兩條記錄,具體如下:

2、事務(wù)a和事務(wù)b同時(shí)開啟事務(wù)start transaction
;
3、事務(wù)a插入數(shù)據(jù)然后提交;
insert into user(user_name, user_password, user_mail, user_state) values('tyson', 'a', 'a', 0);
4、事務(wù)b執(zhí)行全表的update;
update user set user_name = 'a';
5、事務(wù)b然后執(zhí)行查詢,查到了事務(wù)a中插入的數(shù)據(jù)。(下圖左邊是事務(wù)b,右邊是事務(wù)a。事務(wù)開始之前只有兩條記錄,事務(wù)a插入一條數(shù)據(jù)之后,事務(wù)b查詢出來(lái)是三條數(shù)據(jù))
以上就是當(dāng)前讀出現(xiàn)的幻讀現(xiàn)象。
那么MySQL是如何避免幻讀?
在快照讀情況下,MySQL通過(guò)
mvcc
來(lái)避免幻讀。在當(dāng)前讀情況下,MySQL通過(guò)
next-key
來(lái)避免幻讀(加行鎖和間隙鎖來(lái)實(shí)現(xiàn)的)。
next-key包括兩部分:行鎖和間隙鎖。行鎖是加在索引上的鎖,間隙鎖是加在索引之間的。
Serializable
隔離級(jí)別也可以避免幻讀,會(huì)鎖住整張表,并發(fā)性極低,一般不會(huì)使用。
共享鎖和排他鎖
SELECT 的讀取鎖定主要分為兩種方式:共享鎖和排他鎖。
select * from table where id<6 lock in share mode;--共享鎖 select * from table where id<6 for update;--排他鎖
這兩種方式主要的不同在于LOCK IN SHARE MODE
多個(gè)事務(wù)同時(shí)更新同一個(gè)表單時(shí)很容易造成死鎖。
申請(qǐng)排他鎖的前提是,沒有線程對(duì)該結(jié)果集的任何行數(shù)據(jù)使用排它鎖或者共享鎖,否則申請(qǐng)會(huì)受到阻塞。在進(jìn)行事務(wù)操作時(shí),MySQL會(huì)對(duì)查詢結(jié)果集的每行數(shù)據(jù)添加排它鎖,其他線程對(duì)這些數(shù)據(jù)的更改或刪除操作會(huì)被阻塞(只能讀操作),直到該語(yǔ)句的事務(wù)被commit
語(yǔ)句或rollback
語(yǔ)句結(jié)束為止。
SELECT... FOR UPDATE
使用注意事項(xiàng):
for update
僅適用于innodb,且必須在事務(wù)范圍內(nèi)才能生效。根據(jù)主鍵進(jìn)行查詢,查詢條件為
like
或者不等于,主鍵字段產(chǎn)生表鎖。根據(jù)非索引字段進(jìn)行查詢,會(huì)產(chǎn)生表鎖。
bin log/redo log/undo log
MySQL日志主要包括查詢?nèi)罩?、慢查詢?nèi)罩尽⑹聞?wù)日志、錯(cuò)誤日志、二進(jìn)制日志等。其中比較重要的是 bin log
(二進(jìn)制日志)和 redo log
(重做日志)和 undo log
(回滾日志)。
bin log
bin log
是MySQL數(shù)據(jù)庫(kù)級(jí)別的文件,記錄對(duì)MySQL數(shù)據(jù)庫(kù)執(zhí)行修改的所有操作,不會(huì)記錄select和show語(yǔ)句,主要用于恢復(fù)數(shù)據(jù)庫(kù)和同步數(shù)據(jù)庫(kù)。
redo log
redo log
是innodb引擎級(jí)別,用來(lái)記錄innodb存儲(chǔ)引擎的事務(wù)日志,不管事務(wù)是否提交都會(huì)記錄下來(lái),用于數(shù)據(jù)恢復(fù)。當(dāng)數(shù)據(jù)庫(kù)發(fā)生故障,innoDB存儲(chǔ)引擎會(huì)使用redo log
恢復(fù)到發(fā)生故障前的時(shí)刻,以此來(lái)保證數(shù)據(jù)的完整性。將參數(shù)innodb_flush_log_at_tx_commit
設(shè)置為1,那么在執(zhí)行commit時(shí)會(huì)將redo log
同步寫到磁盤。
undo log
除了記錄redo log
外,當(dāng)進(jìn)行數(shù)據(jù)修改時(shí)還會(huì)記錄undo log
,undo log
用于數(shù)據(jù)的撤回操作,它保留了記錄修改前的內(nèi)容。通過(guò)undo log
可以實(shí)現(xiàn)事務(wù)回滾,并且可以根據(jù)undo log
回溯到某個(gè)特定的版本的數(shù)據(jù),實(shí)現(xiàn)MVCC。
bin log和redo log有什么區(qū)別?
bin log
會(huì)記錄所有日志記錄,包括InnoDB、MyISAM等存儲(chǔ)引擎的日志;redo log
只記錄innoDB自身的事務(wù)日志。bin log
只在事務(wù)提交前寫入到磁盤,一個(gè)事務(wù)只寫一次;而在事務(wù)進(jìn)行過(guò)程,會(huì)有redo log
不斷寫入磁盤。bin log
是邏輯日志,記錄的是SQL語(yǔ)句的原始邏輯;redo log
是物理日志,記錄的是在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改。
講一下MySQL架構(gòu)?
MySQL主要分為 Server 層和存儲(chǔ)引擎層:
Server 層:主要包括連接器、查詢緩存、分析器、優(yōu)化器、執(zhí)行器等,所有跨存儲(chǔ)引擎的功能都在這一層實(shí)現(xiàn),比如存儲(chǔ)過(guò)程、觸發(fā)器、視圖,函數(shù)等,還有一個(gè)通用的日志模塊 binglog 日志模塊。
存儲(chǔ)引擎: 主要負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和讀取。server 層通過(guò)api與存儲(chǔ)引擎進(jìn)行通信。
Server 層基本組件
連接器: 當(dāng)客戶端連接 MySQL 時(shí),server層會(huì)對(duì)其進(jìn)行身份認(rèn)證和權(quán)限校驗(yàn)。
查詢緩存: 執(zhí)行查詢語(yǔ)句的時(shí)候,會(huì)先查詢緩存,先校驗(yàn)這個(gè) sql 是否執(zhí)行過(guò),如果有緩存這個(gè) sql,就會(huì)直接返回給客戶端,如果沒有命中,就會(huì)執(zhí)行后續(xù)的操作。
分析器: 沒有命中緩存的話,SQL 語(yǔ)句就會(huì)經(jīng)過(guò)分析器,主要分為兩步,詞法分析和語(yǔ)法分析,先看 SQL 語(yǔ)句要做什么,再檢查 SQL 語(yǔ)句語(yǔ)法是否正確。
優(yōu)化器: 優(yōu)化器對(duì)查詢進(jìn)行優(yōu)化,包括重寫查詢、決定表的讀寫順序以及選擇合適的索引等,生成執(zhí)行計(jì)劃。
執(zhí)行器: 首先執(zhí)行前會(huì)校驗(yàn)該用戶有沒有權(quán)限,如果沒有權(quán)限,就會(huì)返回錯(cuò)誤信息,如果有權(quán)限,就會(huì)根據(jù)執(zhí)行計(jì)劃去調(diào)用引擎的接口,返回結(jié)果。
分庫(kù)分表
當(dāng)單表的數(shù)據(jù)量達(dá)到1000W或100G以后,優(yōu)化索引、添加從庫(kù)等可能對(duì)數(shù)據(jù)庫(kù)性能提升效果不明顯,此時(shí)就要考慮對(duì)其進(jìn)行切分了。切分的目的就在于減少數(shù)據(jù)庫(kù)的負(fù)擔(dān),縮短查詢的時(shí)間。
數(shù)據(jù)切分可以分為兩種方式:垂直劃分和水平劃分。
垂直劃分
垂直劃分?jǐn)?shù)據(jù)庫(kù)是根據(jù)業(yè)務(wù)進(jìn)行劃分,例如購(gòu)物場(chǎng)景,可以將庫(kù)中涉及商品、訂單、用戶的表分別劃分出成一個(gè)庫(kù),通過(guò)降低單庫(kù)的大小來(lái)提高性能。同樣的,分表的情況就是將一個(gè)大表根據(jù)業(yè)務(wù)功能拆分成一個(gè)個(gè)子表,例如商品基本信息和商品描述,商品基本信息一般會(huì)展示在商品列表,商品描述在商品詳情頁(yè),可以將商品基本信息和商品描述拆分成兩張表。
優(yōu)點(diǎn):行記錄變小,數(shù)據(jù)頁(yè)可以存放更多記錄,在查詢時(shí)減少I/O次數(shù)。
缺點(diǎn):
主鍵出現(xiàn)冗余,需要管理冗余列;
會(huì)引起表連接JOIN操作,可以通過(guò)在業(yè)務(wù)服務(wù)器上進(jìn)行join來(lái)減少數(shù)據(jù)庫(kù)壓力;
依然存在單表數(shù)據(jù)量過(guò)大的問(wèn)題。
水平劃分
水平劃分是根據(jù)一定規(guī)則,例如時(shí)間或id序列值等進(jìn)行數(shù)據(jù)的拆分。比如根據(jù)年份來(lái)拆分不同的數(shù)據(jù)庫(kù)。每個(gè)數(shù)據(jù)庫(kù)結(jié)構(gòu)一致,但是數(shù)據(jù)得以拆分,從而提升性能。

優(yōu)點(diǎn):?jiǎn)螏?kù)(表)的數(shù)據(jù)量得以減少,提高性能;切分出的表結(jié)構(gòu)相同,程序改動(dòng)較少。
缺點(diǎn):
分片事務(wù)一致性難以解決
跨節(jié)點(diǎn)
join
性能差,邏輯復(fù)雜數(shù)據(jù)分片在擴(kuò)容時(shí)需要遷移
什么是分區(qū)表?
分區(qū)是把一張表的數(shù)據(jù)分成N多個(gè)區(qū)塊。分區(qū)表是一個(gè)獨(dú)立的邏輯表,但是底層由多個(gè)物理子表組成。
當(dāng)查詢條件的數(shù)據(jù)分布在某一個(gè)分區(qū)的時(shí)候,查詢引擎只會(huì)去某一個(gè)分區(qū)查詢,而不是遍歷整個(gè)表。在管理層面,如果需要?jiǎng)h除某一個(gè)分區(qū)的數(shù)據(jù),只需要?jiǎng)h除對(duì)應(yīng)的分區(qū)即可。
分區(qū)一般都是放在單機(jī)里的,用的比較多的是時(shí)間范圍分區(qū),方便歸檔。只不過(guò)分庫(kù)分表需要代碼實(shí)現(xiàn),分區(qū)則是mysql內(nèi)部實(shí)現(xiàn)。分庫(kù)分表和分區(qū)并不沖突,可以結(jié)合使用。
分區(qū)表類型
range分區(qū),按照范圍分區(qū)。比如按照時(shí)間范圍分區(qū)
CREATE TABLE test_range_partition( ? ? ? ?id INT auto_increment, ? ? ? ?createdate DATETIME, ? ? ? ?primary key (id,createdate) ? ?) ? ? PARTITION BY RANGE (TO_DAYS(createdate) ) ( ? ? ? PARTITION p201801 VALUES LESS THAN ( TO_DAYS('20180201') ), ? ? ? PARTITION p201802 VALUES LESS THAN ( TO_DAYS('20180301') ), ? ? ? PARTITION p201803 VALUES LESS THAN ( TO_DAYS('20180401') ), ? ? ? PARTITION p201804 VALUES LESS THAN ( TO_DAYS('20180501') ), ? ? ? PARTITION p201805 VALUES LESS THAN ( TO_DAYS('20180601') ), ? ? ? PARTITION p201806 VALUES LESS THAN ( TO_DAYS('20180701') ), ? ? ? PARTITION p201807 VALUES LESS THAN ( TO_DAYS('20180801') ), ? ? ? PARTITION p201808 VALUES LESS THAN ( TO_DAYS('20180901') ), ? ? ? PARTITION p201809 VALUES LESS THAN ( TO_DAYS('20181001') ), ? ? ? PARTITION p201810 VALUES LESS THAN ( TO_DAYS('20181101') ), ? ? ? PARTITION p201811 VALUES LESS THAN ( TO_DAYS('20181201') ), ? ? ? PARTITION p201812 VALUES LESS THAN ( TO_DAYS('20190101') ) ? ?);
在/var/lib/mysql/data/
可以找到對(duì)應(yīng)的數(shù)據(jù)文件,每個(gè)分區(qū)表都有一個(gè)使用#分隔命名的表文件:
? -rw-r----- 1 MySQL MySQL ? ?65 Mar 14 21:47 db.opt ? ?-rw-r----- 1 MySQL MySQL ?8598 Mar 14 21:50 test_range_partition.frm ? ?-rw-r----- 1 MySQL MySQL 98304 Mar 14 21:50 test_range_partition#P#p201801.ibd ? ?-rw-r----- 1 MySQL MySQL 98304 Mar 14 21:50 test_range_partition#P#p201802.ibd ? ?-rw-r----- 1 MySQL MySQL 98304 Mar 14 21:50 test_range_partition#P#p201803.ibd ...
list分區(qū)
list分區(qū)和range分區(qū)相似,主要區(qū)別在于list是枚舉值列表的集合,range是連續(xù)的區(qū)間值的集合。對(duì)于list分區(qū),分區(qū)字段必須是已知的,如果插入的字段不在分區(qū)時(shí)的枚舉值中,將無(wú)法插入。
create table test_list_partiotion ? ?( ? ? ? ?id int auto_increment, ? ? ? ?data_type tinyint, ? ? ? ?primary key(id,data_type) ? ?)partition by list(data_type) ? ?( ? ? ? ?partition p0 values in (0,1,2,3,4,5,6), ? ? ? ?partition p1 values in (7,8,9,10,11,12), ? ? ? ?partition p2 values in (13,14,15,16,17) ? ?);
hash分區(qū)
可以將數(shù)據(jù)均勻地分布到預(yù)先定義的分區(qū)中。
create table test_hash_partiotion ? ?( ? ? ? ?id int auto_increment, ? ? ? ?create_date datetime, ? ? ? ?primary key(id,create_date) ? ?)partition by hash(year(create_date)) partitions 10;
分區(qū)的問(wèn)題?
打開和鎖住所有底層表的成本可能很高。當(dāng)查詢?cè)L問(wèn)分區(qū)表時(shí),MySQL 需要打開并鎖住所有的底層表,這個(gè)操作在分區(qū)過(guò)濾之前發(fā)生,所以無(wú)法通過(guò)分區(qū)過(guò)濾來(lái)降低此開銷,會(huì)影響到查詢速度??梢酝ㄟ^(guò)批量操作來(lái)降低此類開銷,比如批量插入、
LOAD DATA INFILE
和一次刪除多行數(shù)據(jù)。維護(hù)分區(qū)的成本可能很高。例如重組分區(qū),會(huì)先創(chuàng)建一個(gè)臨時(shí)分區(qū),然后將數(shù)據(jù)復(fù)制到其中,最后再刪除原分區(qū)。
所有分區(qū)必須使用相同的存儲(chǔ)引擎。
查詢語(yǔ)句執(zhí)行流程?
查詢語(yǔ)句的執(zhí)行流程如下:權(quán)限校驗(yàn)、查詢緩存、分析器、優(yōu)化器、權(quán)限校驗(yàn)、執(zhí)行器、引擎。
舉個(gè)例子,查詢語(yǔ)句如下:
select * from user where id > 1 and name = '大彬';
首先檢查權(quán)限,沒有權(quán)限則返回錯(cuò)誤;
MySQL8.0以前會(huì)查詢緩存,緩存命中則直接返回,沒有則執(zhí)行下一步;
詞法分析和語(yǔ)法分析。提取表名、查詢條件,檢查語(yǔ)法是否有錯(cuò)誤;
兩種執(zhí)行方案,先查
id > 1
還是name = '大彬'
,優(yōu)化器根據(jù)自己的優(yōu)化算法選擇執(zhí)行效率最好的方案;校驗(yàn)權(quán)限,有權(quán)限就調(diào)用數(shù)據(jù)庫(kù)引擎接口,返回引擎的執(zhí)行結(jié)果。
更新語(yǔ)句執(zhí)行過(guò)程?
更新語(yǔ)句執(zhí)行流程如下:分析器、權(quán)限校驗(yàn)、執(zhí)行器、引擎、redo log
(prepare
狀態(tài))、binlog
、redo log
(commit
狀態(tài))
舉個(gè)例子,更新語(yǔ)句如下:
update user set name = '大彬' where id = 1;
先查詢到 id 為1的記錄,有緩存會(huì)使用緩存。
拿到查詢結(jié)果,將 name 更新為大彬,然后調(diào)用引擎接口,寫入更新數(shù)據(jù),innodb 引擎將數(shù)據(jù)保存在內(nèi)存中,同時(shí)記錄
redo log
,此時(shí)redo log
進(jìn)入prepare
狀態(tài)。執(zhí)行器收到通知后記錄
binlog
,然后調(diào)用引擎接口,提交redo log
為commit
狀態(tài)。更新完成。
為什么記錄完redo log
,不直接提交,而是先進(jìn)入prepare
狀態(tài)?
假設(shè)先寫redo log
直接提交,然后寫binlog
,寫完redo log
后,機(jī)器掛了,binlog
日志沒有被寫入,那么機(jī)器重啟后,這臺(tái)機(jī)器會(huì)通過(guò)redo log
恢復(fù)數(shù)據(jù),但是這個(gè)時(shí)候binlog
并沒有記錄該數(shù)據(jù),后續(xù)進(jìn)行機(jī)器備份的時(shí)候,就會(huì)丟失這一條數(shù)據(jù),同時(shí)主從同步也會(huì)丟失這一條數(shù)據(jù)。
exist和in的區(qū)別?
exists
用于對(duì)外表記錄做篩選。exists
會(huì)遍歷外表,將外查詢表的每一行,代入內(nèi)查詢進(jìn)行判斷。當(dāng)exists
里的條件語(yǔ)句能夠返回記錄行時(shí),條件就為真,返回外表當(dāng)前記錄。反之如果exists
里的條件語(yǔ)句不能返回記錄行,條件為假,則外表當(dāng)前記錄被丟棄。
select a.* from A awhere exists(select 1 from B b where a.id=b.id)
in
是先把后邊的語(yǔ)句查出來(lái)放到臨時(shí)表中,然后遍歷臨時(shí)表,將臨時(shí)表的每一行,代入外查詢?nèi)ゲ檎摇?/p>
select * from Awhere id in(select id from B)
子查詢的表比較大的時(shí)候,使用exists
可以有效減少總的循環(huán)次數(shù)來(lái)提升速度;當(dāng)外查詢的表比較大的時(shí)候,使用in
可以有效減少對(duì)外查詢表循環(huán)遍歷來(lái)提升速度。
MySQL中int(10)和char(10)的區(qū)別?
int(10)中的10表示的是顯示數(shù)據(jù)的長(zhǎng)度,而char(10)表示的是存儲(chǔ)數(shù)據(jù)的長(zhǎng)度。
truncate、delete與drop區(qū)別?
相同點(diǎn):
truncate
和不帶where
子句的delete
、以及drop
都會(huì)刪除表內(nèi)的數(shù)據(jù)。drop
、truncate
都是DDL
語(yǔ)句(數(shù)據(jù)定義語(yǔ)言),執(zhí)行后會(huì)自動(dòng)提交。
不同點(diǎn):
truncate 和 delete 只刪除數(shù)據(jù)不刪除表的結(jié)構(gòu);drop 語(yǔ)句將刪除表的結(jié)構(gòu)被依賴的約束、觸發(fā)器、索引;
一般來(lái)說(shuō),執(zhí)行速度: drop > truncate > delete。
having和where區(qū)別?
二者作用的對(duì)象不同,
where
子句作用于表和視圖,having
作用于組。where
在數(shù)據(jù)分組前進(jìn)行過(guò)濾,having
在數(shù)據(jù)分組后進(jìn)行過(guò)濾。
為什么要做主從同步?
讀寫分離,使數(shù)據(jù)庫(kù)能支撐更大的并發(fā)。
在主服務(wù)器上生成實(shí)時(shí)數(shù)據(jù),而在從服務(wù)器上分析這些數(shù)據(jù),從而提高主服務(wù)器的性能。
數(shù)據(jù)備份,保證數(shù)據(jù)的安全。
什么是MySQL主從同步?
主從同步使得數(shù)據(jù)可以從一個(gè)數(shù)據(jù)庫(kù)服務(wù)器復(fù)制到其他服務(wù)器上,在復(fù)制數(shù)據(jù)時(shí),一個(gè)服務(wù)器充當(dāng)主服務(wù)器(master
),其余的服務(wù)器充當(dāng)從服務(wù)器(slave
)。
因?yàn)閺?fù)制是異步進(jìn)行的,所以從服務(wù)器不需要一直連接著主服務(wù)器,從服務(wù)器甚至可以通過(guò)撥號(hào)斷斷續(xù)續(xù)地連接主服務(wù)器。通過(guò)配置文件,可以指定復(fù)制所有的數(shù)據(jù)庫(kù),某個(gè)數(shù)據(jù)庫(kù),甚至是某個(gè)數(shù)據(jù)庫(kù)上的某個(gè)表。
樂觀鎖和悲觀鎖是什么?
數(shù)據(jù)庫(kù)中的并發(fā)控制是確保在多個(gè)事務(wù)同時(shí)存取數(shù)據(jù)庫(kù)中同一數(shù)據(jù)時(shí)不破壞事務(wù)的隔離性和統(tǒng)一性以及數(shù)據(jù)庫(kù)的統(tǒng)一性。樂觀鎖和悲觀鎖是并發(fā)控制主要采用的技術(shù)手段。
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,會(huì)對(duì)操作的數(shù)據(jù)進(jìn)行加鎖,直到提交事務(wù),才會(huì)釋放鎖,其他事務(wù)才能進(jìn)行修改。實(shí)現(xiàn)方式:使用數(shù)據(jù)庫(kù)中的鎖機(jī)制。
樂觀鎖:假設(shè)不會(huì)發(fā)生并發(fā)沖突,只在提交操作時(shí)檢查是否數(shù)據(jù)是否被修改過(guò)。給表增加
version
字段,在修改提交之前檢查version
與原來(lái)取到的version
值是否相等,若相等,表示數(shù)據(jù)沒有被修改,可以更新,否則,數(shù)據(jù)為臟數(shù)據(jù),不能更新。實(shí)現(xiàn)方式:樂觀鎖一般使用版本號(hào)機(jī)制或CAS
算法實(shí)現(xiàn)。
用過(guò)processlist嗎?
show processlist
或 show full processlist
可以查看當(dāng)前 MySQL 是否有壓力,正在運(yùn)行的SQL
,有沒有慢SQL
正在執(zhí)行。返回參數(shù)如下:
id:線程ID,可以用
kill id
殺死某個(gè)線程db:數(shù)據(jù)庫(kù)名稱
user:數(shù)據(jù)庫(kù)用戶
host:數(shù)據(jù)庫(kù)實(shí)例的IP
command:當(dāng)前執(zhí)行的命令,比如
Sleep
,Query
,Connect
等time:消耗時(shí)間,單位秒
state:執(zhí)行狀態(tài),主要有以下狀態(tài):
Sleep,線程正在等待客戶端發(fā)送新的請(qǐng)求
Locked,線程正在等待鎖
Sending data,正在處理
SELECT
查詢的記錄,同時(shí)把結(jié)果發(fā)送給客戶端Kill,正在執(zhí)行
kill
語(yǔ)句,殺死指定線程Connect,一個(gè)從節(jié)點(diǎn)連上了主節(jié)點(diǎn)
Quit,線程正在退出
Sorting for group,正在為
GROUP BY
做排序Sorting for order,正在為
ORDER BY
做排序info:正在執(zhí)行的
SQL
語(yǔ)句
MySQL查詢 limit 1000,10 和limit 10 速度一樣快嗎?
兩種查詢方式。對(duì)應(yīng) limit offset, size
和 limit size
兩種方式。
而其實(shí) limit size
,相當(dāng)于 ?limit 0, size
。也就是從0開始取size條數(shù)據(jù)。
也就是說(shuō),兩種方式的區(qū)別在于offset是否為0。
先來(lái)看下limit sql的內(nèi)部執(zhí)行邏輯。
MySQL內(nèi)部分為server層和存儲(chǔ)引擎層。一般情況下存儲(chǔ)引擎都用innodb。
server層有很多模塊,其中需要關(guān)注的是執(zhí)行器是用于跟存儲(chǔ)引擎打交道的組件。
執(zhí)行器可以通過(guò)調(diào)用存儲(chǔ)引擎提供的接口,將一行行數(shù)據(jù)取出,當(dāng)這些數(shù)據(jù)完全符合要求(比如滿足其他where條件),則會(huì)放到結(jié)果集中,最后返回給調(diào)用mysql的客戶端。
以主鍵索引的limit執(zhí)行過(guò)程為例:
執(zhí)行select * from xxx order by id limit 0, 10;
,select后面帶的是星號(hào),也就是要求獲得行數(shù)據(jù)的所有字段信息。
server層會(huì)調(diào)用innodb的接口,在innodb里的主鍵索引中獲取到第0到10條完整行數(shù)據(jù),依次返回給server層,并放到server層的結(jié)果集中,返回給客戶端。
把offset搞大點(diǎn),比如執(zhí)行的是:select * from xxx order by id limit 500000, 10;
server層會(huì)調(diào)用innodb的接口,由于這次的offset=500000,會(huì)在innodb里的主鍵索引中獲取到第0到(500000 + 10)條完整行數(shù)據(jù),返回給server層之后根據(jù)offset的值挨個(gè)拋棄,最后只留下最后面的size條,也就是10條數(shù)據(jù),放到server層的結(jié)果集中,返回給客戶端。
可以看出,當(dāng)offset非0時(shí),server層會(huì)從引擎層獲取到很多無(wú)用的數(shù)據(jù),而獲取的這些無(wú)用數(shù)據(jù)都是要耗時(shí)的。
因此,mysql查詢中 limit 1000,10 會(huì)比 limit 10 更慢。原因是 limit 1000,10 會(huì)取出1000+10條數(shù)據(jù),并拋棄前1000條,這部分耗時(shí)更大。
高度為3的B+樹,可以存放多少數(shù)據(jù)?
InnoDB存儲(chǔ)引擎有自己的最小儲(chǔ)存單元——頁(yè)(Page)。
查詢InnoDB頁(yè)大小的命令如下:
mysql> show global status like 'innodb_page_size';
+------------------+-------+
| Variable_name ? ?| Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
可以看出 innodb 默認(rèn)的一頁(yè)大小為 16384B = 16384/1024 = 16kb。
在MySQL中,B+樹一個(gè)節(jié)點(diǎn)的大小設(shè)為一頁(yè)或頁(yè)的倍數(shù)最為合適。因?yàn)槿绻粋€(gè)節(jié)點(diǎn)的大小 < 1頁(yè),那么讀取這個(gè)節(jié)點(diǎn)的時(shí)候其實(shí)讀取的還是一頁(yè),這樣就造成了資源的浪費(fèi)。
B+樹中非葉子節(jié)點(diǎn)存的是key + 指針;葉子節(jié)點(diǎn)存的是數(shù)據(jù)行。
對(duì)于葉子節(jié)點(diǎn),如果一行數(shù)據(jù)大小為1k,那么一頁(yè)就能存16條數(shù)據(jù)。
對(duì)于非葉子節(jié)點(diǎn),如果key使用的是bigint,則為8字節(jié),指針在MySQL中為6字節(jié),一共是14字節(jié),則16k能存放 16 * 1024 / 14 = 1170 個(gè)索引指針。
于是可以算出,對(duì)于一顆高度為2的B+樹,根節(jié)點(diǎn)存儲(chǔ)索引指針節(jié)點(diǎn),那么它有1170個(gè)葉子節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù),每個(gè)葉子節(jié)點(diǎn)可以存儲(chǔ)16條數(shù)據(jù),一共 1170 x 16 = 18720 條數(shù)據(jù)。而對(duì)于高度為3的B+樹,就可以存放 1170 x 1170 x 16 = 21902400 條數(shù)據(jù)(兩千多萬(wàn)條數(shù)據(jù)),也就是對(duì)于兩千多萬(wàn)條的數(shù)據(jù),我們只需要高度為3的B+樹就可以完成,通過(guò)主鍵查詢只需要3次IO操作就能查到對(duì)應(yīng)數(shù)據(jù)。
所以在 InnoDB 中B+樹高度一般為3層時(shí),就能滿足千萬(wàn)級(jí)的數(shù)據(jù)存儲(chǔ)。
深分頁(yè)怎么優(yōu)化?
還是以上面的SQL為空:select * from xxx order by id limit 500000, 10;
方法一:
從上面的分析可以看出,當(dāng)offset非常大時(shí),server層會(huì)從引擎層獲取到很多無(wú)用的數(shù)據(jù),而當(dāng)select后面是*號(hào)時(shí),就需要拷貝完整的行信息,拷貝完整數(shù)據(jù)相比只拷貝行數(shù)據(jù)里的其中一兩個(gè)列字段更耗費(fèi)時(shí)間。
因?yàn)榍懊娴膐ffset條數(shù)據(jù)最后都是不要的,沒有必要拷貝完整字段,所以可以將sql語(yǔ)句修改成:
select * from xxx ?where id >=(select id from xxx order by id limit 500000, 1) order by id limit 10;
先執(zhí)行子查詢 select id from xxx by id limit 500000, 1
, 這個(gè)操作,其實(shí)也是將在innodb中的主鍵索引中獲取到500000+1
條數(shù)據(jù),然后server層會(huì)拋棄前500000條,只保留最后一條數(shù)據(jù)的id。
但不同的地方在于,在返回server層的過(guò)程中,只會(huì)拷貝數(shù)據(jù)行內(nèi)的id這一列,而不會(huì)拷貝數(shù)據(jù)行的所有列,當(dāng)數(shù)據(jù)量較大時(shí),這部分的耗時(shí)還是比較明顯的。
在拿到了上面的id之后,假設(shè)這個(gè)id正好等于500000,那sql就變成了
select * from xxx ?where id >=500000 order by id limit 10;
這樣innodb再走一次主鍵索引,通過(guò)B+樹快速定位到id=500000的行數(shù)據(jù),時(shí)間復(fù)雜度是lg(n),然后向后取10條數(shù)據(jù)。
方法二:
將所有的數(shù)據(jù)根據(jù)id主鍵進(jìn)行排序,然后分批次取,將當(dāng)前批次的最大id作為下次篩選的條件進(jìn)行查詢。
select * from xxx where id > start_id order by id limit 10;
通過(guò)主鍵索引,每次定位到start_id的位置,然后往后遍歷10個(gè)數(shù)據(jù),這樣不管數(shù)據(jù)多大,查詢性能都較為穩(wěn)定。
大表查詢慢怎么優(yōu)化?
某個(gè)表有近千萬(wàn)數(shù)據(jù),查詢比較慢,如何優(yōu)化?
當(dāng)MySQL單表記錄數(shù)過(guò)大時(shí),數(shù)據(jù)庫(kù)的性能會(huì)明顯下降,一些常見的優(yōu)化措施如下:
合理建立索引。在合適的字段上建立索引,例如在WHERE和ORDER BY命令上涉及的列建立索引,可根據(jù)EXPLAIN來(lái)查看是否用了索引還是全表掃描
索引優(yōu)化,SQL優(yōu)化。最左匹配原則等,參考:https://topjavaer.cn/database/mysql.html#%E4%BB%80%E4%B9%88%E6%98%AF%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95
建立分區(qū)。對(duì)關(guān)鍵字段建立水平分區(qū),比如時(shí)間字段,若查詢條件往往通過(guò)時(shí)間范圍來(lái)進(jìn)行查詢,能提升不少性能
利用緩存。利用Redis等緩存熱點(diǎn)數(shù)據(jù),提高查詢效率
限定數(shù)據(jù)的范圍。比如:用戶在查詢歷史信息的時(shí)候,可以控制在一個(gè)月的時(shí)間范圍內(nèi)
讀寫分離。經(jīng)典的數(shù)據(jù)庫(kù)拆分方案,主庫(kù)負(fù)責(zé)寫,從庫(kù)負(fù)責(zé)讀
通過(guò)分庫(kù)分表的方式進(jìn)行優(yōu)化,主要有垂直拆分和水平拆分
合理建立索引。在合適的字段上建立索引,例如在WHERE和ORDERBY命令上涉及的列建立索引
數(shù)據(jù)異構(gòu)到es
冷熱數(shù)據(jù)分離。幾個(gè)月之前不常用的數(shù)據(jù)放到冷庫(kù)中,最新的數(shù)據(jù)比較新的數(shù)據(jù)放到熱庫(kù)中
升級(jí)數(shù)據(jù)庫(kù)類型,換一種能兼容MySQL的數(shù)據(jù)庫(kù)(OceanBase、tidb)
MySQL單表多大進(jìn)行分庫(kù)分表?
目前主流的有兩種說(shuō)法:
MySQL 單表數(shù)據(jù)量大于 2000 萬(wàn)行,性能會(huì)明顯下降,考慮進(jìn)行分庫(kù)分表。
阿里巴巴《Java 開發(fā)手冊(cè)》提出單表行數(shù)超過(guò) 500 萬(wàn)行或者單表容量超過(guò) 2GB,才推薦進(jìn)行分庫(kù)分表。
事實(shí)上,這個(gè)數(shù)值和實(shí)際記錄的條數(shù)無(wú)關(guān),而與 MySQL 的配置以及機(jī)器的硬件有關(guān)。因?yàn)镸ySQL為了提高性能,會(huì)將表的索引裝載到內(nèi)存中。在InnoDB buffer size 足夠的情況下,其能完成全加載進(jìn)內(nèi)存,查詢不會(huì)有問(wèn)題。但是,當(dāng)單表數(shù)據(jù)庫(kù)到達(dá)某個(gè)量級(jí)的上限時(shí),導(dǎo)致內(nèi)存無(wú)法存儲(chǔ)其索引,使得之后的 SQL 查詢會(huì)產(chǎn)生磁盤 IO,從而導(dǎo)致性能下降。當(dāng)然,這個(gè)還有具體的表結(jié)構(gòu)的設(shè)計(jì)有關(guān),最終導(dǎo)致的問(wèn)題都是內(nèi)存限制。
因此,對(duì)于分庫(kù)分表,需要結(jié)合實(shí)際需求,不宜過(guò)度設(shè)計(jì),在項(xiàng)目一開始不采用分庫(kù)與分表設(shè)計(jì),而是隨著業(yè)務(wù)的增長(zhǎng),在無(wú)法繼續(xù)優(yōu)化的情況下,再考慮分庫(kù)與分表提高系統(tǒng)的性能。對(duì)此,阿里巴巴《Java 開發(fā)手冊(cè)》補(bǔ)充到:如果預(yù)計(jì)三年后的數(shù)據(jù)量根本達(dá)不到這個(gè)級(jí)別,請(qǐng)不要在創(chuàng)建表時(shí)就分庫(kù)分表。
至于MySQL單表多大進(jìn)行分庫(kù)分表,應(yīng)當(dāng)根據(jù)機(jī)器資源進(jìn)行評(píng)估。
說(shuō)說(shuō)count(1)、count(*)和count(字段名)的區(qū)別
嗯,先說(shuō)說(shuō)count(1) and count(字段名)的區(qū)別。
兩者的主要區(qū)別是
count(1) 會(huì)統(tǒng)計(jì)表中的所有的記錄數(shù),包含字段為null 的記錄。
count(字段名) 會(huì)統(tǒng)計(jì)該字段在表中出現(xiàn)的次數(shù),忽略字段為null 的情況。即不統(tǒng)計(jì)字段為null 的記錄。
接下來(lái)看看三者之間的區(qū)別。
執(zhí)行效果上:
count(*)包括了所有的列,相當(dāng)于行數(shù),在統(tǒng)計(jì)結(jié)果的時(shí)候,不會(huì)忽略列值為NULL
count(1)包括了忽略所有列,用1代表代碼行,在統(tǒng)計(jì)結(jié)果的時(shí)候,不會(huì)忽略列值為NULL
count(字段名)只包括列名那一列,在統(tǒng)計(jì)結(jié)果的時(shí)候,會(huì)忽略列值為空(這里的空不是只空字符串或者0,而是表示null)的計(jì)數(shù),即某個(gè)字段值為NULL時(shí),不統(tǒng)計(jì)。
執(zhí)行效率上:
列名為主鍵,count(字段名)會(huì)比count(1)快
列名不為主鍵,count(1)會(huì)比count(列名)快
如果表多個(gè)列并且沒有主鍵,則 count(1) 的執(zhí)行效率優(yōu)于 count(*)
如果有主鍵,則 select count(主鍵)的執(zhí)行效率是最優(yōu)的
如果表只有一個(gè)字段,則 select count(*)最優(yōu)。
MySQL中DATETIME 和 TIMESTAMP有什么區(qū)別?
嗯,TIMESTAMP
和DATETIME
都可以用來(lái)存儲(chǔ)時(shí)間,它們主要有以下區(qū)別:
1.表示范圍
DATETIME:1000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999
TIMESTAMP:'1970-01-01 00:00:01.000000' UTC 到 '2038-01-09 03:14:07.999999' UTC
TIMESTAMP
支持的時(shí)間范圍比DATATIME
要小,容易出現(xiàn)超出的情況。
2.空間占用
TIMESTAMP :占 4 個(gè)字節(jié)
DATETIME:在 MySQL 5.6.4 之前,占 8 個(gè)字節(jié) ,之后版本,占 5 個(gè)字節(jié)
3.存入時(shí)間是否會(huì)自動(dòng)轉(zhuǎn)換
TIMESTAMP
類型在默認(rèn)情況下,insert、update 數(shù)據(jù)時(shí),TIMESTAMP
列會(huì)自動(dòng)以當(dāng)前時(shí)間(CURRENT_TIMESTAMP
)填充/更新。DATETIME
則不會(huì)做任何轉(zhuǎn)換,也不會(huì)檢測(cè)時(shí)區(qū),你給什么數(shù)據(jù),它存什么數(shù)據(jù)。
4.TIMESTAMP
比較受時(shí)區(qū)timezone的影響以及MYSQL版本和服務(wù)器的SQL MODE的影響。因?yàn)?code>TIMESTAMP存的是時(shí)間戳,在不同的時(shí)區(qū)得出的時(shí)間不一致。
5.如果存進(jìn)NULL,兩者實(shí)際存儲(chǔ)的值不同。
TIMESTAMP:會(huì)自動(dòng)存儲(chǔ)當(dāng)前時(shí)間 now() 。
DATETIME:不會(huì)自動(dòng)存儲(chǔ)當(dāng)前時(shí)間,會(huì)直接存入 NULL 值。
說(shuō)說(shuō)為什么不建議用外鍵?
外鍵是一種約束,這個(gè)約束的存在,會(huì)保證表間數(shù)據(jù)的關(guān)系始終完整。外鍵的存在,并非全然沒有優(yōu)點(diǎn)。
外鍵可以保證數(shù)據(jù)的完整性和一致性,級(jí)聯(lián)操作方便。而且使用外鍵可以將數(shù)據(jù)完整性判斷托付給了數(shù)據(jù)庫(kù)完成,減少了程序的代碼量。
雖然外鍵能夠保證數(shù)據(jù)的完整性,但是會(huì)給系統(tǒng)帶來(lái)很多缺陷。
1、并發(fā)問(wèn)題。在使用外鍵的情況下,每次修改數(shù)據(jù)都需要去另外一個(gè)表檢查數(shù)據(jù),需要獲取額外的鎖。若是在高并發(fā)大流量事務(wù)場(chǎng)景,使用外鍵更容易造成死鎖。
2、擴(kuò)展性問(wèn)題。比如從MySQL
遷移到Oracle
,外鍵依賴于數(shù)據(jù)庫(kù)本身的特性,做遷移可能不方便。
3、不利于分庫(kù)分表。在水平拆分和分庫(kù)的情況下,外鍵是無(wú)法生效的。將數(shù)據(jù)間關(guān)系的維護(hù),放入應(yīng)用程序中,為將來(lái)的分庫(kù)分表省去很多的麻煩。
使用自增主鍵有什么好處?
自增主鍵可以讓主鍵索引盡量地保持遞增順序插入,避免了頁(yè)分裂,因此索引更緊湊,在查詢的時(shí)候,效率也就更高。
InnoDB的自增值為什么不能回收利用?
主要為了提升插入數(shù)據(jù)的效率和并行度。
假設(shè)有兩個(gè)并行執(zhí)行的事務(wù),在申請(qǐng)自增值的時(shí)候,為了避免兩個(gè)事務(wù)申請(qǐng)到相同的自增 id,肯定要加鎖,然后順序申請(qǐng)。
假設(shè)事務(wù) A 申請(qǐng)到了 id=2, 事務(wù) B 申請(qǐng)到 id=3,那么這時(shí)候表 t 的自增值是 4,之后繼續(xù)執(zhí)行。
事務(wù) B 正確提交了,但事務(wù) A 出現(xiàn)了唯一鍵沖突。
如果允許事務(wù) A 把自增 id 回退,也就是把表 t 的當(dāng)前自增值改回 2,那么就會(huì)出現(xiàn)這樣的情況:表里面已經(jīng)有 id=3 的行,而當(dāng)前的自增 id 值是 2。
接下來(lái),繼續(xù)執(zhí)行的其他事務(wù)就會(huì)申請(qǐng)到 id=2,然后再申請(qǐng)到 id=3。這時(shí),就會(huì)出現(xiàn)插入語(yǔ)句報(bào)錯(cuò)“主鍵沖突”。
而為了解決這個(gè)主鍵沖突,有兩種方法:
每次申請(qǐng) id 之前,先判斷表里面是否已經(jīng)存在這個(gè) id。如果存在,就跳過(guò)這個(gè) id。但是,這個(gè)方法的成本很高。因?yàn)?,本?lái)申請(qǐng) id 是一個(gè)很快的操作,現(xiàn)在還要再去主鍵索引樹上判斷 id 是否存在。
把自增 id 的鎖范圍擴(kuò)大,必須等到一個(gè)事務(wù)執(zhí)行完成并提交,下一個(gè)事務(wù)才能再申請(qǐng)自增 id。這個(gè)方法的問(wèn)題,就是鎖的粒度太大,系統(tǒng)并發(fā)能力大大下降。
可見,這兩個(gè)方法都會(huì)導(dǎo)致性能問(wèn)題。
因此,InnoDB 放棄了“允許自增 id 回退”這個(gè)設(shè)計(jì),語(yǔ)句執(zhí)行失敗也不回退自增 id。
自增主鍵保存在什么地方?
不同的引擎對(duì)于自增值的保存策略不同:
MyISAM引擎的自增值保存在數(shù)據(jù)文件中。
在MySQL8.0以前,InnoDB引擎的自增值是存在內(nèi)存中。MySQL重啟之后內(nèi)存中的這個(gè)值就丟失了,每次重啟后第一次打開表的時(shí)候,會(huì)找自增值的最大值max(id),然后將最大值加1作為這個(gè)表的自增值;MySQL8.0版本會(huì)將自增值的變更記錄在redo log中,重啟時(shí)依靠redo log恢復(fù)。
自增主鍵一定是連續(xù)的嗎?
不一定,有幾種情況會(huì)導(dǎo)致自增主鍵不連續(xù)。
1、唯一鍵沖突導(dǎo)致自增主鍵不連續(xù)。當(dāng)我們向一個(gè)自增主鍵的InnoDB表中插入數(shù)據(jù)的時(shí)候,如果違反表中定義的唯一索引的唯一約束,會(huì)導(dǎo)致插入數(shù)據(jù)失敗。此時(shí)表的自增主鍵的鍵值是會(huì)向后加1滾動(dòng)的。下次再次插入數(shù)據(jù)的時(shí)候,就不能再使用上次因插入數(shù)據(jù)失敗而滾動(dòng)生成的鍵值了,必須使用新滾動(dòng)生成的鍵值。
2、事務(wù)回滾導(dǎo)致自增主鍵不連續(xù)。當(dāng)我們向一個(gè)自增主鍵的InnoDB表中插入數(shù)據(jù)的時(shí)候,如果顯式開啟了事務(wù),然后因?yàn)槟撤N原因最后回滾了事務(wù),此時(shí)表的自增值也會(huì)發(fā)生滾動(dòng),而接下里新插入的數(shù)據(jù),也將不能使用滾動(dòng)過(guò)的自增值,而是需要重新申請(qǐng)一個(gè)新的自增值。
3、批量插入導(dǎo)致自增值不連續(xù)。MySQL有一個(gè)批量申請(qǐng)自增id的策略:
語(yǔ)句執(zhí)行過(guò)程中,第一次申請(qǐng)自增id,分配1個(gè)自增id
1個(gè)用完以后,第二次申請(qǐng),會(huì)分配2個(gè)自增id
2個(gè)用完以后,第三次申請(qǐng),會(huì)分配4個(gè)自增id
依次類推,每次申請(qǐng)都是上一次的兩倍(最后一次申請(qǐng)不一定全部使用)
如果下一個(gè)事務(wù)再次插入數(shù)據(jù)的時(shí)候,則會(huì)基于上一個(gè)事務(wù)申請(qǐng)后的自增值基礎(chǔ)上再申請(qǐng)。此時(shí)就出現(xiàn)自增值不連續(xù)的情況出現(xiàn)。
4、自增步長(zhǎng)不是1,也會(huì)導(dǎo)致自增主鍵不連續(xù)。
MySQL數(shù)據(jù)如何同步到Redis緩存?
參考:https://cloud.tencent.com/developer/article/1805755
有兩種方案:
1、通過(guò)MySQL自動(dòng)同步刷新Redis,MySQL觸發(fā)器+UDF函數(shù)實(shí)現(xiàn)。
過(guò)程大致如下:
在MySQL中對(duì)要操作的數(shù)據(jù)設(shè)置觸發(fā)器Trigger,監(jiān)聽操作
客戶端向MySQL中寫入數(shù)據(jù)時(shí),觸發(fā)器會(huì)被觸發(fā),觸發(fā)之后調(diào)用MySQL的UDF函數(shù)
UDF函數(shù)可以把數(shù)據(jù)寫入到Redis中,從而達(dá)到同步的效果
2、解析MySQL的binlog,實(shí)現(xiàn)將數(shù)據(jù)庫(kù)中的數(shù)據(jù)同步到Redis??梢酝ㄟ^(guò)canal實(shí)現(xiàn)。canal是阿里巴巴旗下的一款開源項(xiàng)目,基于數(shù)據(jù)庫(kù)增量日志解析,提供增量數(shù)據(jù)訂閱&消費(fèi)。
canal的原理如下:
canal模擬mysql slave的交互協(xié)議,偽裝自己為mysql slave,向mysql master發(fā)送dump協(xié)議
mysql master收到dump請(qǐng)求,開始推送binary log給canal
canal解析binary log對(duì)象(原始為byte流),將數(shù)據(jù)同步寫入Redis。
為什么阿里Java手冊(cè)禁止使用存儲(chǔ)過(guò)程?
先看看什么是存儲(chǔ)過(guò)程。
存儲(chǔ)過(guò)程是在大型數(shù)據(jù)庫(kù)系統(tǒng)中,一組為了完成特定功能的SQL 語(yǔ)句集,它存儲(chǔ)在數(shù)據(jù)庫(kù)中,一次編譯后永久有效,用戶通過(guò)指定存儲(chǔ)過(guò)程的名字并給出參數(shù)(如果該存儲(chǔ)過(guò)程帶有參數(shù))來(lái)執(zhí)行它。
存儲(chǔ)過(guò)程主要有以下幾個(gè)缺點(diǎn)。
存儲(chǔ)過(guò)程難以調(diào)試。存儲(chǔ)過(guò)程的開發(fā)一直缺少有效的 IDE 環(huán)境。SQL 本身經(jīng)常很長(zhǎng),調(diào)試式要把句子拆開分別獨(dú)立執(zhí)行,非常麻煩。
移植性差。存儲(chǔ)過(guò)程的移植困難,一般業(yè)務(wù)系統(tǒng)總會(huì)不可避免地用到數(shù)據(jù)庫(kù)獨(dú)有的特性和語(yǔ)法,更換數(shù)據(jù)庫(kù)時(shí)這部分代碼就需要重寫,成本較高。
管理困難。存儲(chǔ)過(guò)程的目錄是扁平的,而不是文件系統(tǒng)那樣的樹形結(jié)構(gòu),腳本少的時(shí)候還好辦,一旦多起來(lái),目錄就會(huì)陷入混亂。
存儲(chǔ)過(guò)程是只優(yōu)化一次,有的時(shí)候隨著數(shù)據(jù)量的增加或者數(shù)據(jù)結(jié)構(gòu)的變化,原來(lái)存儲(chǔ)過(guò)程選擇的執(zhí)行計(jì)劃也許并不是最優(yōu)的了,所以這個(gè)時(shí)候需要手動(dòng)干預(yù)或者重新編譯了。
最后給大家分享一個(gè)Github倉(cāng)庫(kù),上面有大彬整理的300多本經(jīng)典的計(jì)算機(jī)書籍PDF,包括C語(yǔ)言、C++、Java、Python、前端、數(shù)據(jù)庫(kù)、操作系統(tǒng)、計(jì)算機(jī)網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)和算法、機(jī)器學(xué)習(xí)、編程人生等,可以star一下,下次找書直接在上面搜索,倉(cāng)庫(kù)持續(xù)更新中~


Github地址: