1、MySQL基本架構
大體來說,MySQL可以分為Server層和存儲引擎層兩部分。
Server 層包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等。
存儲引擎層負責數據的存儲和提取。其架構模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎。也就是說,你執行 create table
建表的時候,如果不指定引擎類型,默認使用的就是 InnoDB。不過,你也可以通過指定存儲引擎的類型來選擇別的引擎,比如在 create table
語句中使用 engine=memory
, 來指定使用內存引擎創建表。不同存儲引擎的表數據存取方式不同,支持的功能也不同。
2、MyISAM和InnoDB區別
MyISAM是MySQL的默認數據庫引擎(5.5版之前)。雖然性能極佳,?且提供了?量的特性,包括全?索引、壓縮、空間函數等,但MyISAM不?持事務和?級鎖,?且最?的缺陷就是崩潰后?法安全恢復。不過,5.5版本之后,MySQL引?了InnoDB(事務性數據庫引擎),MySQL5.5版本后默認的存儲引擎為InnoDB。
?多數時候我們使?的都是 InnoDB 存儲引擎,但是在某些情況下使? MyISAM 也是合適的?如
讀密集的情況下。(如果你不介意 MyISAM 崩潰恢復問題的話)。
兩者的對比:
- 是否?持?級鎖 : MyISAM 只有表級鎖(table-level locking),?InnoDB ?持?級鎖(row?level locking)和表級鎖,默認為?級鎖。
- 是否?持事務和崩潰后的安全恢復: MyISAM 強調的是性能,每次查詢具有原?性,其執?速度?InnoDB類型更快,但是不提供事務?持。但是InnoDB 提供事務?持事務,外部鍵等?級數據庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能?(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
- 是否?持外鍵: MyISAM不?持,?InnoDB?持。
- 是否?持MVCC :僅 InnoDB ?持。應對?并發事務, MVCC?單純的加鎖更?效;MVCC只 在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下?作;MVCC可以使?樂觀(optimistic)鎖和悲觀(pessimistic)鎖來實現;各數據庫中MVCC實現并不統?。推薦閱讀:MySQL-InnoDB-MVCC多版本并發控制(https://segmentfault.com/a/1190000012650596)
《MySQL高性能》上面有有一句話這樣寫到:
不要輕易相信“MyISAM?InnoDB快”之類的經驗之談,這個結論往往不是絕對的。在很多我們已知場景中,InnoDB的速度都可以讓MyISAM望塵莫及,尤其是?到了聚簇索引,或者需要訪問的數據都可以放?內存的應?。
3、字符集及校對規則
字符集指的是?種從?進制編碼到某類字符符號的映射。校對規則則是指某種字符集下的排序規則。MySQL中每?種字符集都會對應?系列的校對規則。
MySQL采?的是類似繼承的?式指定字符集的默認值,每個數據庫以及每張數據表都有??的默認值,他們逐層繼承。?如:某個庫中所有表的默認字符集將是該數據庫所指定的字符集(這些表在沒有指定字符集的情況下,才會采?默認字符集)。
4、索引
MySQL索引使?的數據結構主要有BTree索引和哈希索引 。對于哈希索引來說,底層的數據結構就是哈希表,因此在絕?多數需求為單條記錄查詢的時候(哈希索引做區間查詢的速度較慢),可以選擇哈希索引,查詢性能最快;其余?部分場景,建議選擇BTree索引。
MySQL的BTree索引使?的是B樹中的B+Tree,但對于主要的兩種存儲引擎的實現?式是不同
的。
- MyISAM: B+Tree葉節點的data域存放的是數據記錄的地址。在索引檢索的時候,?先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其 data 域的值,然后以 data 域的值為地址讀取相應的數據記錄。這被稱為“?聚簇索引”。
- InnoDB: 其數據?件本身就是索引?件。相?MyISAM,索引?件和數據?件是分離的,其表數據?件本身就是按B+Tree組織的?個索引結構,樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據?件本身就是主索引。這被稱為“聚簇索引(或聚集索引)”。?其余的索引都作為輔助索引,輔助索引的data域存儲相應記錄主鍵的值?不是地址,這也是和MyISAM不同的地?。在根據主索引搜索時,直接找到key所在的節點即可取出數據;在根據輔助索引查找時,則需要先取出主鍵的值,再??遍主索引。 因此,在設計表的時候,不建議使?過?的字段作為主鍵,也不建議使??單調的字段作為主鍵,這樣會造成主索引頻繁分裂。
5、查詢緩存的使用
MySQL 拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。如果你的查詢能夠直接在這個緩存中找到 key,那么這個 value 就會被直接返回給客戶端。如果語句不在查詢緩存中,就會繼續后面的執行階段。執行完成后,執行結果會被存入查詢緩存中。你可以看到,如果查詢命中緩存,MySQL 不需要執行后面的復雜操作,就可以直接返回結果,這個效率會很高。
查詢緩存的失效非常頻繁,只要有對一個表的更新,這個表上所有的查詢緩存都會被清空。因此很可能你費勁地把結果存起來,還沒使用呢,就被一個更新全清空了。對于更新壓力大的數據庫來說,查詢緩存的命中率會非常低。除非你的業務就是有一張靜態表,很長時間才會更新一次。比如,一個系統配置表,那這張表上的查詢才適合使用查詢緩存。
所以在MySQL 8.0 版本后查詢緩存功能被移除,因為這個功能不太實?。
6、什么是事務
事務是邏輯上的?組操作,要么全部都執?,要么全部都不執行。
MySQL 中,事務支持是在引擎層實現的。你現在知道,MySQL 是一個支持多引擎的系統,但并不是所有的引擎都支持事務。比如 MySQL 原生的 MyISAM 引擎就不支持事務,這也是 MyISAM 被 InnoDB 取代的重要原因之一。
事務最經典也經常被拿出來說例?就是轉賬了。假如?明要給?紅轉賬1000元,這個轉賬會涉及到兩個關鍵操作就是:將?明的余額減少1000元,將?紅的余額增加1000元。萬?在這兩個操作之間突然出現錯誤?如銀?系統崩潰,導致?明余額減少??紅的余額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要么都成功,要么都要失敗。
7、事務的四大特性(ACID)
- 原?性(Atomicity): 事務是最?的執?單位,不允許分割。事務的原?性確保動作要么全部完成,要么完全不起作?;
- ?致性(Consistency): 執?事務前后,數據保持?致,多個事務對同?個數據讀取的結果是相同的;
- 隔離性(Isolation): 并發訪問數據庫時,?個?戶的事務不被其他事務所?擾,各并發事務之間數據庫是獨?的;
- 持久性(Durability): ?個事務被提交之后。它對數據庫中數據的改變是持久的,即使數據庫發?故障也不應該對其有任何影響。
8、并發事務帶來哪些問題?
在典型的應?程序中,多個事務并發運?,經常會操作相同的數據來完成各?的任務(多個用戶對同一數據進行操作)。并發雖然是必須的,但可能會導致以下的問題:
- 臟讀(Dirty read): 當?個事務正在訪問數據并且對數據進?了修改,?這種修改還沒有提交到數據庫中,這時另外?個事務也訪問了這個數據,然后使?了這個數據。因為這個數據是還沒有提交的數據,那么另外?個事務讀到的這個數據是“臟數據”,依據“臟數據”所做的操作可能是不正確的。
- 丟失修改(Lost to modify): 指在?個事務讀取?個數據時,另外?個事務也訪問了該數據,那么在第?個事務中修改了這個數據后,第?個事務也修改了這個數據。這樣第?個事務內的修改結果就被丟失,因此稱為丟失修改。 例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
- 不可重復讀(Unrepeatableread): 指在?個事務內多次讀同?數據。在這個事務還沒有結束時,另?個事務也訪問該數據。那么,在第?個事務中的兩次讀數據之間,由于第?個事務的修改導致第?個事務兩次讀取的數據可能不太?樣。這就發?了在?個事務內兩次讀到的數據是不?樣的情況,因此稱為不可重復讀。
- 幻讀(Phantom read): 幻讀與不可重復讀類似。它發?在?個事務(T1)讀取了??數據,接著另?個并發事務(T2)插?了?些數據時。在隨后的查詢中,第?個事務(T1)就會發現多了?些原本不存在的記錄,就好像發?了幻覺?樣,所以稱為幻讀。
不可重復讀和幻讀區別:
不可重復讀的重點是修改?如多次讀取?條記錄發現其中某些列的值被修改,幻讀的重點在于新增或者刪除?如多次讀取?條記錄發現記錄增多或減少了。
9、事務隔離級別有哪些?MySQL的默認隔離級別是?
SQL標準定義了四個隔離級別:
- 讀取未提交(read uncommitted):最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀。
- 讀取已提交(read committed):允許讀取并發事務已經提交的數據,可以阻?臟讀,但是幻讀或不可重復讀仍有可能發?。
- 可重復讀(repeatable read):對同?字段的多次讀取結果都是?致的,除?數據是被本身事務??所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發?。
- 可串行化(serializable ):最?的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執?,這樣事務之間就完全不可能產??擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
MySQL InnoDB 存儲引擎的默認?持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;
命令來查看。
這?需要注意的是:與 SQL 標準不同的地?在于 InnoDB 存儲引擎在 REPEATABLE?READ(可重讀)事務隔離級別下使?的是Next-Key Lock 鎖算法,因此可以避免幻讀的產?,這與其他數據庫系統(如 SQL Server)是不同的。所以說InnoDB 存儲引擎的默認?持的隔離級別是 REPEATABLE-READ(可重讀)已經可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可串?化) 隔離級別。因為隔離級別越低,事務請求的鎖越少,所以?部分數據庫系統的隔離級別都是 READ-COMMITTED(讀取提交內容) ,但是你要知道的是InnoDB 存儲引擎默認使? REPEAaTABLE-READ(可重讀) 并不會有任何性能損失。
10、鎖機制與InnoDB鎖算法
MyISAM和InnoDB存儲引擎使?的鎖:
- MyISAM采?表級鎖(table-level locking)。
- InnoDB?持?級鎖(row-level locking)和表級鎖,默認為行級鎖。
表級鎖和行級鎖對比:
- 表級鎖: MySQL中鎖定粒度最大的?種鎖,對當前操作的整張表加鎖,實現簡單,資源消耗也比較少,加鎖快,不會出現死鎖。其鎖定粒度最?,觸發鎖沖突的概率最?,并發度最低,MyISAM和 InnoDB引擎都?持表級鎖。
- 行級鎖: MySQL中鎖定粒度最小的?種鎖,只針對當前操作的行進?加鎖。 行級鎖能大大減少數據庫操作的沖突。其加鎖粒度最小,并發度?,但加鎖的開銷也最?,加鎖慢,會出現死鎖。
InnoDB存儲引擎的鎖的算法有三種:
- Record lock:單個?記錄上的鎖
- Gap lock:間隙鎖,鎖定?個范圍,不包括記錄本身
- Next-key lock:record+gap 鎖定?個范圍,包含記錄本身
相關知識點:
- innodb對于?的查詢使?next-key lock
- Next-locking keying為了解決Phantom Problem幻讀問題
- 當查詢的索引含有唯?屬性時,將next-key lock降級為record key
- Gap鎖設計的?的是為了阻?多個事務將記錄插?到同?范圍內,?這會導致幻讀問題的產?
- 有兩種?式顯式關閉gap鎖:(除了外鍵約束和唯?性檢查外,其余情況僅使?record lock) A. 將事務隔離級別設置為RC B. 將參數innodb_locks_unsafe_for_binlog設置為1
11、大表優化
當MySQL單表記錄數過?時,數據庫的CRUD性能會明顯下降,?些常?的優化措施如下:
- 限定數據的范圍
務必禁?不帶任何限制數據范圍條件的查詢語句。?如:我們當?戶在查詢訂單歷史的時候,我們可以控制在?個?的范圍內;
- 讀/寫分離
經典的數據庫拆分?案,主庫負責寫,從庫負責讀;
- 垂直分區
根據數據庫??數據表的相關性進?拆分。 例如,?戶表中既有?戶的登錄信息?有?戶的基本信息,可以將?戶表拆分成兩個單獨的表,甚?放到單獨的庫做分庫。
簡單來說垂直拆分是指數據表列的拆分,把?張列?較多的表拆分為多張表。 如下圖所示,這樣來說?家應該就更容易理解了。
- 垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易于維護。
- 垂直拆分的缺點: 主鍵會出現冗余,需要管理冗余列,并會引起Join操作,可以通過在應?層進行Join來解決。此外,垂直分區會讓事務變得更加復雜。
- 水平分區
保持數據表結構不變,通過某種策略存儲數據分?。這樣每??數據分散到不同的表或者庫中,達到了分布式的?的。 ?平拆分可以?撐?常?的數據量。
?平拆分是指數據表?的拆分,表的?數超過200萬?時,就會變慢,這時可以把?張的表的數據拆成多張表來存放。舉個例?:我們可以將?戶信息表拆分成多個?戶信息表,這樣就可以避免單?表數據量過?對性能造成影響。
?平拆分可以?持?常?的數據量。需要注意的?點是:分表僅僅是解決了單?表數據過?的問題,但由于表的數據還是在同?臺機器上,其實對于提升MySQL并發能?沒有什么意義,所以?平拆分最好分庫。
?平拆分能夠 ?持?常?的數據量存儲,應?端改造也少,但 分?事務難以解決 ,跨節點Join性能較差,邏輯復雜。《Java?程師修煉之道》的作者推薦 盡量不要對數據進?分?,因為拆分會帶來邏輯、部署、運維的各種復雜度 ,?般的數據表在優化得當的情況下?撐千萬以下的數據量是沒有太?問題的。如果實在要分?,盡量選擇客戶端分?架構,這樣可以減少?次和中間件的?絡I/O。
- 客戶端代理: 分?邏輯在應?端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 當當?的 Sharding-JDBC 、阿?的TDDL是兩種比較常?的實現。
- 中間件代理: 在應?和數據中間加了?個代理層。分片邏輯統?維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、?易的DDB等等都是這種架構的實現。
12、解釋?下什么是池化設計思想。什么是數據庫連接池?為什么需要數據庫連接池?
池化設計應該不是?個新名詞。我們常?的如java線程池、jdbc連接池、redis連接池等就是這類設計的代表實現。這種設計會初始預設資源,解決的問題就是抵消每次獲取資源的消耗,如創建線程的開銷,獲取遠程連接的開銷等。就好?你去?堂打飯,打飯的?媽會先把飯盛好?份放那?,你來了就直接拿著飯盒加菜即可,不?再臨時?盛飯?打菜,效率就?了。除了初始化資源,池化設計還包括如下這些特征:池?的初始值、池?的活躍值、池?的最?值等,這些特征可以直接映射到java線程池和數據庫連接池的成員屬性中。
數據庫連接本質就是?個 socket 的連接。數據庫服務端還要維護?些緩存和?戶權限信息之類的所以占?了?些內存。我們可以把數據庫連接池是看做是維護的數據庫連接的緩存,以便將來
需要對數據庫的請求時可以重?這些連接。為每個?戶打開和維護數據庫連接,尤其是對動態數據庫驅動的?站應?程序的請求,既昂貴?浪費資源。在連接池中,創建連接后,將其放置在池中,并再次使?它,因此不必建?新的連接。如果使?了所有連接,則會建??個新連接并將其添加到池中。 連接池還減少了?戶必須等待建?與數據庫的連接的時間。
13、一條SQL語句在MySQL中如何執行
- 查詢語句:
- 連接器:身份認證和權限相關(登錄 MySQL 的時候)。
- 查詢緩存:執行查詢語句的時候,會先查詢緩存(MySQL 8.0 版本后移除,因為這個功能不太實用)。
- 分析器:沒有命中緩存的話,SQL 語句就會經過分析器,詞法分析分析輸入的SQL語句字符串各部分內容,根據詞法分析的結果,語法分析器會根據語法規則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法。
- 優化器:在開始執行之前,還要先經過優化器的處理。優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序。
- 執行器:開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤。如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口獲取數據。
-
更新語句
更新語句的執行流程
MySQL 自帶的日志模塊式 binlog(歸檔日志) ,所有的存儲引擎都可以使用,我們常用的 InnoDB 引擎還自帶了一個日志模塊 redo log(重做日志)。
14、一條SQL語句執行得很慢得原因有哪些
詳細內容可參考 騰訊?試:?條SQL語句執?得很慢的原因有哪些?---不看后悔系列
一條SQL語句執行得很慢,一般可以分為以下兩種情況來討論:
-
大多數情況是正常的,只是偶爾會出現很慢的情況。
- 數據庫在刷新臟頁(flush)
當我們要往數據庫插入一條數據、或者要更新一條數據的時候,我們知道數據庫會在內存中把對應字段的數據更新了,但是更新之后,這些更新的字段并不會馬上同步持久化到磁盤中去,而是把這些更新的記錄寫入到 redo log 日記中去,等到空閑的時候,在通過 redo log 里的日記把最新的數據同步到磁盤中去。
刷臟頁有下面4種場景
- redolog寫滿了
- 內存不夠用了:如果一次查詢較多的數據,恰好碰到所查數據頁不在內存中時,需要申請內存,而此時恰好內存不足的時候就需要淘汰一部分內存數據頁,如果是干凈頁,就直接釋放,如果恰好是臟頁就需要刷臟頁。
- MySQL 認為系統“空閑”的時候:這時系統沒什么壓力。
- MySQL 正常關閉的時候:這時候,MySQL 會把內存的臟頁都 flush 到磁盤上,這樣下次 MySQL 啟動的時候,就可以直接從磁盤上讀數據,啟動速度會很快。
- 拿不到鎖
我們要執行的這條語句,剛好這條語句涉及到的表,別人在用,并且加鎖了,我們拿不到鎖,只能慢慢等待別人釋放鎖了。或者,表沒有加鎖,但要使用到的某個一行被加鎖了。如果要判斷是否真的在等待鎖,我們可以用 show
processlist
這個命令來查看當前的狀態
- 數據庫在刷新臟頁(flush)
-
在數據量不變的情況下,這條SQL語句一直以來都執行的很慢。
- 沒有使用到索引
(1)字段沒有索引:查詢字段上沒有索引,只能走全表掃描,導致查詢語句很慢。
(2)字段有索引但卻沒有用到索引:select * from t where c - 1 = 1000;
語句不會用到索引,因為運算在左邊,需要把運算修改至右邊,select * from t where c = 1000+1;
就能用上索引。
(3)函數操作導致沒有用上索引:如果我們在查詢的時候,對字段進行了函數操作,也是會導致沒有用上索引的。 - 數據庫自己選錯索引了
select * from t where 100 < c and c < 100000;
這條查詢語句,就算在 c 字段上有索引,系統也并不一定會走 c 這個字段上的索引,而是有可能會直接掃描掃描全表,找出所有符合 100 < c and c < 100000 的數據。
- 沒有使用到索引