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

如果collation = utf8mb4_general_ci
,是指使用utf8mb4字符集的前提下,挨個字符進行比較(general
),并且不區(qū)分大小寫(_ci,case insensitice
)。
這種情況下,"debug"和"Debug"是同一個單詞。
如果改成collation=utf8mb4_bin
,就是指挨個比較二進制位大小。
于是"debug"和"Debug"就不是同一個單詞。
那utf8mb4對比utf8有什么劣勢嗎?
我們知道數(shù)據(jù)庫表里,字段類型如果是char(2)
的話,里面的2
是指字符個數(shù),也就是說不管這張表用的是什么編碼的字符集,都能放上2個字符。
而char又是固定長度,為了能放下2個utf8mb4的字符,char會默認保留2*4(maxlen=4)= 8
個字節(jié)的空間。
如果是utf8mb3,則會默認保留 2 * 3 (maxlen=3) = 6
個字節(jié)的空間。也就是說,在這種情況下,utf8mb4會比utf8mb3多使用一些空間。
索引
什么是索引?
索引是存儲引擎用于提高數(shù)據(jù)庫表的訪問速度的一種數(shù)據(jù)結(jié)構(gòu)。它可以比作一本字典的目錄,可以幫你快速找到對應(yīng)的記錄。
索引一般存儲在磁盤的文件中,它是占用物理空間的。
索引的優(yōu)缺點?
優(yōu)點:
加快數(shù)據(jù)查找的速度
為用來排序或者是分組的字段添加索引,可以加快分組和排序的速度
加快表與表之間的連接
缺點:
建立索引需要占用物理空間
會降低表的增刪改的效率,因為每次對表記錄進行增刪改,需要進行動態(tài)維護索引,導(dǎo)致增刪改時間變長
索引的作用?
數(shù)據(jù)是存儲在磁盤上的,查詢數(shù)據(jù)時,如果沒有索引,會加載所有的數(shù)據(jù)到內(nèi)存,依次進行檢索,讀取磁盤次數(shù)較多。有了索引,就不需要加載所有數(shù)據(jù),因為B+樹的高度一般在2-4層,最多只需要讀取2-4次磁盤,查詢速度大大提升。
什么情況下需要建索引?
經(jīng)常用于查詢的字段
經(jīng)常用于連接的字段建立索引,可以加快連接的速度
經(jīng)常需要排序的字段建立索引,因為索引已經(jīng)排好序,可以加快排序查詢速度
什么情況下不建索引?
where
條件中用不到的字段不適合建立索引表記錄較少。比如只有幾百條數(shù)據(jù),沒必要加索引。
需要經(jīng)常增刪改。需要評估是否適合加索引
參與列計算的列不適合建索引
區(qū)分度不高的字段不適合建立索引,如性別,只有男/女/未知三個值。加了索引,查詢效率也不會提高。
索引的數(shù)據(jù)結(jié)構(gòu)
索引的數(shù)據(jù)結(jié)構(gòu)主要有B+樹和哈希表,對應(yīng)的索引分別為B+樹索引和哈希索引。InnoDB引擎的索引類型有B+樹索引和哈希索引,默認的索引類型為B+樹索引
B+樹索引
B+ 樹是基于B 樹和葉子節(jié)點順序訪問指針進行實現(xiàn),它具有B樹的平衡性,并且通過順序訪問指針來提高區(qū)間查詢的性能。
在 B+ 樹中,節(jié)點中的 key 從左到右遞增排列,如果某個指針的左右相鄰 key 分別是 keyi 和 keyi+1,則該指針指向節(jié)點的所有 key 大于等于 keyi 且小于等于 keyi+1。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ydUgFZmO-1677315913406)(http://img.topjavaer.cn/img/B+樹索引0.png)]
進行查找操作時,首先在根節(jié)點進行二分查找,找到key
所在的指針,然后遞歸地在指針所指向的節(jié)點進行查找。直到查找到葉子節(jié)點,然后在葉子節(jié)點上進行二分查找,找出key
所對應(yīng)的數(shù)據(jù)項。
MySQL 數(shù)據(jù)庫使用最多的索引類型是BTREE
索引,底層基于B+樹數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。
哈希索引
哈希索引是基于哈希表實現(xiàn)的,對于每一行數(shù)據(jù),存儲引擎會對索引列進行哈希計算得到哈希碼,并且哈希算法要盡量保證不同的列值計算出的哈希碼值是不同的,將哈希碼的值作為哈希表的key值,將指向數(shù)據(jù)行的指針作為哈希表的value值。這樣查找一個數(shù)據(jù)的時間復(fù)雜度就是O(1),一般多用于精確查找。
Hash索引和B+樹索引的區(qū)別?
哈希索引不支持排序,因為哈希表是無序的。
哈希索引不支持范圍查找。
哈希索引不支持模糊查詢及多列索引的最左前綴匹配。
因為哈希表中會存在哈希沖突,所以哈希索引的性能是不穩(wěn)定的,而B+樹索引的性能是相對穩(wěn)定的,每次查詢都是從根節(jié)點到葉子節(jié)點。
為什么B+樹比B樹更適合實現(xiàn)數(shù)據(jù)庫索引?
由于B+樹的數(shù)據(jù)都存儲在葉子結(jié)點中,葉子結(jié)點均為索引,方便掃庫,只需要掃一遍葉子結(jié)點即可,但是B樹因為其分支結(jié)點同樣存儲著數(shù)據(jù),我們要找到具體的數(shù)據(jù),需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區(qū)間查詢的情況,而在數(shù)據(jù)庫中基于范圍的查詢是非常頻繁的,所以通常B+樹用于數(shù)據(jù)庫索引。
B+樹的節(jié)點只存儲索引key值,具體信息的地址存在于葉子節(jié)點的地址中。這就使以頁為單位的索引中可以存放更多的節(jié)點。減少更多的I/O支出。
B+樹的查詢效率更加穩(wěn)定,任何關(guān)鍵字的查找必須走一條從根結(jié)點到葉子結(jié)點的路。所有關(guān)鍵字查詢的路徑長度相同,導(dǎo)致每一個數(shù)據(jù)的查詢效率相當。
索引有什么分類?
1、主鍵索引:名為primary的唯一非空索引,不允許有空值。
2、唯一索引:索引列中的值必須是唯一的,但是允許為空值。唯一索引和主鍵索引的區(qū)別是:唯一索引字段可以為null且可以存在多個null值,而主鍵索引字段不可以為null。唯一索引的用途:唯一標識數(shù)據(jù)庫表中的每條記錄,主要是用來防止數(shù)據(jù)重復(fù)插入。創(chuàng)建唯一索引的SQL語句如下:
3、組合索引:在表中的多個字段組合上創(chuàng)建的索引,只有在查詢條件中使用了這些字段的左邊字段時,索引才會被使用,使用組合索引時需遵循最左前綴原則。
4、全文索引:只能在CHAR、VARCHAR和TEXT類型字段上使用全文索引。
5、普通索引:普通索引是最基本的索引,它沒有任何限制,值可以為空。
什么是最左匹配原則?
如果 SQL 語句中用到了組合索引中的最左邊的索引,那么這條 SQL 語句就可以利用這個組合索引去進行匹配。當遇到范圍查詢(>
、<
、between
、like
)就會停止匹配,后面的字段不會用到索引。
對(a,b,c)
建立索引,查詢條件使用 a/ab/abc 會走索引,使用 bc 不會走索引。
對(a,b,c,d)
建立索引,查詢條件為a = 1 and b = 2 and c > 3 and d = 4
,那么a、b和c三個字段能用到索引,而d無法使用索引。因為遇到了范圍查詢。
如下圖,對(a, b) 建立索引,a 在索引樹中是全局有序的,而 b 是全局無序,局部有序(當a相等時,會根據(jù)b進行排序)。直接執(zhí)行b = 2
這種查詢條件無法使用索引。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3dtvHvSS-1677315913415)(http://img.topjavaer.cn/img/最左前綴.png)]
當a的值確定的時候,b是有序的。例如a = 1時,b值為1,2是有序的狀態(tài)。當a = 2時候,b的值為1,4也是有序狀態(tài)。 當執(zhí)行a = 1 and b = 2時a和b字段能用到索引。而執(zhí)行a > 1 and b = 2時,a字段能用到索引,b字段用不到索引。因為a的值此時是一個范圍,不是固定的,在這個范圍內(nèi)b值不是有序的,因此b字段無法使用索引。
什么是聚集索引?
InnoDB使用表的主鍵構(gòu)造主鍵索引樹,同時葉子節(jié)點中存放的即為整張表的記錄數(shù)據(jù)。聚集索引葉子節(jié)點的存儲是邏輯上連續(xù)的,使用雙向鏈表連接,葉子節(jié)點按照主鍵的順序排序,因此對于主鍵的排序查找和范圍查找速度比較快。
聚集索引的葉子節(jié)點就是整張表的行記錄。InnoDB 主鍵使用的是聚簇索引。聚集索引要比非聚集索引查詢效率高很多。
對于InnoDB
來說,聚集索引一般是表中的主鍵索引,如果表中沒有顯示指定主鍵,則會選擇表中的第一個不允許為NULL
的唯一索引。如果沒有主鍵也沒有合適的唯一索引,那么InnoDB
內(nèi)部會生成一個隱藏的主鍵作為聚集索引,這個隱藏的主鍵長度為6個字節(jié),它的值會隨著數(shù)據(jù)的插入自增。
什么是覆蓋索引?
select
的數(shù)據(jù)列只用從索引中就能夠取得,不需要回表進行二次查詢,也就是說查詢列要被所使用的索引覆蓋。對于innodb
表的二級索引,如果索引能覆蓋到查詢的列,那么就可以避免對主鍵索引的二次查詢。
不是所有類型的索引都可以成為覆蓋索引。覆蓋索引要存儲索引列的值,而哈希索引、全文索引不存儲索引列的值,所以MySQL使用b+樹索引做覆蓋索引。
對于使用了覆蓋索引的查詢,在查詢前面使用explain
,輸出的extra列會顯示為using index
。
比如user_like
用戶點贊表,組合索引為(user_id, blog_id)
,user_id
和blog_id
都不為null
。
explain
結(jié)果的Extra
列為Using index
,查詢的列被索引覆蓋,并且where篩選條件符合最左前綴原則,通過索引查找就能直接找到符合條件的數(shù)據(jù),不需要回表查詢數(shù)據(jù)。
explain
結(jié)果的Extra
列為Using where; Using index
, 查詢的列被索引覆蓋,where篩選條件不符合最左前綴原則,無法通過索引查找找到符合條件的數(shù)據(jù),但可以通過索引掃描找到符合條件的數(shù)據(jù),也不需要回表查詢數(shù)據(jù)。

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

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


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

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

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

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

2、事務(wù)a和事務(wù)b同時開啟事務(wù)start transaction
;
3、事務(wù)a插入數(shù)據(jù)然后提交;
4、事務(wù)b執(zhí)行全表的update;
5、事務(wù)b然后執(zhí)行查詢,查到了事務(wù)a中插入的數(shù)據(jù)。(下圖左邊是事務(wù)b,右邊是事務(wù)a。事務(wù)開始之前只有兩條記錄,事務(wù)a插入一條數(shù)據(jù)之后,事務(wù)b查詢出來是三條數(shù)據(jù))
那么MySQL是如何避免幻讀?
在快照讀情況下,MySQL通過
mvcc
來避免幻讀。在當前讀情況下,MySQL通過
next-key
來避免幻讀(加行鎖和間隙鎖來實現(xiàn)的)。
next-key包括兩部分:行鎖和間隙鎖。行鎖是加在索引上的鎖,間隙鎖是加在索引之間的。
Serializable
隔離級別也可以避免幻讀,會鎖住整張表,并發(fā)性極低,一般不會使用。
共享鎖和排他鎖
SELECT 的讀取鎖定主要分為兩種方式:共享鎖和排他鎖。
這兩種方式主要的不同在于LOCK IN SHARE MODE
多個事務(wù)同時更新同一個表單時很容易造成死鎖。申請排他鎖的前提是,沒有線程對該結(jié)果集的任何行數(shù)據(jù)使用排它鎖或者共享鎖,否則申請會受到阻塞。在進行事務(wù)操作時,MySQL會對查詢結(jié)果集的每行數(shù)據(jù)添加排它鎖,其他線程對這些數(shù)據(jù)的更改或刪除操作會被阻塞(只能讀操作),直到該語句的事務(wù)被commit
語句或rollback
語句結(jié)束為止。
SELECT... FOR UPDATE
使用注意事項:
for update
僅適用于innodb,且必須在事務(wù)范圍內(nèi)才能生效。根據(jù)主鍵進行查詢,查詢條件為
like
或者不等于,主鍵字段產(chǎn)生表鎖。根據(jù)非索引字段進行查詢,會產(chǎn)生表鎖。
bin log/redo log/undo log
MySQL日志主要包括查詢?nèi)罩?、慢查詢?nèi)罩?、事?wù)日志、錯誤日志、二進制日志等。其中比較重要的是 bin log
(二進制日志)和 redo log
(重做日志)和 undo log
(回滾日志)。
bin log
bin log
是MySQL數(shù)據(jù)庫級別的文件,記錄對MySQL數(shù)據(jù)庫執(zhí)行修改的所有操作,不會記錄select和show語句,主要用于恢復(fù)數(shù)據(jù)庫和同步數(shù)據(jù)庫。
redo log
redo log
是innodb引擎級別,用來記錄innodb存儲引擎的事務(wù)日志,不管事務(wù)是否提交都會記錄下來,用于數(shù)據(jù)恢復(fù)。當數(shù)據(jù)庫發(fā)生故障,innoDB存儲引擎會使用redo log
恢復(fù)到發(fā)生故障前的時刻,以此來保證數(shù)據(jù)的完整性。將參數(shù)innodb_flush_log_at_tx_commit
設(shè)置為1,那么在執(zhí)行commit時會將redo log
同步寫到磁盤。
undo log
除了記錄redo log
外,當進行數(shù)據(jù)修改時還會記錄undo log
,undo log
用于數(shù)據(jù)的撤回操作,它保留了記錄修改前的內(nèi)容。通過undo log
可以實現(xiàn)事務(wù)回滾,并且可以根據(jù)undo log
回溯到某個特定的版本的數(shù)據(jù),實現(xiàn)MVCC。
bin log和redo log有什么區(qū)別?
bin log
會記錄所有日志記錄,包括InnoDB、MyISAM等存儲引擎的日志;redo log
只記錄innoDB自身的事務(wù)日志。bin log
只在事務(wù)提交前寫入到磁盤,一個事務(wù)只寫一次;而在事務(wù)進行過程,會有redo log
不斷寫入磁盤。bin log
是邏輯日志,記錄的是SQL語句的原始邏輯;redo log
是物理日志,記錄的是在某個數(shù)據(jù)頁上做了什么修改。
講一下MySQL架構(gòu)?
MySQL主要分為 Server 層和存儲引擎層:
Server 層:主要包括連接器、查詢緩存、分析器、優(yōu)化器、執(zhí)行器等,所有跨存儲引擎的功能都在這一層實現(xiàn),比如存儲過程、觸發(fā)器、視圖,函數(shù)等,還有一個通用的日志模塊 binglog 日志模塊。
存儲引擎: 主要負責數(shù)據(jù)的存儲和讀取。server 層通過api與存儲引擎進行通信。
Server 層基本組件
連接器: 當客戶端連接 MySQL 時,server層會對其進行身份認證和權(quán)限校驗。
查詢緩存: 執(zhí)行查詢語句的時候,會先查詢緩存,先校驗這個 sql 是否執(zhí)行過,如果有緩存這個 sql,就會直接返回給客戶端,如果沒有命中,就會執(zhí)行后續(xù)的操作。
分析器: 沒有命中緩存的話,SQL 語句就會經(jīng)過分析器,主要分為兩步,詞法分析和語法分析,先看 SQL 語句要做什么,再檢查 SQL 語句語法是否正確。
優(yōu)化器: 優(yōu)化器對查詢進行優(yōu)化,包括重寫查詢、決定表的讀寫順序以及選擇合適的索引等,生成執(zhí)行計劃。
執(zhí)行器: 首先執(zhí)行前會校驗該用戶有沒有權(quán)限,如果沒有權(quán)限,就會返回錯誤信息,如果有權(quán)限,就會根據(jù)執(zhí)行計劃去調(diào)用引擎的接口,返回結(jié)果。
分庫分表
當單表的數(shù)據(jù)量達到1000W或100G以后,優(yōu)化索引、添加從庫等可能對數(shù)據(jù)庫性能提升效果不明顯,此時就要考慮對其進行切分了。切分的目的就在于減少數(shù)據(jù)庫的負擔,縮短查詢的時間。
數(shù)據(jù)切分可以分為兩種方式:垂直劃分和水平劃分。
垂直劃分
垂直劃分數(shù)據(jù)庫是根據(jù)業(yè)務(wù)進行劃分,例如購物場景,可以將庫中涉及商品、訂單、用戶的表分別劃分出成一個庫,通過降低單庫的大小來提高性能。同樣的,分表的情況就是將一個大表根據(jù)業(yè)務(wù)功能拆分成一個個子表,例如商品基本信息和商品描述,商品基本信息一般會展示在商品列表,商品描述在商品詳情頁,可以將商品基本信息和商品描述拆分成兩張表。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x3caROJh-1677315913458)(http://img.topjavaer.cn/img/垂直劃分.png)]
優(yōu)點:行記錄變小,數(shù)據(jù)頁可以存放更多記錄,在查詢時減少I/O次數(shù)。
缺點:
主鍵出現(xiàn)冗余,需要管理冗余列;
會引起表連接JOIN操作,可以通過在業(yè)務(wù)服務(wù)器上進行join來減少數(shù)據(jù)庫壓力;
依然存在單表數(shù)據(jù)量過大的問題。
水平劃分
水平劃分是根據(jù)一定規(guī)則,例如時間或id序列值等進行數(shù)據(jù)的拆分。比如根據(jù)年份來拆分不同的數(shù)據(jù)庫。每個數(shù)據(jù)庫結(jié)構(gòu)一致,但是數(shù)據(jù)得以拆分,從而提升性能。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-c8GaY50m-1677315913464)(http://img.topjavaer.cn/img/水平劃分.png)]
優(yōu)點:單庫(表)的數(shù)據(jù)量得以減少,提高性能;切分出的表結(jié)構(gòu)相同,程序改動較少。
缺點:
分片事務(wù)一致性難以解決
跨節(jié)點
join
性能差,邏輯復(fù)雜數(shù)據(jù)分片在擴容時需要遷移
什么是分區(qū)表?
分區(qū)是把一張表的數(shù)據(jù)分成N多個區(qū)塊。分區(qū)表是一個獨立的邏輯表,但是底層由多個物理子表組成。
當查詢條件的數(shù)據(jù)分布在某一個分區(qū)的時候,查詢引擎只會去某一個分區(qū)查詢,而不是遍歷整個表。在管理層面,如果需要刪除某一個分區(qū)的數(shù)據(jù),只需要刪除對應(yīng)的分區(qū)即可。
分區(qū)一般都是放在單機里的,用的比較多的是時間范圍分區(qū),方便歸檔。只不過分庫分表需要代碼實現(xiàn),分區(qū)則是mysql內(nèi)部實現(xiàn)。分庫分表和分區(qū)并不沖突,可以結(jié)合使用。
分區(qū)表類型
range分區(qū),按照范圍分區(qū)。比如按照時間范圍分區(qū)
在/var/lib/mysql/data/
可以找到對應(yīng)的數(shù)據(jù)文件,每個分區(qū)表都有一個使用#分隔命名的表文件:
list分區(qū)
list分區(qū)和range分區(qū)相似,主要區(qū)別在于list是枚舉值列表的集合,range是連續(xù)的區(qū)間值的集合。對于list分區(qū),分區(qū)字段必須是已知的,如果插入的字段不在分區(qū)時的枚舉值中,將無法插入。
hash分區(qū)
可以將數(shù)據(jù)均勻地分布到預(yù)先定義的分區(qū)中。
分區(qū)的問題?
打開和鎖住所有底層表的成本可能很高。當查詢訪問分區(qū)表時,MySQL 需要打開并鎖住所有的底層表,這個操作在分區(qū)過濾之前發(fā)生,所以無法通過分區(qū)過濾來降低此開銷,會影響到查詢速度??梢酝ㄟ^批量操作來降低此類開銷,比如批量插入、
LOAD DATA INFILE
和一次刪除多行數(shù)據(jù)。維護分區(qū)的成本可能很高。例如重組分區(qū),會先創(chuàng)建一個臨時分區(qū),然后將數(shù)據(jù)復(fù)制到其中,最后再刪除原分區(qū)。
所有分區(qū)必須使用相同的存儲引擎。
查詢語句執(zhí)行流程?
查詢語句的執(zhí)行流程如下:權(quán)限校驗、查詢緩存、分析器、優(yōu)化器、權(quán)限校驗、執(zhí)行器、引擎。
舉個例子,查詢語句如下:
首先檢查權(quán)限,沒有權(quán)限則返回錯誤;
MySQL8.0以前會查詢緩存,緩存命中則直接返回,沒有則執(zhí)行下一步;
詞法分析和語法分析。提取表名、查詢條件,檢查語法是否有錯誤;
兩種執(zhí)行方案,先查
id > 1
還是name = '大彬'
,優(yōu)化器根據(jù)自己的優(yōu)化算法選擇執(zhí)行效率最好的方案;校驗權(quán)限,有權(quán)限就調(diào)用數(shù)據(jù)庫引擎接口,返回引擎的執(zhí)行結(jié)果。
更新語句執(zhí)行過程?
更新語句執(zhí)行流程如下:分析器、權(quán)限校驗、執(zhí)行器、引擎、redo log
(prepare
狀態(tài))、binlog
、redo log
(commit
狀態(tài))
舉個例子,更新語句如下:
先查詢到 id 為1的記錄,有緩存會使用緩存。
拿到查詢結(jié)果,將 name 更新為大彬,然后調(diào)用引擎接口,寫入更新數(shù)據(jù),innodb 引擎將數(shù)據(jù)保存在內(nèi)存中,同時記錄
redo log
,此時redo log
進入prepare
狀態(tài)。執(zhí)行器收到通知后記錄
binlog
,然后調(diào)用引擎接口,提交redo log
為commit
狀態(tài)。更新完成。
為什么記錄完redo log
,不直接提交,而是先進入prepare
狀態(tài)?
假設(shè)先寫redo log直接提交,然后寫binlog,寫完redo log后,機器掛了,binlog日志沒有被寫入,那么機器重啟后,這臺機器會通過redo log恢復(fù)數(shù)據(jù),但是這個時候binlog并沒有記錄該數(shù)據(jù),后續(xù)進行機器備份的時候,就會丟失這一條數(shù)據(jù),同時主從同步也會丟失這一條數(shù)據(jù)。
exist和in的區(qū)別?
exists
用于對外表記錄做篩選。exists
會遍歷外表,將外查詢表的每一行,代入內(nèi)查詢進行判斷。當exists
里的條件語句能夠返回記錄行時,條件就為真,返回外表當前記錄。反之如果exists
里的條件語句不能返回記錄行,條件為假,則外表當前記錄被丟棄。
in
是先把后邊的語句查出來放到臨時表中,然后遍歷臨時表,將臨時表的每一行,代入外查詢?nèi)ゲ檎摇?/p>
子查詢的表比較大的時候,使用exists
可以有效減少總的循環(huán)次數(shù)來提升速度;當外查詢的表比較大的時候,使用in
可以有效減少對外查詢表循環(huán)遍歷來提升速度。
MySQL中int(10)和char(10)的區(qū)別?
int(10)中的10表示的是顯示數(shù)據(jù)的長度,而char(10)表示的是存儲數(shù)據(jù)的長度。
truncate、delete與drop區(qū)別?
相同點:
truncate
和不帶where
子句的delete
、以及drop
都會刪除表內(nèi)的數(shù)據(jù)。drop
、truncate
都是DDL
語句(數(shù)據(jù)定義語言),執(zhí)行后會自動提交。
不同點:
truncate 和 delete 只刪除數(shù)據(jù)不刪除表的結(jié)構(gòu);drop 語句將刪除表的結(jié)構(gòu)被依賴的約束、觸發(fā)器、索引;
一般來說,執(zhí)行速度: drop > truncate > delete。
having和where區(qū)別?
二者作用的對象不同,
where
子句作用于表和視圖,having
作用于組。where
在數(shù)據(jù)分組前進行過濾,having
在數(shù)據(jù)分組后進行過濾。
什么是MySQL主從同步?
主從同步使得數(shù)據(jù)可以從一個數(shù)據(jù)庫服務(wù)器復(fù)制到其他服務(wù)器上,在復(fù)制數(shù)據(jù)時,一個服務(wù)器充當主服務(wù)器(master
),其余的服務(wù)器充當從服務(wù)器(slave
)。
因為復(fù)制是異步進行的,所以從服務(wù)器不需要一直連接著主服務(wù)器,從服務(wù)器甚至可以通過撥號斷斷續(xù)續(xù)地連接主服務(wù)器。通過配置文件,可以指定復(fù)制所有的數(shù)據(jù)庫,某個數(shù)據(jù)庫,甚至是某個數(shù)據(jù)庫上的某個表。
為什么要做主從同步?
讀寫分離,使數(shù)據(jù)庫能支撐更大的并發(fā)。
在主服務(wù)器上生成實時數(shù)據(jù),而在從服務(wù)器上分析這些數(shù)據(jù),從而提高主服務(wù)器的性能。
數(shù)據(jù)備份,保證數(shù)據(jù)的安全。
樂觀鎖和悲觀鎖是什么?
數(shù)據(jù)庫中的并發(fā)控制是確保在多個事務(wù)同時存取數(shù)據(jù)庫中同一數(shù)據(jù)時不破壞事務(wù)的隔離性和統(tǒng)一性以及數(shù)據(jù)庫的統(tǒng)一性。樂觀鎖和悲觀鎖是并發(fā)控制主要采用的技術(shù)手段。
悲觀鎖:假定會發(fā)生并發(fā)沖突,會對操作的數(shù)據(jù)進行加鎖,直到提交事務(wù),才會釋放鎖,其他事務(wù)才能進行修改。實現(xiàn)方式:使用數(shù)據(jù)庫中的鎖機制。
樂觀鎖:假設(shè)不會發(fā)生并發(fā)沖突,只在提交操作時檢查是否數(shù)據(jù)是否被修改過。給表增加version字段,在修改提交之前檢查version與原來取到的version值是否相等,若相等,表示數(shù)據(jù)沒有被修改,可以更新,否則,數(shù)據(jù)為臟數(shù)據(jù),不能更新。實現(xiàn)方式:樂觀鎖一般使用版本號機制或CAS算法實現(xiàn)。
用過processlist嗎?
show processlist
或 show full processlist
可以查看當前 MySQL 是否有壓力,正在運行的SQL
,有沒有慢SQL
正在執(zhí)行。返回參數(shù)如下:
id:線程ID,可以用
kill id
殺死某個線程db:數(shù)據(jù)庫名稱
user:數(shù)據(jù)庫用戶
host:數(shù)據(jù)庫實例的IP
command:當前執(zhí)行的命令,比如
Sleep
,Query
,Connect
等time:消耗時間,單位秒
state:執(zhí)行狀態(tài),主要有以下狀態(tài):
Sleep,線程正在等待客戶端發(fā)送新的請求
Locked,線程正在等待鎖
Sending data,正在處理
SELECT
查詢的記錄,同時把結(jié)果發(fā)送給客戶端Kill,正在執(zhí)行
kill
語句,殺死指定線程Connect,一個從節(jié)點連上了主節(jié)點
Quit,線程正在退出
Sorting for group,正在為
GROUP BY
做排序Sorting for order,正在為
ORDER BY
做排序
?? 8.info:正在執(zhí)行的SQL
語句
MySQL查詢 limit 1000,10 和limit 10 速度一樣快嗎?
兩種查詢方式。對應(yīng) limit offset, size
和 limit size
兩種方式。
而其實 limit size
,相當于 limit 0, size
。也就是從0開始取size條數(shù)據(jù)。
也就是說,兩種方式的區(qū)別在于offset是否為0。
先來看下limit sql的內(nèi)部執(zhí)行邏輯。
MySQL內(nèi)部分為server層和存儲引擎層。一般情況下存儲引擎都用innodb。
server層有很多模塊,其中需要關(guān)注的是執(zhí)行器是用于跟存儲引擎打交道的組件。
執(zhí)行器可以通過調(diào)用存儲引擎提供的接口,將一行行數(shù)據(jù)取出,當這些數(shù)據(jù)完全符合要求(比如滿足其他where條件),則會放到結(jié)果集中,最后返回給調(diào)用mysql的客戶端。
以主鍵索引的limit執(zhí)行過程為例:
執(zhí)行select * from xxx order by id limit 0, 10;
,select后面帶的是星號,也就是要求獲得行數(shù)據(jù)的所有字段信息。
server層會調(diào)用innodb的接口,在innodb里的主鍵索引中獲取到第0到10條完整行數(shù)據(jù),依次返回給server層,并放到server層的結(jié)果集中,返回給客戶端。
把offset搞大點,比如執(zhí)行的是:select * from xxx order by id limit 500000, 10;
server層會調(diào)用innodb的接口,由于這次的offset=500000,會在innodb里的主鍵索引中獲取到第0到(500000 + 10)條完整行數(shù)據(jù),返回給server層之后根據(jù)offset的值挨個拋棄,最后只留下最后面的size條,也就是10條數(shù)據(jù),放到server層的結(jié)果集中,返回給客戶端。
可以看出,當offset非0時,server層會從引擎層獲取到很多無用的數(shù)據(jù),而獲取的這些無用數(shù)據(jù)都是要耗時的。
因此,mysql查詢中 limit 1000,10 會比 limit 10 更慢。原因是 limit 1000,10 會取出1000+10條數(shù)據(jù),并拋棄前1000條,這部分耗時更大。
深分頁怎么優(yōu)化?
還是以上面的SQL為空:select * from xxx order by id limit 500000, 10;
方法一:
從上面的分析可以看出,當offset非常大時,server層會從引擎層獲取到很多無用的數(shù)據(jù),而當select后面是*號時,就需要拷貝完整的行信息,拷貝完整數(shù)據(jù)相比只拷貝行數(shù)據(jù)里的其中一兩個列字段更耗費時間。
因為前面的offset條數(shù)據(jù)最后都是不要的,沒有必要拷貝完整字段,所以可以將sql語句修改成:
先執(zhí)行子查詢 select id from xxx by id limit 500000, 1, 這個操作,其實也是將在innodb中的主鍵索引中獲取到500000+1條數(shù)據(jù),然后server層會拋棄前500000條,只保留最后一條數(shù)據(jù)的id。
但不同的地方在于,在返回server層的過程中,只會拷貝數(shù)據(jù)行內(nèi)的id這一列,而不會拷貝數(shù)據(jù)行的所有列,當數(shù)據(jù)量較大時,這部分的耗時還是比較明顯的。
在拿到了上面的id之后,假設(shè)這個id正好等于500000,那sql就變成了
這樣innodb再走一次主鍵索引,通過B+樹快速定位到id=500000的行數(shù)據(jù),時間復(fù)雜度是lg(n),然后向后取10條數(shù)據(jù)。
方法二:
將所有的數(shù)據(jù)根據(jù)id主鍵進行排序,然后分批次取,將當前批次的最大id作為下次篩選的條件進行查詢。
mysql
通過主鍵索引,每次定位到start_id的位置,然后往后遍歷10個數(shù)據(jù),這樣不管數(shù)據(jù)多大,查詢性能都較為穩(wěn)定。
高度為3的B+樹,可以存放多少數(shù)據(jù)?
InnoDB存儲引擎有自己的最小儲存單元——頁(Page)。
查詢InnoDB頁大小的命令如下:
可以看出 innodb 默認的一頁大小為 16384B = 16384/1024 = 16kb。
在MySQL中,B+樹一個節(jié)點的大小設(shè)為一頁或頁的倍數(shù)最為合適。因為如果一個節(jié)點的大小 < 1頁,那么讀取這個節(jié)點的時候其實讀取的還是一頁,這樣就造成了資源的浪費。
B+樹中非葉子節(jié)點存的是key + 指針;葉子節(jié)點存的是數(shù)據(jù)行。
對于葉子節(jié)點,如果一行數(shù)據(jù)大小為1k,那么一頁就能存16條數(shù)據(jù)。
對于非葉子節(jié)點,如果key使用的是bigint,則為8字節(jié),指針在MySQL中為6字節(jié),一共是14字節(jié),則16k能存放 16 * 1024 / 14 = 1170 個索引指針。
于是可以算出,對于一顆高度為2的B+樹,根節(jié)點存儲索引指針節(jié)點,那么它有1170個葉子節(jié)點存儲數(shù)據(jù),每個葉子節(jié)點可以存儲16條數(shù)據(jù),一共 1170 x 16 = 18720 條數(shù)據(jù)。而對于高度為3的B+樹,就可以存放 1170 x 1170 x 16 = 21902400 條數(shù)據(jù)(兩千多萬條數(shù)據(jù)),也就是對于兩千多萬條的數(shù)據(jù),我們只需要高度為3的B+樹就可以完成,通過主鍵查詢只需要3次IO操作就能查到對應(yīng)數(shù)據(jù)。
所以在 InnoDB 中B+樹高度一般為3層時,就能滿足千萬級的數(shù)據(jù)存儲。
MySQL單表多大進行分庫分表?
目前主流的有兩種說法:
MySQL 單表數(shù)據(jù)量大于 2000 萬行,性能會明顯下降,考慮進行分庫分表。
阿里巴巴《Java 開發(fā)手冊》提出單表行數(shù)超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
事實上,這個數(shù)值和實際記錄的條數(shù)無關(guān),而與 MySQL 的配置以及機器的硬件有關(guān)。因為MySQL為了提高性能,會將表的索引裝載到內(nèi)存中。在InnoDB buffer size 足夠的情況下,其能完成全加載進內(nèi)存,查詢不會有問題。但是,當單表數(shù)據(jù)庫到達某個量級的上限時,導(dǎo)致內(nèi)存無法存儲其索引,使得之后的 SQL 查詢會產(chǎn)生磁盤 IO,從而導(dǎo)致性能下降。當然,這個還有具體的表結(jié)構(gòu)的設(shè)計有關(guān),最終導(dǎo)致的問題都是內(nèi)存限制。
因此,對于分庫分表,需要結(jié)合實際需求,不宜過度設(shè)計,在項目一開始不采用分庫與分表設(shè)計,而是隨著業(yè)務(wù)的增長,在無法繼續(xù)優(yōu)化的情況下,再考慮分庫與分表提高系統(tǒng)的性能。對此,阿里巴巴《Java 開發(fā)手冊》補充到:如果預(yù)計三年后的數(shù)據(jù)量根本達不到這個級別,請不要在創(chuàng)建表時就分庫分表。
至于MySQL單表多大進行分庫分表,應(yīng)當根據(jù)機器資源進行評估。
大表查詢慢怎么優(yōu)化?
某個表有近千萬數(shù)據(jù),查詢比較慢,如何優(yōu)化?
當MySQL單表記錄數(shù)過大時,數(shù)據(jù)庫的性能會明顯下降,一些常見的優(yōu)化措施如下:
合理建立索引。在合適的字段上建立索引,例如在WHERE和ORDER BY命令上涉及的列建立索引,可根據(jù)EXPLAIN來查看是否用了索引還是全表掃描
索引優(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ū)。對關(guān)鍵字段建立水平分區(qū),比如時間字段,若查詢條件往往通過時間范圍來進行查詢,能提升不少性能
利用緩存。利用Redis等緩存熱點數(shù)據(jù),提高查詢效率
限定數(shù)據(jù)的范圍。比如:用戶在查詢歷史信息的時候,可以控制在一個月的時間范圍內(nèi)
讀寫分離。經(jīng)典的數(shù)據(jù)庫拆分方案,主庫負責寫,從庫負責讀
通過分庫分表的方式進行優(yōu)化,主要有垂直拆分和水平拆分
合理建立索引。在合適的字段上建立索引,例如在WHERE和ORDERBY命令上涉及的列建立索引
?? 7.數(shù)據(jù)異構(gòu)到es
?? 8.冷熱數(shù)據(jù)分離。幾個月之前不常用的數(shù)據(jù)放到冷庫中,最新的數(shù)據(jù)比較新的數(shù)據(jù)放到熱庫中
?? 9.升級數(shù)據(jù)庫類型,換一種能兼容MySQL的數(shù)據(jù)庫(OceanBase、tidb)
說說count(1)、count(*)和count(字段名)的區(qū)別
嗯,先說說count(1) and count(字段名)的區(qū)別。
兩者的主要區(qū)別是
count(1) 會統(tǒng)計表中的所有的記錄數(shù),包含字段為null 的記錄。
count(字段名) 會統(tǒng)計該字段在表中出現(xiàn)的次數(shù),忽略字段為null 的情況。即不統(tǒng)計字段為null 的記錄。
接下來看看三者之間的區(qū)別。
執(zhí)行效果上:
count(*)包括了所有的列,相當于行數(shù),在統(tǒng)計結(jié)果的時候,不會忽略列值為NULL
count(1)包括了忽略所有列,用1代表代碼行,在統(tǒng)計結(jié)果的時候,不會忽略列值為NULL
count(字段名)只包括列名那一列,在統(tǒng)計結(jié)果的時候,會忽略列值為空(這里的空不是只空字符串或者0,而是表示null)的計數(shù),即某個字段值為NULL時,不統(tǒng)計。
執(zhí)行效率上:
列名為主鍵,count(字段名)會比count(1)快
列名不為主鍵,count(1)會比count(列名)快
如果表多個列并且沒有主鍵,則 count(1) 的執(zhí)行效率優(yōu)于 count(*)
如果有主鍵,則 select count(主鍵)的執(zhí)行效率是最優(yōu)的
如果表只有一個字段,則 select count(*)最優(yōu)。
MySQL中DATETIME 和 TIMESTAMP有什么區(qū)別?
嗯,TIMESTAMP
和DATETIME
都可以用來存儲時間,它們主要有以下區(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
支持的時間范圍比DATATIME
要小,容易出現(xiàn)超出的情況。
2.空間占用
TIMESTAMP :占 4 個字節(jié)
DATETIME:在 MySQL 5.6.4 之前,占 8 個字節(jié) ,之后版本,占 5 個字節(jié)
3.存入時間是否會自動轉(zhuǎn)換
TIMESTAMP
類型在默認情況下,insert、update 數(shù)據(jù)時,TIMESTAMP
列會自動以當前時間(CURRENT_TIMESTAMP
)填充/更新。DATETIME
則不會做任何轉(zhuǎn)換,也不會檢測時區(qū),你給什么數(shù)據(jù),它存什么數(shù)據(jù)。
4.TIMESTAMP
比較受時區(qū)timezone的影響以及MYSQL版本和服務(wù)器的SQL MODE的影響。因為TIMESTAMP
存的是時間戳,在不同的時區(qū)得出的時間不一致。
5.如果存進NULL,兩者實際存儲的值不同。
TIMESTAMP:會自動存儲當前時間 now() 。
DATETIME:不會自動存儲當前時間,會直接存入 NULL 值。
說說為什么不建議用外鍵?
外鍵是一種約束,這個約束的存在,會保證表間數(shù)據(jù)的關(guān)系始終完整。外鍵的存在,并非全然沒有優(yōu)點。
外鍵可以保證數(shù)據(jù)的完整性和一致性,級聯(lián)操作方便。而且使用外鍵可以將數(shù)據(jù)完整性判斷托付給了數(shù)據(jù)庫完成,減少了程序的代碼量。
雖然外鍵能夠保證數(shù)據(jù)的完整性,但是會給系統(tǒng)帶來很多缺陷。
1、并發(fā)問題。在使用外鍵的情況下,每次修改數(shù)據(jù)都需要去另外一個表檢查數(shù)據(jù),需要獲取額外的鎖。若是在高并發(fā)大流量事務(wù)場景,使用外鍵更容易造成死鎖。
2、擴展性問題。比如從MySQL
遷移到Oracle
,外鍵依賴于數(shù)據(jù)庫本身的特性,做遷移可能不方便。
3、不利于分庫分表。在水平拆分和分庫的情況下,外鍵是無法生效的。將數(shù)據(jù)間關(guān)系的維護,放入應(yīng)用程序中,為將來的分庫分表省去很多的麻煩。
使用自增主鍵有什么好處?
自增主鍵可以讓主鍵索引盡量地保持遞增順序插入,避免了頁分裂,因此索引更緊湊,在查詢的時候,效率也就更高。
自增主鍵保存在什么地方?
不同的引擎對于自增值的保存策略不同:
MyISAM引擎的自增值保存在數(shù)據(jù)文件中。
在MySQL8.0以前,InnoDB引擎的自增值是存在內(nèi)存中。MySQL重啟之后內(nèi)存中的這個值就丟失了,每次重啟后第一次打開表的時候,會找自增值的最大值max(id),然后將最大值加1作為這個表的自增值;MySQL8.0版本會將自增值的變更記錄在redo log中,重啟時依靠redo log恢復(fù)。
自增主鍵一定是連續(xù)的嗎?
不一定,有幾種情況會導(dǎo)致自增主鍵不連續(xù)。
1、唯一鍵沖突導(dǎo)致自增主鍵不連續(xù)。當我們向一個自增主鍵的InnoDB表中插入數(shù)據(jù)的時候,如果違反表中定義的唯一索引的唯一約束,會導(dǎo)致插入數(shù)據(jù)失敗。此時表的自增主鍵的鍵值是會向后加1滾動的。下次再次插入數(shù)據(jù)的時候,就不能再使用上次因插入數(shù)據(jù)失敗而滾動生成的鍵值了,必須使用新滾動生成的鍵值。
2、事務(wù)回滾導(dǎo)致自增主鍵不連續(xù)。當我們向一個自增主鍵的InnoDB表中插入數(shù)據(jù)的時候,如果顯式開啟了事務(wù),然后因為某種原因最后回滾了事務(wù),此時表的自增值也會發(fā)生滾動,而接下里新插入的數(shù)據(jù),也將不能使用滾動過的自增值,而是需要重新申請一個新的自增值。
3、批量插入導(dǎo)致自增值不連續(xù)。MySQL有一個批量申請自增id的策略:
語句執(zhí)行過程中,第一次申請自增id,分配1個自增id
1個用完以后,第二次申請,會分配2個自增id
2個用完以后,第三次申請,會分配4個自增id
依次類推,每次申請都是上一次的兩倍(最后一次申請不一定全部使用)
如果下一個事務(wù)再次插入數(shù)據(jù)的時候,則會基于上一個事務(wù)申請后的自增值基礎(chǔ)上再申請。此時就出現(xiàn)自增值不連續(xù)的情況出現(xiàn)。
4、自增步長不是1,也會導(dǎo)致自增主鍵不連續(xù)。
InnoDB的自增值為什么不能回收利用?
主要為了提升插入數(shù)據(jù)的效率和并行度。
假設(shè)有兩個并行執(zhí)行的事務(wù),在申請自增值的時候,為了避免兩個事務(wù)申請到相同的自增 id,肯定要加鎖,然后順序申請。
假設(shè)事務(wù) A 申請到了 id=2, 事務(wù) B 申請到 id=3,那么這時候表 t 的自增值是 4,之后繼續(xù)執(zhí)行。
事務(wù) B 正確提交了,但事務(wù) A 出現(xiàn)了唯一鍵沖突。
如果允許事務(wù) A 把自增 id 回退,也就是把表 t 的當前自增值改回 2,那么就會出現(xiàn)這樣的情況:表里面已經(jīng)有 id=3 的行,而當前的自增 id 值是 2。
接下來,繼續(xù)執(zhí)行的其他事務(wù)就會申請到 id=2,然后再申請到 id=3。這時,就會出現(xiàn)插入語句報錯“主鍵沖突”。
而為了解決這個主鍵沖突,有兩種方法:
每次申請 id 之前,先判斷表里面是否已經(jīng)存在這個 id。如果存在,就跳過這個 id。但是,這個方法的成本很高。因為,本來申請 id 是一個很快的操作,現(xiàn)在還要再去主鍵索引樹上判斷 id 是否存在。
把自增 id 的鎖范圍擴大,必須等到一個事務(wù)執(zhí)行完成并提交,下一個事務(wù)才能再申請自增 id。這個方法的問題,就是鎖的粒度太大,系統(tǒng)并發(fā)能力大大下降。
可見,這兩個方法都會導(dǎo)致性能問題。
因此,InnoDB 放棄了“允許自增 id 回退”這個設(shè)計,語句執(zhí)行失敗也不回退自增 id。
MySQL數(shù)據(jù)如何同步到Redis緩存?
有兩種方案:
1、通過MySQL自動同步刷新Redis,MySQL觸發(fā)器+UDF函數(shù)實現(xiàn)。
過程大致如下:
在MySQL中對要操作的數(shù)據(jù)設(shè)置觸發(fā)器Trigger,監(jiān)聽操作
客戶端向MySQL中寫入數(shù)據(jù)時,觸發(fā)器會被觸發(fā),觸發(fā)之后調(diào)用MySQL的UDF函數(shù)
UDF函數(shù)可以把數(shù)據(jù)寫入到Redis中,從而達到同步的效果
2、解析MySQL的binlog,實現(xiàn)將數(shù)據(jù)庫中的數(shù)據(jù)同步到Redis??梢酝ㄟ^canal實現(xiàn)。canal是阿里巴巴旗下的一款開源項目,基于數(shù)據(jù)庫增量日志解析,提供增量數(shù)據(jù)訂閱&消費。
canal的原理如下:
canal模擬mysql slave的交互協(xié)議,偽裝自己為mysql slave,向mysql master發(fā)送dump協(xié)議
mysql master收到dump請求,開始推送binary log給canal
canal解析binary log對象(原始為byte流),將數(shù)據(jù)同步寫入Redis。
為什么阿里Java手冊禁止使用存儲過程?
先看看什么是存儲過程。
存儲過程是在大型數(shù)據(jù)庫系統(tǒng)中,一組為了完成特定功能的SQL 語句集,它存儲在數(shù)據(jù)庫中,一次編譯后永久有效,用戶通過指定存儲過程的名字并給出參數(shù)(如果該存儲過程帶有參數(shù))來執(zhí)行它。
存儲過程主要有以下幾個缺點。
存儲過程難以調(diào)試。存儲過程的開發(fā)一直缺少有效的 IDE 環(huán)境。SQL 本身經(jīng)常很長,調(diào)試式要把句子拆開分別獨立執(zhí)行,非常麻煩。
移植性差。存儲過程的移植困難,一般業(yè)務(wù)系統(tǒng)總會不可避免地用到數(shù)據(jù)庫獨有的特性和語法,更換數(shù)據(jù)庫時這部分代碼就需要重寫,成本較高。
管理困難。存儲過程的目錄是扁平的,而不是文件系統(tǒng)那樣的樹形結(jié)構(gòu),腳本少的時候還好辦,一旦多起來,目錄就會陷入混亂。
存儲過程是只優(yōu)化一次,有的時候隨著數(shù)據(jù)量的增加或者數(shù)據(jù)結(jié)構(gòu)的變化,原來存儲過程選擇的執(zhí)行計劃也許并不是最優(yōu)的了,所以這個時候需要手動干預(yù)或者重新編譯了。

最后
??? 小伙伴們學習編程,有時候不知道怎么學,從哪里開始學。掌握了基本的一些語法或者做了兩個案例后,不知道下一步怎么走,不知道如何去學習更加高深的知識。
那么對于這些小伙伴們,我準備了大量的視頻教程,PDF電子書籍,以及源代碼!
只要+up主威信wangkeit1備注“B站”就可以白嫖領(lǐng)取啦!
