寫在之前:不建議那種上來就是各種面試題羅列,然后背書式的去記憶,對技術的提升幫助很小,對正經面試也沒什么幫助,有點東西的面試官深挖下就懵逼了。
個人建議把面試題看作是費曼學習法中的回顧、簡化的環節,准備面試的時候,跟着題目先自己講給自己聽,看看自己會滿意嗎,不滿意就繼續學習這個點,如此反復,好的offer離你不遠的,奧利給
文章收錄在 GitHub JavaKeeper ,N線互聯網開發必備技能兵器譜,筆記自取。
一、MySQL架構
和其它數據庫相比,MySQL有點與眾不同,它的架構可以在多種不同場景中應用並發揮良好作用。主要體現在存儲引擎的架構上,插件式的存儲引擎架構將查詢處理和其它的系統任務以及數據的存儲提取相分離。這種架構可以根據業務的需求和實際需要選擇合適的存儲引擎。
-
連接層:最上層是一些客戶端和連接服務。主要完成一些類似於連接處理、授權認證、及相關的安全方案。在該層上引入了線程池的概念,為通過認證安全接入的客戶端提供線程。同樣在該層上可以實現基於SSL的安全鏈接。服務器也會為安全接入的每個客戶端驗證它所具有的操作權限。
-
服務層:第二層服務層,主要完成大部分的核心服務功能, 包括查詢解析、分析、優化、緩存、以及所有的內置函數,所有跨存儲引擎的功能也都在這一層實現,包括觸發器、存儲過程、視圖等
-
引擎層:第三層存儲引擎層,存儲引擎真正的負責了MySQL中數據的存儲和提取,服務器通過API與存儲引擎進行通信。不同的存儲引擎具有的功能不同,這樣我們可以根據自己的實際需要進行選取
-
存儲層:第四層為數據存儲層,主要是將數據存儲在運行於該設備的文件系統之上,並完成與存儲引擎的交互
畫出 MySQL 架構圖,這種變態問題都能問的出來
MySQL 的查詢流程具體是?or 一條SQL語句在MySQL中如何執行的?
客戶端請求 ---> 連接器(驗證用戶身份,給予權限) ---> 查詢緩存(存在緩存則直接返回,不存在則執行后續操作) ---> 分析器(對SQL進行詞法分析和語法分析操作) ---> 優化器(主要對執行的sql優化選擇最優的執行方案方法) ---> 執行器(執行時會先看用戶是否有執行權限,有才去使用這個引擎提供的接口) ---> 去引擎層獲取數據返回(如果開啟查詢緩存則會緩存查詢結果)
說說MySQL有哪些存儲引擎?都有哪些區別?
二、存儲引擎
存儲引擎是MySQL的組件,用於處理不同表類型的SQL操作。不同的存儲引擎提供不同的存儲機制、索引技巧、鎖定水平等功能,使用不同的存儲引擎,還可以獲得特定的功能。
使用哪一種引擎可以靈活選擇,一個數據庫中多個表可以使用不同引擎以滿足各種性能和實際需求,使用合適的存儲引擎,將會提高整個數據庫的性能 。
MySQL服務器使用可插拔的存儲引擎體系結構,可以從運行中的 MySQL 服務器加載或卸載存儲引擎 。
查看存儲引擎
-- 查看支持的存儲引擎
SHOW ENGINES
-- 查看默認存儲引擎
SHOW VARIABLES LIKE 'storage_engine'
--查看具體某一個表所使用的存儲引擎,這個默認存儲引擎被修改了!
show create table tablename
--准確查看某個數據庫中的某一表所使用的存儲引擎
show table status like 'tablename'
show table status from database where name="tablename"
設置存儲引擎
-- 建表時指定存儲引擎。默認的就是INNODB,不需要設置
CREATE TABLE t1 (i INT) ENGINE = INNODB;
CREATE TABLE t2 (i INT) ENGINE = CSV;
CREATE TABLE t3 (i INT) ENGINE = MEMORY;
-- 修改存儲引擎
ALTER TABLE t ENGINE = InnoDB;
-- 修改默認存儲引擎,也可以在配置文件my.cnf中修改默認引擎
SET default_storage_engine=NDBCLUSTER;
默認情況下,每當 CREATE TABLE
或 ALTER TABLE
不能使用默認存儲引擎時,都會生成一個警告。為了防止在所需的引擎不可用時出現令人困惑的意外行為,可以啟用 NO_ENGINE_SUBSTITUTION SQL
模式。如果所需的引擎不可用,則此設置將產生錯誤而不是警告,並且不會創建或更改表
存儲引擎對比
常見的存儲引擎就 InnoDB、MyISAM、Memory、NDB。
InnoDB 現在是 MySQL 默認的存儲引擎,支持事務、行級鎖定和外鍵
文件存儲結構對比
在 MySQL中建立任何一張數據表,在其數據目錄對應的數據庫目錄下都有對應表的 .frm
文件,.frm
文件是用來保存每個數據表的元數據(meta)信息,包括表結構的定義等,與數據庫存儲引擎無關,也就是任何存儲引擎的數據表都必須有.frm
文件,命名方式為 數據表名.frm,如user.frm。
查看MySQL 數據保存在哪里:show variables like 'data%'
MyISAM 物理文件結構為:
.frm
文件:與表相關的元數據信息都存放在frm文件,包括表結構的定義信息等.MYD
(MYData
) 文件:MyISAM 存儲引擎專用,用於存儲MyISAM 表的數據.MYI
(MYIndex
)文件:MyISAM 存儲引擎專用,用於存儲MyISAM 表的索引相關信息
InnoDB 物理文件結構為:
-
.frm
文件:與表相關的元數據信息都存放在frm文件,包括表結構的定義信息等 -
.ibd
文件或.ibdata
文件: 這兩種文件都是存放 InnoDB 數據的文件,之所以有兩種文件形式存放 InnoDB 的數據,是因為 InnoDB 的數據存儲方式能夠通過配置來決定是使用共享表空間存放存儲數據,還是用獨享表空間存放存儲數據。獨享表空間存儲方式使用
.ibd
文件,並且每個表一個.ibd
文件
共享表空間存儲方式使用.ibdata
文件,所有表共同使用一個.ibdata
文件(或多個,可自己配置)
ps:正經公司,這些都有專業運維去做,數據備份、恢復啥的,讓我一個 Javaer 搞這的話,加錢不?
面試這么回答
- InnoDB 支持事務,MyISAM 不支持事務。這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一;
- InnoDB 支持外鍵,而 MyISAM 不支持。對一個包含外鍵的 InnoDB 表轉為 MYISAM 會失敗;
- InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主鍵索引的葉子節點上,因此 InnoDB 必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。而 MyISAM 是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
- InnoDB 不保存表的具體行數,執行
select count(*) from table
時需要全表掃描。而 MyISAM 用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快; - InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。一個更新語句會鎖住整張表,導致其他查詢和更新都會被阻塞,因此並發訪問受限。這也是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一;
對比項 | MyISAM | InnoDB |
---|---|---|
主外鍵 | 不支持 | 支持 |
事務 | 不支持 | 支持 |
行表鎖 | 表鎖,即使操作一條記錄也會鎖住整個表,不適合高並發的操作 | 行鎖,操作時只鎖某一行,不對其它行有影響,適合高並發的操作 |
緩存 | 只緩存索引,不緩存真實數據 | 不僅緩存索引還要緩存真實數據,對內存要求較高,而且內存大小對性能有決定性的影響 |
表空間 | 小 | 大 |
關注點 | 性能 | 事務 |
默認安裝 | 是 | 是 |
一張表,里面有ID自增主鍵,當insert了17條記錄之后,刪除了第15,16,17條記錄,再把Mysql重啟,再insert一條記錄,這條記錄的ID是18還是15 ?
如果表的類型是MyISAM,那么是18。因為MyISAM表會把自增主鍵的最大ID 記錄到數據文件中,重啟MySQL自增主鍵的最大ID也不會丟失;
如果表的類型是InnoDB,那么是15。因為InnoDB 表只是把自增主鍵的最大ID記錄到內存中,所以重啟數據庫或對表進行OPTION操作,都會導致最大ID丟失。
哪個存儲引擎執行 select count(*) 更快,為什么?
MyISAM更快,因為MyISAM內部維護了一個計數器,可以直接調取。
-
在 MyISAM 存儲引擎中,把表的總行數存儲在磁盤上,當執行 select count(*) from t 時,直接返回總數據。
-
在 InnoDB 存儲引擎中,跟 MyISAM 不一樣,沒有將總行數存儲在磁盤上,當執行 select count(*) from t 時,會先把數據讀出來,一行一行的累加,最后返回總數量。
InnoDB 中 count(*) 語句是在執行的時候,全表掃描統計總數量,所以當數據越來越大時,語句就越來越耗時了,為什么 InnoDB 引擎不像 MyISAM 引擎一樣,將總行數存儲到磁盤上?這跟 InnoDB 的事務特性有關,由於多版本並發控制(MVCC)的原因,InnoDB 表“應該返回多少行”也是不確定的。
三、數據類型
主要包括以下五大類:
- 整數類型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT
- 浮點數類型:FLOAT、DOUBLE、DECIMAL
- 字符串類型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB
- 日期類型:Date、DateTime、TimeStamp、Time、Year
- 其他數據類型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等
CHAR 和 VARCHAR 的區別?
char是固定長度,varchar長度可變:
char(n) 和 varchar(n) 中括號中 n 代表字符的個數,並不代表字節個數,比如 CHAR(30) 就可以存儲 30 個字符。
存儲時,前者不管實際存儲數據的長度,直接按 char 規定的長度分配存儲空間;而后者會根據實際存儲的數據分配最終的存儲空間
相同點:
- char(n),varchar(n)中的n都代表字符的個數
- 超過char,varchar最大長度n的限制后,字符串會被截斷。
不同點:
- char不論實際存儲的字符數都會占用n個字符的空間,而varchar只會占用實際字符應該占用的字節空間加1(實際長度length,0<=length<255)或加2(length>255)。因為varchar保存數據時除了要保存字符串之外還會加一個字節來記錄長度(如果列聲明長度大於255則使用兩個字節來保存長度)。
- 能存儲的最大空間限制不一樣:char的存儲上限為255字節。
- char在存儲時會截斷尾部的空格,而varchar不會。
char是適合存儲很短的、一般固定長度的字符串。例如,char非常適合存儲密碼的MD5值,因為這是一個定長的值。對於非常短的列,char比varchar在存儲空間上也更有效率。
列的字符串類型可以是什么?
字符串類型是:SET、BLOB、ENUM、CHAR、CHAR、TEXT、VARCHAR
BLOB和TEXT有什么區別?
BLOB是一個二進制對象,可以容納可變數量的數據。有四種類型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB
TEXT是一個不區分大小寫的BLOB。四種TEXT類型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。
BLOB 保存二進制數據,TEXT 保存字符數據。
四、索引
說說你對 MySQL 索引的理解?
數據庫索引的原理,為什么要用 B+樹,為什么不用二叉樹?
聚集索引與非聚集索引的區別?
InnoDB引擎中的索引策略,了解過嗎?
創建索引的方式有哪些?
聚簇索引/非聚簇索引,mysql索引底層實現,為什么不用B-tree,為什么不用hash,葉子結點存放的是數據還是指向數據的內存地址,使用索引需要注意的幾個地方?
-
MYSQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取數據的數據結構,所以說索引的本質是:數據結構
-
索引的目的在於提高查詢效率,可以類比字典、 火車站的車次表、圖書的目錄等 。
-
可以簡單的理解為“排好序的快速查找數據結構”,數據本身之外,數據庫還維護者一個滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。下圖是一種可能的索引方式示例。
左邊的數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址
為了加快Col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值,和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在一定的復雜度內獲取到對應的數據,從而快速檢索出符合條件的記錄。
-
索引本身也很大,不可能全部存儲在內存中,一般以索引文件的形式存儲在磁盤上
-
平常說的索引,沒有特別指明的話,就是B+樹(多路搜索樹,不一定是二叉樹)結構組織的索引。其中聚集索引,次要索引,覆蓋索引,復合索引,前綴索引,唯一索引默認都是使用B+樹索引,統稱索引。此外還有哈希索引等。
基本語法:
-
創建:
-
創建索引:
CREATE [UNIQUE] INDEX indexName ON mytable(username(length));
如果是CHAR,VARCHAR類型,length可以小於字段實際長度;如果是BLOB和TEXT類型,必須指定 length。
-
修改表結構(添加索引):
ALTER table tableName ADD [UNIQUE] INDEX indexName(columnName)
-
-
刪除:
DROP INDEX [indexName] ON mytable;
-
查看:
SHOW INDEX FROM table_name\G
--可以通過添加 \G 來格式化輸出信息。 -
使用ALERT命令
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list):
該語句添加一個主鍵,這意味着索引值必須是唯一的,且不能為NULL。ALTER TABLE tbl_name ADD UNIQUE index_name (column_list
這條語句創建索引的值必須是唯一的(除了NULL外,NULL可能會出現多次)。ALTER TABLE tbl_name ADD INDEX index_name (column_list)
添加普通索引,索引值可出現多次。ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)
該語句指定了索引為 FULLTEXT ,用於全文索引。
優勢
-
提高數據檢索效率,降低數據庫IO成本
-
降低數據排序的成本,降低CPU的消耗
劣勢
- 索引也是一張表,保存了主鍵和索引字段,並指向實體表的記錄,所以也需要占用內存
- 雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如對表進行INSERT、UPDATE和DELETE。
因為更新表時,MySQL不僅要保存數據,還要保存一下索引文件每次更新添加了索引列的字段,
都會調整因為更新所帶來的鍵值變化后的索引信息
MySQL索引分類
數據結構角度
- B+樹索引
- Hash索引
- Full-Text全文索引
- R-Tree索引
從物理存儲角度
-
聚集索引(clustered index)
-
非聚集索引(non-clustered index),也叫輔助索引(secondary index)
聚集索引和非聚集索引都是B+樹結構
從邏輯角度
- 主鍵索引:主鍵索引是一種特殊的唯一索引,不允許有空值
- 普通索引或者單列索引:每個索引只包含單個列,一個表可以有多個單列索引
- 多列索引(復合索引、聯合索引):復合索引指多個字段上創建的索引,只有在查詢條件中使用了創建索引時的第一個字段,索引才會被使用。使用復合索引時遵循最左前綴集合
- 唯一索引或者非唯一索引
- 空間索引:空間索引是對空間數據類型的字段建立的索引,MYSQL中的空間數據類型有4種,分別是GEOMETRY、POINT、LINESTRING、POLYGON。
MYSQL使用SPATIAL關鍵字進行擴展,使得能夠用於創建正規索引類型的語法創建空間索引。創建空間索引的列,必須將其聲明為NOT NULL,空間索引只能在存儲引擎為MYISAM的表中創建
為什么MySQL 索引中用B+tree,不用B-tree 或者其他樹,為什么不用 Hash 索引
聚簇索引/非聚簇索引,MySQL 索引底層實現,葉子結點存放的是數據還是指向數據的內存地址,使用索引需要注意的幾個地方?
使用索引查詢一定能提高查詢的性能嗎?為什么?
MySQL索引結構
首先要明白索引(index)是在存儲引擎(storage engine)層面實現的,而不是server層面。不是所有的存儲引擎都支持所有的索引類型。即使多個存儲引擎支持某一索引類型,它們的實現和行為也可能有所差別。
B+Tree索引
MyISAM 和 InnoDB 存儲引擎,都使用 B+Tree的數據結構,它相對與 B-Tree結構,所有的數據都存放在葉子節點上,且把葉子節點通過指針連接到一起,形成了一條數據鏈表,以加快相鄰數據的檢索效率。
先了解下 B-Tree 和 B+Tree 的區別
B-Tree
B-Tree是為磁盤等外存儲設備設計的一種平衡查找樹。
系統從磁盤讀取數據到內存時是以磁盤塊(block)為基本單位的,位於同一個磁盤塊中的數據會被一次性讀取出來,而不是需要什么取什么。
InnoDB 存儲引擎中有頁(Page)的概念,頁是其磁盤管理的最小單位。InnoDB 存儲引擎中默認每個頁的大小為16KB,可通過參數 innodb_page_size
將頁的大小設置為 4K、8K、16K,在 MySQL 中可通過如下命令查看頁的大小:show variables like 'innodb_page_size';
而系統一個磁盤塊的存儲空間往往沒有這么大,因此 InnoDB 每次申請磁盤空間時都會是若干地址連續磁盤塊來達到頁的大小 16KB。InnoDB 在把磁盤數據讀入到磁盤時會以頁為基本單位,在查詢數據時如果一個頁中的每條數據都能有助於定位數據記錄的位置,這將會減少磁盤I/O次數,提高查詢效率。
B-Tree 結構的數據可以讓系統高效的找到數據所在的磁盤塊。為了描述 B-Tree,首先定義一條記錄為一個二元組[key, data] ,key為記錄的鍵值,對應表中的主鍵值,data 為一行記錄中除主鍵外的數據。對於不同的記錄,key值互不相同。
一棵m階的B-Tree有如下特性:
- 每個節點最多有m個孩子
- 除了根節點和葉子節點外,其它每個節點至少有Ceil(m/2)個孩子。
- 若根節點不是葉子節點,則至少有2個孩子
- 所有葉子節點都在同一層,且不包含其它關鍵字信息
- 每個非終端節點包含n個關鍵字信息(P0,P1,…Pn, k1,…kn)
- 關鍵字的個數n滿足:ceil(m/2)-1 <= n <= m-1
- ki(i=1,…n)為關鍵字,且關鍵字升序排序
- Pi(i=1,…n)為指向子樹根節點的指針。P(i-1)指向的子樹的所有節點關鍵字均小於ki,但都大於k(i-1)
B-Tree 中的每個節點根據實際情況可以包含大量的關鍵字信息和分支,如下圖所示為一個 3 階的 B-Tree:
每個節點占用一個盤塊的磁盤空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。兩個關鍵詞划分成的三個范圍域對應三個指針指向的子樹的數據的范圍域。以根節點為例,關鍵字為17和35,P1指針指向的子樹的數據范圍為小於17,P2指針指向的子樹的數據范圍為17~35,P3指針指向的子樹的數據范圍為大於35。
模擬查找關鍵字29的過程:
- 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操作第1次】
- 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
- 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操作第2次】
- 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
- 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操作第3次】
- 在磁盤塊8中的關鍵字列表中找到關鍵字29。
分析上面過程,發現需要3次磁盤I/O操作,和3次內存查找操作。由於內存中的關鍵字是一個有序表結構,可以利用二分法查找提高效率。而3次磁盤I/O操作是影響整個B-Tree查找效率的決定因素。B-Tree相對於AVLTree縮減了節點個數,使每次磁盤I/O取到內存的數據都發揮了作用,從而提高了查詢效率。
B+Tree
B+Tree 是在 B-Tree 基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB 存儲引擎就是用 B+Tree 實現其索引結構。
從上一節中的B-Tree結構圖中可以看到每個節點中不僅包含數據的key值,還有data值。而每一個頁的存儲空間是有限的,如果data數據較大時將會導致每個節點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時同樣會導致B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。在B+Tree中,所有數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲key值信息,這樣可以大大加大每個節點存儲的key值數量,降低B+Tree的高度。
B+Tree相對於B-Tree有幾點不同:
- 非葉子節點只存儲鍵值信息;
- 所有葉子節點之間都有一個鏈指針;
- 數據記錄都存放在葉子節點中
將上一節中的B-Tree優化,由於B+Tree的非葉子節點只存儲鍵值信息,假設每個磁盤塊能存儲4個鍵值及指針信息,則變成B+Tree后其結構如下圖所示:
通常在B+Tree上有兩個頭指針,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即數據節點)之間是一種鏈式環結構。因此可以對B+Tree進行兩種查找運算:一種是對於主鍵的范圍查找和分頁查找,另一種是從根節點開始,進行隨機查找。
可能上面例子中只有22條數據記錄,看不出B+Tree的優點,下面做一個推算:
InnoDB存儲引擎中頁的大小為16KB,一般表的主鍵類型為INT(占用4個字節)或BIGINT(占用8個字節),指針類型也一般為4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(因為是估值,為方便計算,這里的K取值為103)。也就是說一個深度為3的B+Tree索引可以維護103 * 10^3 * 10^3 = 10億 條記錄。
實際情況中每個節點可能不能填充滿,因此在數據庫中,B+Tree的高度一般都在2-4層。MySQL的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多只需要1~3次磁盤I/O操作。
B+Tree性質
- 通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什么每個數據項,即索引字段要盡量的小,比如int占4字節,要比bigint8字節少一半。這也是為什么b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
- 當b+樹的數據項是復合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最后得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪里查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然后再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。
MyISAM主鍵索引與輔助索引的結構
MyISAM引擎的索引文件和數據文件是分離的。MyISAM引擎索引結構的葉子節點的數據域,存放的並不是實際的數據記錄,而是數據記錄的地址。索引文件與數據文件分離,這樣的索引稱為"非聚簇索引"。MyISAM的主索引與輔助索引區別並不大,只是主鍵索引不能有重復的關鍵字。
在MyISAM中,索引(含葉子節點)存放在單獨的.myi文件中,葉子節點存放的是數據的物理地址偏移量(通過偏移量訪問就是隨機訪問,速度很快)。
主索引是指主鍵索引,鍵值不可能重復;輔助索引則是普通索引,鍵值可能重復。
通過索引查找數據的流程:先從索引文件中查找到索引節點,從中拿到數據的文件指針,再到數據文件中通過文件指針定位了具體的數據。輔助索引類似。
InnoDB主鍵索引與輔助索引的結構
InnoDB引擎索引結構的葉子節點的數據域,存放的就是實際的數據記錄(對於主索引,此處會存放表中所有的數據記錄;對於輔助索引此處會引用主鍵,檢索的時候通過主鍵到主鍵索引中找到對應數據行),或者說,InnoDB的數據文件本身就是主鍵索引文件,這樣的索引被稱為"“聚簇索引”,一個表只能有一個聚簇索引。
主鍵索引:
我們知道InnoDB索引是聚集索引,它的索引和數據是存入同一個.idb文件中的,因此它的索引結構是在同一個樹節點中同時存放索引和數據,如下圖中最底層的葉子節點有三行數據,對應於數據表中的id、stu_id、name數據項。
在Innodb中,索引分葉子節點和非葉子節點,非葉子節點就像新華字典的目錄,單獨存放在索引段中,葉子節點則是順序排列的,在數據段中。Innodb的數據文件可以按照表來切分(只需要開啟innodb_file_per_table)
,切分后存放在xxx.ibd
中,默認不切分,存放在xxx.ibdata
中。
輔助(非主鍵)索引:
這次我們以示例中學生表中的name列建立輔助索引,它的索引結構跟主鍵索引的結構有很大差別,在最底層的葉子結點有兩行數據,第一行的字符串是輔助索引,按照ASCII碼進行排序,第二行的整數是主鍵的值。
這就意味着,對name列進行條件搜索,需要兩個步驟:
① 在輔助索引上檢索name,到達其葉子節點獲取對應的主鍵;
② 使用主鍵在主索引上再進行對應的檢索操作
這也就是所謂的“回表查詢”
InnoDB 索引結構需要注意的點
-
數據文件本身就是索引文件
-
表數據文件本身就是按 B+Tree 組織的一個索引結構文件
-
聚集索引中葉節點包含了完整的數據記錄
-
InnoDB 表必須要有主鍵,並且推薦使用整型自增主鍵
正如我們上面介紹 InnoDB 存儲結構,索引與數據是共同存儲的,不管是主鍵索引還是輔助索引,在查找時都是通過先查找到索引節點才能拿到相對應的數據,如果我們在設計表結構時沒有顯式指定索引列的話,MySQL 會從表中選擇數據不重復的列建立索引,如果沒有符合的列,則 MySQL 自動為 InnoDB 表生成一個隱含字段作為主鍵,並且這個字段長度為6個字節,類型為整型。
那為什么推薦使用整型自增主鍵而不是選擇UUID?
-
UUID是字符串,比整型消耗更多的存儲空間;
-
在B+樹中進行查找時需要跟經過的節點值比較大小,整型數據的比較運算比字符串更快速;
-
自增的整型索引在磁盤中會連續存儲,在讀取一頁數據時也是連續;UUID是隨機產生的,讀取的上下兩行數據存儲是分散的,不適合執行where id > 5 && id < 20的條件查詢語句。
-
在插入或刪除數據時,整型自增主鍵會在葉子結點的末尾建立新的葉子節點,不會破壞左側子樹的結構;UUID主鍵很容易出現這樣的情況,B+樹為了維持自身的特性,有可能會進行結構的重構,消耗更多的時間。
為什么非主鍵索引結構葉子節點存儲的是主鍵值?
保證數據一致性和節省存儲空間,可以這么理解:商城系統訂單表會存儲一個用戶ID作為關聯外鍵,而不推薦存儲完整的用戶信息,因為當我們用戶表中的信息(真實名稱、手機號、收貨地址···)修改后,不需要再次維護訂單表的用戶數據,同時也節省了存儲空間。
Hash索引
-
主要就是通過Hash算法(常見的Hash算法有直接定址法、平方取中法、折疊法、除數取余法、隨機數法),將數據庫字段數據轉換成定長的Hash值,與這條數據的行指針一並存入Hash表的對應位置;如果發生Hash碰撞(兩個不同關鍵字的Hash值相同),則在對應Hash鍵下以鏈表形式存儲。
檢索算法:在檢索查詢時,就再次對待查關鍵字再次執行相同的Hash算法,得到Hash值,到對應Hash表對應位置取出數據即可,如果發生Hash碰撞,則需要在取值時進行篩選。目前使用Hash索引的數據庫並不多,主要有Memory等。
MySQL目前有Memory引擎和NDB引擎支持Hash索引。
full-text全文索引
-
全文索引也是MyISAM的一種特殊索引類型,主要用於全文索引,InnoDB從MYSQL5.6版本提供對全文索引的支持。
-
它用於替代效率較低的LIKE模糊匹配操作,而且可以通過多字段組合的全文索引一次性全模糊匹配多個字段。
-
同樣使用B-Tree存放索引數據,但使用的是特定的算法,將字段數據分割后再進行索引(一般每4個字節一次分割),索引文件存儲的是分割前的索引字符串集合,與分割后的索引信息,對應Btree結構的節點存儲的是分割后的詞信息以及它在分割前的索引字符串集合中的位置。
R-Tree空間索引
空間索引是MyISAM的一種特殊索引類型,主要用於地理空間數據類型
為什么Mysql索引要用B+樹不是B樹?
用B+樹不用B樹考慮的是IO對性能的影響,B樹的每個節點都存儲數據,而B+樹只有葉子節點才存儲數據,所以查找相同數據量的情況下,B樹的高度更高,IO更頻繁。數據庫索引是存儲在磁盤上的,當數據量大時,就不能把整個索引全部加載到內存了,只能逐一加載每一個磁盤頁(對應索引樹的節點)。其中在MySQL底層對B+樹進行進一步優化:在葉子節點中是雙向鏈表,且在鏈表的頭結點和尾節點也是循環指向的。
面試官:為何不采用Hash方式?
因為Hash索引底層是哈希表,哈希表是一種以key-value存儲數據的結構,所以多個數據在存儲關系上是完全沒有任何順序關系的,所以,對於區間查詢是無法直接通過索引查詢的,就需要全表掃描。所以,哈希索引只適用於等值查詢的場景。而B+ Tree是一種多路平衡查詢樹,所以他的節點是天然有序的(左子節點小於父節點、父節點小於右子節點),所以對於范圍查詢的時候不需要做全表掃描。
哈希索引不支持多列聯合索引的最左匹配規則,如果有大量重復鍵值得情況下,哈希索引的效率會很低,因為存在哈希碰撞問題。
哪些情況需要創建索引
-
主鍵自動建立唯一索引
-
頻繁作為查詢條件的字段
-
查詢中與其他表關聯的字段,外鍵關系建立索引
-
單鍵/組合索引的選擇問題,高並發下傾向創建組合索引
-
查詢中排序的字段,排序字段通過索引訪問大幅提高排序速度
-
查詢中統計或分組字段
哪些情況不要創建索引
- 表記錄太少
- 經常增刪改的表
- 數據重復且分布均勻的表字段,只應該為最經常查詢和最經常排序的數據列建立索引(如果某個數據類包含太多的重復數據,建立索引沒有太大意義)
- 頻繁更新的字段不適合創建索引(會加重IO負擔)
- where條件里用不到的字段不創建索引
MySQL高效索引
覆蓋索引(Covering Index),或者叫索引覆蓋, 也就是平時所說的不需要回表操作
-
就是select的數據列只用從索引中就能夠取得,不必讀取數據行,MySQL可以利用索引返回select列表中的字段,而不必根據索引再次讀取數據文件,換句話說查詢列要被所建的索引覆蓋。
-
索引是高效找到行的一個方法,但是一般數據庫也能使用索引找到一個列的數據,因此它不必讀取整個行。畢竟索引葉子節點存儲了它們索引的數據,當能通過讀取索引就可以得到想要的數據,那就不需要讀取行了。一個索引包含(覆蓋)滿足查詢結果的數據就叫做覆蓋索引。
-
判斷標准
使用explain,可以通過輸出的extra列來判斷,對於一個索引覆蓋查詢,顯示為using index,MySQL查詢優化器在執行查詢前會決定是否有索引覆蓋查詢
五、MySQL查詢
count(*) 和 count(1)和count(列名)區別 ps:這道題說法有點多
執行效果上:
- count(*)包括了所有的列,相當於行數,在統計結果的時候,不會忽略列值為NULL
- count(1)包括了所有列,用1代表代碼行,在統計結果的時候,不會忽略列值為NULL
- count(列名)只包括列名那一列,在統計結果的時候,會忽略列值為空(這里的空不是只空字符串或者0,而是表示null)的計數,即某個字段值為NULL時,不統計。
執行效率上:
- 列名為主鍵,count(列名)會比count(1)快
- 列名不為主鍵,count(1)會比count(列名)快
- 如果表多個列並且沒有主鍵,則 count(1) 的執行效率優於 count(*)
- 如果有主鍵,則 select count(主鍵)的執行效率是最優的
- 如果表只有一個字段,則 select count(*) 最優。
MySQL中 in和 exists 的區別?
- exists:exists對外表用loop逐條查詢,每次查詢都會查看exists的條件語句,當exists里的條件語句能夠返回記錄行時(無論記錄行是的多少,只要能返回),條件就為真,返回當前loop到的這條記錄;反之,如果exists里的條件語句不能返回記錄行,則當前loop到的這條記錄被丟棄,exists的條件就像一個bool條件,當能返回結果集則為true,不能返回結果集則為false
- in:in查詢相當於多個or條件的疊加
SELECT * FROM A WHERE A.id IN (SELECT id FROM B);
SELECT * FROM A WHERE EXISTS (SELECT * from B WHERE B.id = A.id);
如果查詢的兩個表大小相當,那么用in和exists差別不大。
如果兩個表中一個較小,一個是大表,則子查詢表大的用exists,子查詢表小的用in:
UNION和UNION ALL的區別?
UNION和UNION ALL都是將兩個結果集合並為一個,兩個要聯合的SQL語句 字段個數必須一樣,而且字段類型要“相容”(一致);
-
UNION在進行表連接后會篩選掉重復的數據記錄(效率較低),而UNION ALL則不會去掉重復的數據記錄;
-
UNION會按照字段的順序進行排序,而UNION ALL只是簡單的將兩個結果合並就返回;
SQL執行順序
-
手寫
SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> ORDER BY <order_by_condition> LIMIT <limit_number>
-
機讀
FROM <left_table> ON <join_condition> <join_type> JOIN <right_table> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> SELECT DISTINCT <select_list> ORDER BY <order_by_condition> LIMIT <limit_number>
-
總結
mysql 的內連接、左連接、右連接有什么區別?
什么是內連接、外連接、交叉連接、笛卡爾積呢?
Join圖
六、MySQL 事務
事務的隔離級別有哪些?MySQL的默認隔離級別是什么?
什么是幻讀,臟讀,不可重復讀呢?
MySQL事務的四大特性以及實現原理
MVCC熟悉嗎,它的底層原理?
MySQL 事務主要用於處理操作量大,復雜度高的數據。比如說,在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操作語句就構成一個事務!
ACID — 事務基本要素
事務是由一組SQL語句組成的邏輯處理單元,具有4個屬性,通常簡稱為事務的ACID屬性。
- A (Atomicity) 原子性:整個事務中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣
- C (Consistency) 一致性:在事務開始之前和事務結束以后,數據庫的完整性約束沒有被破壞
- I (Isolation)隔離性:一個事務的執行不能其它事務干擾。即一個事務內部的操作及使用的數據對其它並發事務是隔離的,並發執行的各個事務之間不能互相干擾
- D (Durability) 持久性:在事務完成以后,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾
並發事務處理帶來的問題
- 更新丟失(Lost Update): 事務A和事務B選擇同一行,然后基於最初選定的值更新該行時,由於兩個事務都不知道彼此的存在,就會發生丟失更新問題
- 臟讀(Dirty Reads):事務A讀取了事務B更新的數據,然后B回滾操作,那么A讀取到的數據是臟數據
- 不可重復讀(Non-Repeatable Reads):事務 A 多次讀取同一數據,事務B在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
- 幻讀(Phantom Reads):幻讀與不可重復讀類似。它發生在一個事務A讀取了幾行數據,接着另一個並發事務B插入了一些數據時。在隨后的查詢中,事務A就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。
幻讀和不可重復讀的區別:
- 不可重復讀的重點是修改:在同一事務中,同樣的條件,第一次讀的數據和第二次讀的數據不一樣。(因為中間有其他事務提交了修改)
- 幻讀的重點在於新增或者刪除:在同一事務中,同樣的條件,,第一次和第二次讀出來的記錄數不一樣。(因為中間有其他事務提交了插入/刪除)
並發事務處理帶來的問題的解決辦法:
-
“更新丟失”通常是應該完全避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。
-
“臟讀” 、 “不可重復讀”和“幻讀” ,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決:
- 一種是加鎖:在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
- 另一種是數據多版本並發控制(MultiVersion Concurrency Control,簡稱 MVCC 或 MCC),也稱為多版本數據庫:不用加任何鎖, 通過一定機制生成一個數據請求時間點的一致性數據快照 (Snapshot), 並用這個快照來提供一定級別 (語句級或事務級) 的一致性讀取。從用戶的角度來看,好象是數據庫可以提供同一數據的多個版本。
事務隔離級別
數據庫事務的隔離級別有4種,由低到高分別為
- READ-UNCOMMITTED(讀未提交): 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀。
- READ-COMMITTED(讀已提交): 允許讀取並發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生。
- REPEATABLE-READ(可重復讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
- SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
查看當前數據庫的事務隔離級別:
show variables like 'tx_isolation'
下面通過事例一一闡述在事務的並發操作中可能會出現臟讀,不可重復讀,幻讀和事務隔離級別的聯系。
數據庫的事務隔離越嚴格,並發副作用越小,但付出的代價就越大,因為事務隔離實質上就是使事務在一定程度上“串行化”進行,這顯然與“並發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重復讀”和“幻讀”並不敏感,可能更關心數據並發訪問的能力。
Read uncommitted
讀未提交,就是一個事務可以讀取另一個未提交事務的數據。
事例:老板要給程序員發工資,程序員的工資是3.6萬/月。但是發工資時老板不小心按錯了數字,按成3.9萬/月,該錢已經打到程序員的戶口,但是事務還沒有提交,就在這時,程序員去查看自己這個月的工資,發現比往常多了3千元,以為漲工資了非常高興。但是老板及時發現了不對,馬上回滾差點就提交了的事務,將數字改成3.6萬再提交。
分析:實際程序員這個月的工資還是3.6萬,但是程序員看到的是3.9萬。他看到的是老板還沒提交事務時的數據。這就是臟讀。
那怎么解決臟讀呢?Read committed!讀提交,能解決臟讀問題。
Read committed
讀提交,顧名思義,就是一個事務要等另一個事務提交后才能讀取數據。
事例:程序員拿着信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(程序員事務開啟),收費系統事先檢測到他的卡里有3.6萬,就在這個時候!!程序員的妻子要把錢全部轉出充當家用,並提交。當收費系統准備扣款時,再檢測卡里的金額,發現已經沒錢了(第二次檢測金額當然要等待妻子轉出金額事務提交完)。程序員就會很郁悶,明明卡里是有錢的…
分析:這就是讀提交,若有事務對數據進行更新(UPDATE)操作時,讀操作事務要等待這個更新操作事務提交后才能讀取數據,可以解決臟讀問題。但在這個事例中,出現了一個事務范圍內兩個相同的查詢卻返回了不同數據,這就是不可重復讀。
那怎么解決可能的不可重復讀問題?Repeatable read !
Repeatable read
重復讀,就是在開始讀取數據(事務開啟)時,不再允許修改操作。 MySQL的默認事務隔離級別
事例:程序員拿着信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(事務開啟,不允許其他事務的UPDATE修改操作),收費系統事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉出金額了。接下來收費系統就可以扣款了。
分析:重復讀可以解決不可重復讀問題。寫到這里,應該明白的一點就是,不可重復讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因為幻讀問題對應的是插入INSERT操作,而不是UPDATE操作。
什么時候會出現幻讀?
事例:程序員某一天去消費,花了2千元,然后他的妻子去查看他今天的消費記錄(全表掃描FTS,妻子事務開啟),看到確實是花了2千元,就在這個時候,程序員花了1萬買了一部電腦,即新增INSERT了一條消費記錄,並提交。當妻子打印程序員的消費記錄清單時(妻子事務提交),發現花了1.2萬元,似乎出現了幻覺,這就是幻讀。
那怎么解決幻讀問題?Serializable!
Serializable 序列化
Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執行,可以避免臟讀、不可重復讀與幻讀。簡單來說,Serializable會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用問題。這種事務隔離級別效率低下,比較耗數據庫性能,一般不使用。
比較
事務隔離級別 | 讀數據一致性 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|---|
讀未提交(read-uncommitted) | 最低級被,只能保證不讀取物理上損壞的數據 | 是 | 是 | 是 |
讀已提交(read-committed) | 語句級 | 否 | 是 | 是 |
可重復讀(repeatable-read) | 事務級 | 否 | 否 | 是 |
串行化(serializable) | 最高級別,事務級 | 否 | 否 | 否 |
需要說明的是,事務隔離級別和數據訪問的並發性是對立的,事務隔離級別越高並發性就越差。所以要根據具體的應用來確定合適的事務隔離級別,這個地方沒有萬能的原則。
MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;
命令來查看,MySQL 8.0 該命令改為SELECT @@transaction_isolation;
這里需要注意的是:與 SQL 標准不同的地方在於InnoDB 存儲引擎在 REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-Key Lock 算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server)是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)已經可以完全保證事務的隔離性要求,即達到了 SQL標准的 SERIALIZABLE(可串行化)隔離級別,而且保留了比較好的並發性能。
因為隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀已提交):,但是你要知道的是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。
MVCC 多版本並發控制
MySQL的大多數事務型存儲引擎實現都不是簡單的行級鎖。基於提升並發性考慮,一般都同時實現了多版本並發控制(MVCC),包括Oracle、PostgreSQL。只是實現機制各不相同。
可以認為 MVCC 是行級鎖的一個變種,但它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現機制有所不同,但大都實現了非阻塞的讀操作,寫操作也只是鎖定必要的行。
MVCC 的實現是通過保存數據在某個時間點的快照來實現的。也就是說不管需要執行多長時間,每個事物看到的數據都是一致的。
典型的MVCC實現方式,分為樂觀(optimistic)並發控制和悲觀(pressimistic)並發控制。下邊通過 InnoDB的簡化版行為來說明 MVCC 是如何工作的。
InnoDB 的 MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現。這兩個列,一個保存了行的創建時間,一個保存行的過期時間(刪除時間)。當然存儲的並不是真實的時間,而是系統版本號(system version number)。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。
REPEATABLE READ(可重讀)隔離級別下MVCC如何工作:
-
SELECT
InnoDB會根據以下兩個條件檢查每行記錄:
-
InnoDB只查找版本早於當前事務版本的數據行,這樣可以確保事務讀取的行,要么是在開始事務之前已經存在要么是事務自身插入或者修改過的
-
行的刪除版本號要么未定義,要么大於當前事務版本號,這樣可以確保事務讀取到的行在事務開始之前未被刪除
只有符合上述兩個條件的才會被查詢出來
-
-
INSERT:InnoDB為新插入的每一行保存當前系統版本號作為行版本號
-
DELETE:InnoDB為刪除的每一行保存當前系統版本號作為行刪除標識
-
UPDATE:InnoDB為插入的一行新紀錄保存當前系統版本號作為行版本號,同時保存當前系統版本號到原來的行作為刪除標識
保存這兩個額外系統版本號,使大多數操作都不用加鎖。使數據操作簡單,性能很好,並且也能保證只會讀取到符合要求的行。不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作和一些額外的維護工作。
MVCC 只在 COMMITTED READ(讀提交)和REPEATABLE READ(可重復讀)兩種隔離級別下工作。
事務日志
InnoDB 使用日志來減少提交事務時的開銷。因為日志中已經記錄了事務,就無須在每個事務提交時把緩沖池的臟塊刷新(flush)到磁盤中。
事務修改的數據和索引通常會映射到表空間的隨機位置,所以刷新這些變更到磁盤需要很多隨機 IO。
InnoDB 假設使用常規磁盤,隨機IO比順序IO昂貴得多,因為一個IO請求需要時間把磁頭移到正確的位置,然后等待磁盤上讀出需要的部分,再轉到開始位置。
InnoDB 用日志把隨機IO變成順序IO。一旦日志安全寫到磁盤,事務就持久化了,即使斷電了,InnoDB可以重放日志並且恢復已經提交的事務。
InnoDB 使用一個后台線程智能地刷新這些變更到數據文件。這個線程可以批量組合寫入,使得數據寫入更順序,以提高效率。
事務日志可以幫助提高事務效率:
- 使用事務日志,存儲引擎在修改表的數據時只需要修改其內存拷貝,再把該修改行為記錄到持久在硬盤上的事務日志中,而不用每次都將修改的數據本身持久到磁盤。
- 事務日志采用的是追加的方式,因此寫日志的操作是磁盤上一小塊區域內的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭,所以采用事務日志的方式相對來說要快得多。
- 事務日志持久以后,內存中被修改的數據在后台可以慢慢刷回到磁盤。
- 如果數據的修改已經記錄到事務日志並持久化,但數據本身沒有寫回到磁盤,此時系統崩潰,存儲引擎在重啟時能夠自動恢復這一部分修改的數據。
目前來說,大多數存儲引擎都是這樣實現的,我們通常稱之為預寫式日志(Write-Ahead Logging),修改數據需要寫兩次磁盤。
事務的實現
事務的實現是基於數據庫的存儲引擎。不同的存儲引擎對事務的支持程度不一樣。MySQL 中支持事務的存儲引擎有 InnoDB 和 NDB。
事務的實現就是如何實現ACID特性。
事務的隔離性是通過鎖實現,而事務的原子性、一致性和持久性則是通過事務日志實現 。
事務是如何通過日志來實現的,說得越深入越好。
事務日志包括:重做日志redo和回滾日志undo
-
redo log(重做日志) 實現持久化和原子性
在innoDB的存儲引擎中,事務日志通過重做(redo)日志和innoDB存儲引擎的日志緩沖(InnoDB Log Buffer)實現。事務開啟時,事務中的操作,都會先寫入存儲引擎的日志緩沖中,在事務提交之前,這些緩沖的日志都需要提前刷新到磁盤上持久化,這就是DBA們口中常說的“日志先行”(Write-Ahead Logging)。當事務提交之后,在Buffer Pool中映射的數據文件才會慢慢刷新到磁盤。此時如果數據庫崩潰或者宕機,那么當系統重啟進行恢復時,就可以根據redo log中記錄的日志,把數據庫恢復到崩潰前的一個狀態。未完成的事務,可以繼續提交,也可以選擇回滾,這基於恢復的策略而定。
在系統啟動的時候,就已經為redo log分配了一塊連續的存儲空間,以順序追加的方式記錄Redo Log,通過順序IO來改善性能。所有的事務共享redo log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一起。
-
undo log(回滾日志) 實現一致性
undo log 主要為事務的回滾服務。在事務執行的過程中,除了記錄redo log,還會記錄一定量的undo log。undo log記錄了數據在每個操作前的狀態,如果事務執行過程中需要回滾,就可以根據undo log進行回滾操作。單個事務的回滾,只會回滾當前事務做的操作,並不會影響到其他的事務做的操作。
Undo記錄的是已部分完成並且寫入硬盤的未完成的事務,默認情況下回滾日志是記錄下表空間中的(共享表空間或者獨享表空間)
二種日志均可以視為一種恢復操作,redo_log是恢復提交事務修改的頁操作,而undo_log是回滾行記錄到特定版本。二者記錄的內容也不同,redo_log是物理日志,記錄頁的物理修改操作,而undo_log是邏輯日志,根據每行記錄進行記錄。
又引出個問題:你知道MySQL 有多少種日志嗎?
-
錯誤日志:記錄出錯信息,也記錄一些警告信息或者正確的信息。
-
查詢日志:記錄所有對數據庫請求的信息,不論這些請求是否得到了正確的執行。
-
慢查詢日志:設置一個閾值,將運行時間超過該值的所有SQL語句都記錄到慢查詢的日志文件中。
-
二進制日志:記錄對數據庫執行更改的所有操作。
-
中繼日志:中繼日志也是二進制日志,用來給slave 庫恢復
-
事務日志:重做日志redo和回滾日志undo
分布式事務相關問題,可能還會問到 2PC、3PC,,,
MySQL對分布式事務的支持
分布式事務的實現方式有很多,既可以采用 InnoDB 提供的原生的事務支持,也可以采用消息隊列來實現分布式事務的最終一致性。這里我們主要聊一下 InnoDB 對分布式事務的支持。
MySQL 從 5.0.3 InnoDB 存儲引擎開始支持XA協議的分布式事務。一個分布式事務會涉及多個行動,這些行動本身是事務性的。所有行動都必須一起成功完成,或者一起被回滾。
在MySQL中,使用分布式事務涉及一個或多個資源管理器和一個事務管理器。
如圖,MySQL 的分布式事務模型。模型中分三塊:應用程序(AP)、資源管理器(RM)、事務管理器(TM):
- 應用程序:定義了事務的邊界,指定需要做哪些事務;
- 資源管理器:提供了訪問事務的方法,通常一個數據庫就是一個資源管理器;
- 事務管理器:協調參與了全局事務中的各個事務。
分布式事務采用兩段式提交(two-phase commit)的方式:
- 第一階段所有的事務節點開始准備,告訴事務管理器ready。
- 第二階段事務管理器告訴每個節點是commit還是rollback。如果有一個節點失敗,就需要全局的節點全部rollback,以此保障事務的原子性。
七、MySQL鎖機制
數據庫的樂觀鎖和悲觀鎖?
MySQL 中有哪幾種鎖,列舉一下?
MySQL中InnoDB引擎的行鎖是怎么實現的?
MySQL 間隙鎖有沒有了解,死鎖有沒有了解,寫一段會造成死鎖的 sql 語句,死鎖發生了如何解決,MySQL 有沒有提供什么機制去解決死鎖
鎖是計算機協調多個進程或線程並發訪問某一資源的機制。
在數據庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,數據也是一種供許多用戶共享的資源。數據庫鎖定機制簡單來說,就是數據庫為了保證數據的一致性,而使各種共享資源在被並發訪問變得有序所設計的一種規則。
打個比方,我們到淘寶上買一件商品,商品只有一件庫存,這個時候如果還有另一個人買,那么如何解決是你買到還是另一個人買到的問題?這里肯定要用到事物,我們先從庫存表中取出物品數量,然后插入訂單,付款后插入付款表信息,然后更新商品數量。在這個過程中,使用鎖可以對有限的資源進行保護,解決隔離和並發的矛盾。
鎖的分類
從對數據操作的類型分類:
-
讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行,不會互相影響
-
寫鎖(排他鎖):當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖
從對數據操作的粒度分類:
為了盡可能提高數據庫的並發度,每次鎖定的數據范圍越小越好,理論上每次只鎖定當前操作的數據的方案會得到最大的並發度,但是管理鎖是很耗資源的事情(涉及獲取,檢查,釋放鎖等動作),因此數據庫系統需要在高並發響應和系統性能兩方面進行平衡,這樣就產生了“鎖粒度(Lock granularity)”的概念。
-
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,並發度最低(MyISAM 和 MEMORY 存儲引擎采用的是表級鎖);
-
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,並發度也最高(InnoDB 存儲引擎既支持行級鎖也支持表級鎖,但默認情況下是采用行級鎖);
-
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般。
適用:從鎖的角度來說,表級鎖更適合於以查詢為主,只有少量按索引條件更新數據的應用,如Web應用;而行級鎖則更適合於有大量按索引條件並發更新少量不同數據,同時又有並發查詢的應用,如一些在線事務處理(OLTP)系統。
行鎖 | 表鎖 | 頁鎖 | |
---|---|---|---|
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ | |
Memory | √ |
MyISAM 表鎖
MyISAM 的表鎖有兩種模式:
- 表共享讀鎖 (Table Read Lock):不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
- 表獨占寫鎖 (Table Write Lock):會阻塞其他用戶對同一表的讀和寫操作;
MyISAM 表的讀操作與寫操作之間,以及寫操作之間是串行的。當一個線程獲得對一個表的寫鎖后, 只有持有鎖的線程可以對表進行更新操作。 其他線程的讀、 寫操作都會等待,直到鎖被釋放為止。
默認情況下,寫鎖比讀鎖具有更高的優先級:當一個鎖釋放時,這個鎖會優先給寫鎖隊列中等候的獲取鎖請求,然后再給讀鎖隊列中等候的獲取鎖請求。
InnoDB 行鎖
InnoDB 實現了以下兩種類型的行鎖:
- 共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
- 排他鎖(X):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。
為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB 還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:
- 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。
- 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的 IX 鎖。
索引失效會導致行鎖變表鎖。比如 vchar 查詢不寫單引號的情況。
加鎖機制
樂觀鎖與悲觀鎖是兩種並發控制的思想,可用於解決丟失更新問題
樂觀鎖會“樂觀地”假定大概率不會發生並發更新沖突,訪問、處理數據過程中不加鎖,只在更新數據時再根據版本號或時間戳判斷是否有沖突,有則處理,無則提交事務。用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式
悲觀鎖會“悲觀地”假定大概率會發生並發更新沖突,訪問、處理數據前就加排他鎖,在整個數據處理過程中鎖定數據,事務提交或回滾后才釋放鎖。另外與樂觀鎖相對應的,悲觀鎖是由數據庫自己實現了的,要用的時候,我們直接調用數據庫的相關語句就可以了。
鎖模式(InnoDB有三種行鎖的算法)
-
記錄鎖(Record Locks): 單個行記錄上的鎖。對索引項加鎖,鎖定符合條件的行。其他事務不能修改和刪除加鎖項;
SELECT * FROM table WHERE id = 1 FOR UPDATE;
它會在 id=1 的記錄上加上記錄鎖,以阻止其他事務插入,更新,刪除 id=1 這一行
在通過 主鍵索引 與 唯一索引 對數據行進行 UPDATE 操作時,也會對該行數據加記錄鎖:
-- id 列為主鍵列或唯一索引列 UPDATE SET age = 50 WHERE id = 1;
-
間隙鎖(Gap Locks): 當我們使用范圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖。對於鍵值在條件范圍內但並不存在的記錄,叫做“間隙”。
InnoDB 也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖。
對索引項之間的“間隙”加鎖,鎖定記錄的范圍(對第一條記錄前的間隙或最后一條將記錄后的間隙加鎖),不包含索引項本身。其他事務不能在鎖范圍內插入數據,這樣就防止了別的事務新增幻影行。
間隙鎖基於非唯一索引,它鎖定一段范圍內的索引記錄。間隙鎖基於下面將會提到的
Next-Key Locking
算法,請務必牢記:使用間隙鎖鎖住的是一個區間,而不僅僅是這個區間中的每一條數據。SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
即所有在
(1,10)
區間內的記錄行都會被鎖住,所有id 為 2、3、4、5、6、7、8、9 的數據行的插入會被阻塞,但是 1 和 10 兩條記錄行並不會被鎖住。GAP鎖的目的,是為了防止同一事務的兩次當前讀,出現幻讀的情況
-
臨鍵鎖(Next-key Locks): 臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區間。(臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務的隔離級別降級為RC,臨鍵鎖則也會失效。)
Next-Key 可以理解為一種特殊的間隙鎖,也可以理解為一種特殊的算法。通過臨建鎖可以解決幻讀的問題。 每個數據行上的非唯一索引列上都會存在一把臨鍵鎖,當某個事務持有該數據行的臨鍵鎖時,會鎖住一段左開右閉區間的數據。需要強調的一點是,
InnoDB
中行級鎖是基於索引實現的,臨鍵鎖只與非唯一索引列有關,在唯一索引列(包括主鍵列)上不存在臨鍵鎖。對於行的查詢,都是采用該方法,主要目的是解決幻讀的問題。
select for update有什么含義,會鎖表還是鎖行還是其他
for update 僅適用於InnoDB,且必須在事務塊(BEGIN/COMMIT)中才能生效。在進行事務操作時,通過“for update”語句,MySQL會對查詢結果集中每行數據都添加排他鎖,其他線程對該記錄的更新與刪除操作都會阻塞。排他鎖包含行鎖、表鎖。
InnoDB這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!
假設有個表單 products ,里面有id跟name二個欄位,id是主鍵。
- 明確指定主鍵,並且有此筆資料,row lock
SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;
- 明確指定主鍵,若查無此筆資料,無lock
SELECT * FROM products WHERE id='-1' FOR UPDATE;
- 無主鍵,table lock
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
- 主鍵不明確,table lock
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
- 主鍵不明確,table lock
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
注1: FOR UPDATE僅適用於InnoDB,且必須在交易區塊(BEGIN/COMMIT)中才能生效。
注2: 要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個視窗來做測試。
MySQL 遇到過死鎖問題嗎,你是如何解決的?
死鎖
死鎖產生:
- 死鎖是指兩個或多個事務在同一資源上相互占用,並請求鎖定對方占用的資源,從而導致惡性循環
- 當事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖
- 鎖的行為和順序和存儲引擎相關。以同樣的順序執行語句,有些存儲引擎會產生死鎖有些不會——死鎖有雙重原因:真正的數據沖突;存儲引擎的實現方式。
檢測死鎖:數據庫系統實現了各種死鎖檢測和死鎖超時的機制。InnoDB存儲引擎能檢測到死鎖的循環依賴並立即返回一個錯誤。
死鎖恢復:死鎖發生以后,只有部分或完全回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。所以事務型應用程序在設計時必須考慮如何處理死鎖,多數情況下只需要重新執行因死鎖回滾的事務即可。
外部鎖的死鎖檢測:發生死鎖后,InnoDB 一般都能自動檢測到,並使一個事務釋放鎖並回退,另一個事務獲得鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 並不能完全自動檢測到死鎖, 這需要通過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決
死鎖影響性能:死鎖會影響性能而不是會產生嚴重錯誤,因為InnoDB會自動檢測死鎖狀況並回滾其中一個受影響的事務。在高並發系統上,當許多線程等待同一個鎖時,死鎖檢測可能導致速度變慢。 有時當發生死鎖時,禁用死鎖檢測(使用innodb_deadlock_detect配置選項)可能會更有效,這時可以依賴innodb_lock_wait_timeout
設置進行事務回滾。
MyISAM避免死鎖:
- 在自動加鎖的情況下,MyISAM 總是一次獲得 SQL 語句所需要的全部鎖,所以 MyISAM 表不會出現死鎖。
InnoDB避免死鎖:
- 為了在單個InnoDB表上執行多個並發寫入操作時避免死鎖,可以在事務開始時通過為預期要修改的每個元祖(行)使用
SELECT ... FOR UPDATE
語句來獲取必要的鎖,即使這些行的更改語句是在之后才執行的。 - 在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不應先申請共享鎖、更新時再申請排他鎖,因為這時候當用戶再申請排他鎖時,其他事務可能又已經獲得了相同記錄的共享鎖,從而造成鎖沖突,甚至死鎖
- 如果事務需要修改或鎖定多個表,則應在每個事務中以相同的順序使用加鎖語句。 在應用中,如果不同的程序會並發存取多個表,應盡量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會
- 通過
SELECT ... LOCK IN SHARE MODE
獲取行的讀鎖后,如果當前事務再需要對該記錄進行更新操作,則很有可能造成死鎖。 - 改變事務隔離級別
如果出現死鎖,可以用 show engine innodb status;
命令來確定最后一個死鎖產生的原因。返回結果中包括死鎖相關事務的詳細信息,如引發死鎖的 SQL 語句,事務已經獲得的鎖,正在等待什么鎖,以及被回滾的事務等。據此可以分析死鎖產生的原因和改進措施。
八、MySQL調優
日常工作中你是怎么優化SQL的?
SQL優化的一般步驟是什么,怎么看執行計划(explain),如何理解其中各個字段的含義?
如何寫sql能夠有效的使用到復合索引?
一條sql執行過長的時間,你如何優化,從哪些方面入手?
什么是最左前綴原則?什么是最左匹配原則?
影響mysql的性能因素
-
業務需求對MySQL的影響(合適合度)
-
存儲定位對MySQL的影響
- 不適合放進MySQL的數據
- 二進制多媒體數據
- 流水隊列數據
- 超大文本數據
- 需要放進緩存的數據
- 系統各種配置及規則數據
- 活躍用戶的基本信息數據
- 活躍用戶的個性化定制信息數據
- 准實時的統計信息數據
- 其他一些訪問頻繁但變更較少的數據
- 不適合放進MySQL的數據
-
Schema設計對系統的性能影響
- 盡量減少對數據庫訪問的請求
- 盡量減少無用數據的查詢請求
-
硬件環境對系統性能的影響
性能分析
MySQL Query Optimizer
-
MySQL 中有專門負責優化 SELECT 語句的優化器模塊,主要功能:通過計算分析系統中收集到的統計信息,為客戶端請求的 Query 提供他認為最優的執行計划(他認為最優的數據檢索方式,但不見得是 DBA 認為是最優的,這部分最耗費時間)
-
當客戶端向 MySQL 請求一條 Query,命令解析器模塊完成請求分類,區別出是 SELECT 並轉發給 MySQL Query Optimize r時,MySQL Query Optimizer 首先會對整條 Query 進行優化,處理掉一些常量表達式的預算,直接換算成常量值。並對 Query 中的查詢條件進行簡化和轉換,如去掉一些無用或顯而易見的條件、結構調整等。然后分析 Query 中的 Hint 信息(如果有),看顯示 Hint 信息是否可以完全確定該 Query 的執行計划。如果沒有 Hint 或 Hint 信息還不足以完全確定執行計划,則會讀取所涉及對象的統計信息,根據 Query 進行寫相應的計算分析,然后再得出最后的執行計划。
MySQL常見瓶頸
-
CPU:CPU在飽和的時候一般發生在數據裝入內存或從磁盤上讀取數據時候
-
IO:磁盤I/O瓶頸發生在裝入數據遠大於內存容量的時候
-
服務器硬件的性能瓶頸:top,free,iostat 和 vmstat來查看系統的性能狀態
性能下降SQL慢 執行時間長 等待時間長 原因分析
- 查詢語句寫的爛
- 索引失效(單值、復合)
- 關聯查詢太多join(設計缺陷或不得已的需求)
- 服務器調優及各個參數設置(緩沖、線程數等)
MySQL常見性能分析手段
在優化MySQL時,通常需要對數據庫進行分析,常見的分析手段有慢查詢日志,EXPLAIN 分析查詢,profiling分析以及show命令查詢系統狀態及系統變量,通過定位分析性能的瓶頸,才能更好的優化數據庫系統的性能。
性能瓶頸定位
我們可以通過 show 命令查看 MySQL 狀態及變量,找到系統的瓶頸:
Mysql> show status ——顯示狀態信息(擴展show status like ‘XXX’)
Mysql> show variables ——顯示系統變量(擴展show variables like ‘XXX’)
Mysql> show innodb status ——顯示InnoDB存儲引擎的狀態
Mysql> show processlist ——查看當前SQL執行,包括執行狀態、是否鎖表等
Shell> mysqladmin variables -u username -p password——顯示系統變量
Shell> mysqladmin extended-status -u username -p password——顯示狀態信息
Explain(執行計划)
是什么:使用 Explain 關鍵字可以模擬優化器執行SQL查詢語句,從而知道 MySQL 是如何處理你的 SQL 語句的。分析你的查詢語句或是表結構的性能瓶頸
能干嗎:
- 表的讀取順序
- 數據讀取操作的操作類型
- 哪些索引可以使用
- 哪些索引被實際使用
- 表之間的引用
- 每張表有多少行被優化器查詢
怎么玩:
- Explain + SQL語句
- 執行計划包含的信息(如果有分區表的話還會有partitions)
各字段解釋
-
id(select 查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序)
- id相同,執行順序從上往下
- id全不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
- id部分相同,執行順序是先按照數字大的先執行,然后數字相同的按照從上往下的順序執行
-
select_type(查詢類型,用於區別普通查詢、聯合查詢、子查詢等復雜查詢)
- SIMPLE :簡單的select查詢,查詢中不包含子查詢或UNION
- PRIMARY:查詢中若包含任何復雜的子部分,最外層查詢被標記為PRIMARY
- SUBQUERY:在select或where列表中包含了子查詢
- DERIVED:在from列表中包含的子查詢被標記為DERIVED,MySQL會遞歸執行這些子查詢,把結果放在臨時表里
- UNION:若第二個select出現在UNION之后,則被標記為UNION,若UNION包含在from子句的子查詢中,外層select將被標記為DERIVED
- UNION RESULT:從UNION表獲取結果的select
-
table(顯示這一行的數據是關於哪張表的)
-
type(顯示查詢使用了那種類型,從最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )
- system:表只有一行記錄(等於系統表),是 const 類型的特例,平時不會出現
- const:表示通過索引一次就找到了,const 用於比較 primary key 或 unique 索引,因為只要匹配一行數據,所以很快,如將主鍵置於 where 列表中,mysql 就能將該查詢轉換為一個常量
- eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描
- ref:非唯一性索引掃描,范圍匹配某個單獨值得所有行。本質上也是一種索引訪問,他返回所有匹配某個單獨值的行,然而,它可能也會找到多個符合條件的行,多以他應該屬於查找和掃描的混合體
- range:只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引,一般就是在你的where語句中出現了between、<、>、in等的查詢,這種范圍掃描索引比全表掃描要好,因為它只需開始於索引的某一點,而結束於另一點,不用掃描全部索引
- index:Full Index Scan,index於ALL區別為index類型只遍歷索引樹。通常比ALL快,因為索引文件通常比數據文件小。(也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)
- ALL:Full Table Scan,將遍歷全表找到匹配的行
tip: 一般來說,得保證查詢至少達到range級別,最好到達ref
-
possible_keys(顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段若存在索引,則該索引將被列出,但不一定被查詢實際使用)
-
key
-
實際使用的索引,如果為NULL,則沒有使用索引
-
查詢中若使用了覆蓋索引,則該索引和查詢的 select 字段重疊,僅出現在key列表中
-
-
key_len
- 表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
- key_len顯示的值為索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的
-
ref (顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值)
-
rows (根據表統計信息及索引選用情況,大致估算找到所需的記錄所需要讀取的行數)
-
Extra(包含不適合在其他列中顯示但十分重要的額外信息)
-
using filesort: 說明mysql會對數據使用一個外部的索引排序,不是按照表內的索引順序進行讀取。mysql中無法利用索引完成的排序操作稱為“文件排序”。常見於order by和group by語句中
-
Using temporary:使用了臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
-
using index:表示相應的select操作中使用了覆蓋索引,避免訪問了表的數據行,效率不錯,如果同時出現using where,表明索引被用來執行索引鍵值的查找;否則索引被用來讀取數據而非執行查找操作
-
using where:使用了where過濾
-
using join buffer:使用了連接緩存
-
impossible where:where子句的值總是false,不能用來獲取任何元祖
-
select tables optimized away:在沒有group by子句的情況下,基於索引優化操作或對於MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段再進行計算,查詢執行計划生成的階段即完成優化
-
distinct:優化distinct操作,在找到第一匹配的元祖后即停止找同樣值的動作
-
case:
-
第一行(執行順序4):id列為1,表示是union里的第一個select,select_type列的primary表示該查詢為外層查詢,table列被標記為
,表示查詢結果來自一個衍生表,其中derived3中3代表該查詢衍生自第三個select查詢,即id為3的select。【select d1.name......】 -
第二行(執行順序2):id為3,是整個查詢中第三個select的一部分。因查詢包含在from中,所以為derived。【select id,name from t1 where other_column=''】
-
第三行(執行順序3):select列表中的子查詢select_type為subquery,為整個查詢中的第二個select。【select id from t3】
-
第四行(執行順序1):select_type為union,說明第四個select是union里的第二個select,最先執行【select name,id from t2】
-
第五行(執行順序5):代表從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行union操作。【兩個結果union操作】
慢查詢日志
MySQL 的慢查詢日志是 MySQL 提供的一種日志記錄,它用來記錄在 MySQL 中響應時間超過閾值的語句,具體指運行時間超過 long_query_time
值的 SQL,則會被記錄到慢查詢日志中。
long_query_time
的默認值為10,意思是運行10秒以上的語句- 默認情況下,MySQL數據庫沒有開啟慢查詢日志,需要手動設置參數開啟
查看開啟狀態
SHOW VARIABLES LIKE '%slow_query_log%'
開啟慢查詢日志
- 臨時配置:
mysql> set global slow_query_log='ON';
mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log';
mysql> set global long_query_time=2;
也可set文件位置,系統會默認給一個缺省文件host_name-slow.log
使用set操作開啟慢查詢日志只對當前數據庫生效,如果MySQL重啟則會失效。
-
永久配置
修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入兩個配置參數
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/hostname-slow.log
long_query_time = 3
注:log-slow-queries 參數為慢查詢日志存放的位置,一般這個目錄要有 MySQL 的運行帳號的可寫權限,一般都將這個目錄設置為 MySQL 的數據存放目錄;long_query_time=2 中的 2 表示查詢超過兩秒才記錄;在my.cnf或者 my.ini 中添加 log-queries-not-using-indexes 參數,表示記錄下沒有使用索引的查詢。
可以用 select sleep(4)
驗證是否成功開啟。
在生產環境中,如果手工分析日志,查找、分析SQL,還是比較費勁的,所以MySQL提供了日志分析工具mysqldumpslow。
通過 mysqldumpslow --help 查看操作幫助信息
-
得到返回記錄集最多的10個SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log
-
得到訪問次數最多的10個SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log
-
得到按照時間排序的前10條里面含有左連接的查詢語句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log
-
也可以和管道配合使用
mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more
也可使用 pt-query-digest 分析 RDS MySQL 慢查詢日志
Show Profile 分析查詢
通過慢日志查詢可以知道哪些 SQL 語句執行效率低下,通過 explain 我們可以得知 SQL 語句的具體執行情況,索引使用等,還可以結合Show Profile
命令查看執行狀態。
-
Show Profile 是 MySQL 提供可以用來分析當前會話中語句執行的資源消耗情況。可以用於SQL的調優的測量
-
默認情況下,參數處於關閉狀態,並保存最近15次的運行結果
-
分析步驟
-
是否支持,看看當前的mysql版本是否支持
mysql>Show variables like 'profiling'; --默認是關閉,使用前需要開啟
-
開啟功能,默認是關閉,使用前需要開啟
mysql>set profiling=1;
-
運行SQL
-
查看結果
mysql> show profiles;
+----------+------------+---------------------------------+
| Query_ID | Duration | Query |
+----------+------------+---------------------------------+
| 1 | 0.00385450 | show variables like "profiling" |
| 2 | 0.00170050 | show variables like "profiling" |
| 3 | 0.00038025 | select * from t_base_user |
+----------+------------+---------------------------------+-
診斷SQL,show profile cpu,block io for query id(上一步前面的問題SQL數字號碼)
-
日常開發需要注意的結論
-
converting HEAP to MyISAM 查詢結果太大,內存都不夠用了往磁盤上搬了。
-
create tmp table 創建臨時表,這個要注意
-
Copying to tmp table on disk 把內存臨時表復制到磁盤
-
locked
-
-
查詢中哪些情況不會使用索引?
性能優化
索引優化
- 全值匹配我最愛
- 最佳左前綴法則,比如建立了一個聯合索引(a,b,c),那么其實我們可利用的索引就有(a), (a,b), (a,b,c)
- 不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
- 存儲引擎不能使用索引中范圍條件右邊的列
- 盡量使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減少select
- is null ,is not null 也無法使用索引
- like "xxxx%" 是可以用到索引的,like "%xxxx" 則不行(like "%xxx%" 同理)。like以通配符開頭('%abc...')索引失效會變成全表掃描的操作,
- 字符串不加單引號索引失效
- 少用or,用它來連接時會索引失效
- <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 則不行,會導致全表掃描
一般性建議
-
對於單鍵索引,盡量選擇針對當前query過濾性更好的索引
-
在選擇組合索引的時候,當前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。
-
在選擇組合索引的時候,盡量選擇可以能夠包含當前query中的where字句中更多字段的索引
-
盡可能通過分析統計信息和調整query的寫法來達到選擇合適索引的目的
-
少用Hint強制索引
查詢優化
永遠小標驅動大表(小的數據集驅動大的數據集)
slect * from A where id in (select id from B)`等價於
#等價於
select id from B
select * from A where A.id=B.id
當 B 表的數據集必須小於 A 表的數據集時,用 in 優於 exists
select * from A where exists (select 1 from B where B.id=A.id)
#等價於
select * from A
select * from B where B.id = A.id`
當 A 表的數據集小於B表的數據集時,用 exists優於用 in
注意:A表與B表的ID字段應建立索引。
order by關鍵字優化
-
order by子句,盡量使用 Index 方式排序,避免使用 FileSort 方式排序
-
MySQL 支持兩種方式的排序,FileSort 和 Index,Index效率高,它指 MySQL 掃描索引本身完成排序,FileSort 效率較低;
-
ORDER BY 滿足兩種情況,會使用Index方式排序;①ORDER BY語句使用索引最左前列 ②使用where子句與ORDER BY子句條件列組合滿足索引最左前列
-
盡可能在索引列上完成排序操作,遵照索引建的最佳最前綴
-
如果不在索引列上,filesort 有兩種算法,mysql就要啟動雙路排序和單路排序
- 雙路排序:MySQL 4.1之前是使用雙路排序,字面意思就是兩次掃描磁盤,最終得到數據
- 單路排序:從磁盤讀取查詢需要的所有列,按照order by 列在 buffer對它們進行排序,然后掃描排序后的列表進行輸出,效率高於雙路排序
-
優化策略
- 增大sort_buffer_size參數的設置
- 增大max_lencth_for_sort_data參數的設置
GROUP BY關鍵字優化
- group by實質是先排序后進行分組,遵照索引建的最佳左前綴
- 當無法使用索引列,增大
max_length_for_sort_data
參數的設置,增大sort_buffer_size
參數的設置 - where高於having,能寫在where限定的條件就不要去having限定了
數據類型優化
MySQL 支持的數據類型非常多,選擇正確的數據類型對於獲取高性能至關重要。不管存儲哪種類型的數據,下面幾個簡單的原則都有助於做出更好的選擇。
-
更小的通常更好:一般情況下,應該盡量使用可以正確存儲數據的最小數據類型。
簡單就好:簡單的數據類型通常需要更少的CPU周期。例如,整數比字符操作代價更低,因為字符集和校對規則(排序規則)使字符比較比整型比較復雜。
-
盡量避免NULL:通常情況下最好指定列為NOT NULL
九、分區、分表、分庫
MySQL分區
一般情況下我們創建的表對應一組存儲文件,使用MyISAM
存儲引擎時是一個.MYI
和.MYD
文件,使用Innodb
存儲引擎時是一個.ibd
和.frm
(表結構)文件。
當數據量較大時(一般千萬條記錄級別以上),MySQL的性能就會開始下降,這時我們就需要將數據分散到多組存儲文件,保證其單個文件的執行效率
能干嘛
- 邏輯數據分割
- 提高單一的寫和讀應用速度
- 提高分區范圍讀查詢的速度
- 分割數據能夠有多個不同的物理文件路徑
- 高效的保存歷史數據
怎么玩
首先查看當前數據庫是否支持分區
-
MySQL5.6以及之前版本:
SHOW VARIABLES LIKE '%partition%';
-
MySQL5.6:
show plugins;
分區類型及操作
-
RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區。mysql將會根據指定的拆分策略,,把數據放在不同的表文件上。相當於在文件上,被拆成了小塊.但是,對外給客戶的感覺還是一張表,透明的。
按照 range 來分,就是每個庫一段連續的數據,這個一般是按比如時間范圍來的,比如交易表啊,銷售表啊等,可以根據年月來存放數據。可能會產生熱點問題,大量的流量都打在最新的數據上了。
range 來分,好處在於說,擴容的時候很簡單。
-
LIST分區:類似於按RANGE分區,每個分區必須明確定義。它們的主要區別在於,LIST分區中每個分區的定義和選擇是基於某列的值從屬於一個值列表集中的一個值,而RANGE分區是從屬於一個連續區間值的集合。
-
HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。
hash 分發,好處在於說,可以平均分配每個庫的數據量和請求壓力;壞處在於說擴容起來比較麻煩,會有一個數據遷移的過程,之前的數據需要重新計算 hash 值重新分配到不同的庫或表
-
KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值。
看上去分區表很帥氣,為什么大部分互聯網還是更多的選擇自己分庫分表來水平擴展咧?
- 分區表,分區鍵設計不太靈活,如果不走分區鍵,很容易出現全表鎖
- 一旦數據並發量上來,如果在分區表實施關聯,就是一個災難
- 自己分庫分表,自己掌控業務場景與訪問模式,可控。分區表,研發寫了一個sql,都不確定mysql是怎么玩的,不太可控
隨着業務的發展,業務越來越復雜,應用的模塊越來越多,總的數據量很大,高並發讀寫操作均超過單個數據庫服務器的處理能力怎么辦?
這個時候就出現了數據分片,數據分片指按照某個維度將存放在單一數據庫中的數據分散地存放至多個數據庫或表中。數據分片的有效手段就是對關系型數據庫進行分庫和分表。
區別於分區的是,分區一般都是放在單機里的,用的比較多的是時間范圍分區,方便歸檔。只不過分庫分表需要代碼實現,分區則是mysql內部實現。分庫分表和分區並不沖突,可以結合使用。
說說分庫與分表的設計
MySQL分表
分表有兩種分割方式,一種垂直拆分,另一種水平拆分。
-
垂直拆分
垂直分表,通常是按照業務功能的使用頻次,把主要的、熱門的字段放在一起做為主要表。然后把不常用的,按照各自的業務屬性進行聚集,拆分到不同的次要表中;主要表和次要表的關系一般都是一對一的。
-
水平拆分(數據分片)
單表的容量不超過500W,否則建議水平拆分。是把一個表復制成同樣表結構的不同表,然后把數據按照一定的規則划分,分別存儲到這些表中,從而保證單表的容量不會太大,提升性能;當然這些結構一樣的表,可以放在一個或多個數據庫中。
水平分割的幾種方法:
- 使用MD5哈希,做法是對UID進行md5加密,然后取前幾位(我們這里取前兩位),然后就可以將不同的UID哈希到不同的用戶表(user_xx)中了。
- 還可根據時間放入不同的表,比如:article_201601,article_201602。
- 按熱度拆分,高點擊率的詞條生成各自的一張表,低熱度的詞條都放在一張大表里,待低熱度的詞條達到一定的貼數后,再把低熱度的表單獨拆分成一張表。
- 根據ID的值放入對應的表,第一個表user_0000,第二個100萬的用戶數據放在第二 個表user_0001中,隨用戶增加,直接添加用戶表就行了。
MySQL分庫
為什么要分庫?
數據庫集群環境后都是多台 slave,基本滿足了讀取操作; 但是寫入或者說大數據、頻繁的寫入操作對master性能影響就比較大,這個時候,單庫並不能解決大規模並發寫入的問題,所以就會考慮分庫。
分庫是什么?
一個庫里表太多了,導致了海量數據,系統性能下降,把原本存儲於一個庫的表拆分存儲到多個庫上, 通常是將表按照功能模塊、關系密切程度划分出來,部署到不同庫上。
優點:
-
減少增量數據寫入時的鎖對查詢的影響
-
由於單表數量下降,常見的查詢操作由於減少了需要掃描的記錄,使得單表單次查詢所需的檢索行數變少,減少了磁盤IO,時延變短
但是它無法解決單表數據量太大的問題
分庫分表后的難題
分布式事務的問題,數據的完整性和一致性問題。
數據操作維度問題:用戶、交易、訂單各個不同的維度,用戶查詢維度、產品數據分析維度的不同對比分析角度。 跨庫聯合查詢的問題,可能需要兩次查詢 跨節點的count、order by、group by以及聚合函數問題,可能需要分別在各個節點上得到結果后在應用程序端進行合並 額外的數據管理負擔,如:訪問數據表的導航定位 額外的數據運算壓力,如:需要在多個節點執行,然后再合並計算程序編碼開發難度提升,沒有太好的框架解決,更多依賴業務看如何分,如何合,是個難題。
配主從,正經公司的話,也不會讓 Javaer 去搞的,但還是要知道
十、主從復制
復制的基本原理
-
slave 會從 master 讀取 binlog 來進行數據同步
-
三個步驟
- master將改變記錄到二進制日志(binary log)。這些記錄過程叫做二進制日志事件,binary log events;
- salve 將 master 的 binary log events 拷貝到它的中繼日志(relay log);
- slave 重做中繼日志中的事件,將改變應用到自己的數據庫中。MySQL 復制是異步且是串行化的。
復制的基本原則
- 每個 slave只有一個 master
- 每個 salve只能有一個唯一的服務器 ID
- 每個master可以有多個salve
復制的最大問題
- 延時
十一、其他問題
說一說三個范式
- 第一范式(1NF):數據庫表中的字段都是單一屬性的,不可再分。這個單一屬性由基本類型構成,包括整型、實數、字符型、邏輯型、日期型等。
- 第二范式(2NF):數據庫表中不存在非關鍵字段對任一候選關鍵字段的部分函數依賴(部分函數依賴指的是存在組合關鍵字中的某些字段決定非關鍵字段的情況),也即所有非關鍵字段都完全依賴於任意一組候選關鍵字。
- 第三范式(3NF):在第二范式的基礎上,數據表中如果不存在非關鍵字段對任一候選關鍵字段的傳遞函數依賴則符合第三范式。所謂傳遞函數依賴,指的是如 果存在"A → B → C"的決定關系,則C傳遞函數依賴於A。因此,滿足第三范式的數據庫表應該不存在如下依賴關系: 關鍵字段 → 非關鍵字段 x → 非關鍵字段y
百萬級別或以上的數據如何刪除
關於索引:由於索引需要額外的維護成本,因為索引文件是單獨存在的文件,所以當我們對數據的增加,修改,刪除,都會產生額外的對索引文件的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執行效率。所以,在我們刪除數據庫百萬級別數據的時候,查詢MySQL官方手冊得知刪除數據的速度和創建的索引數量是成正比的。
- 所以我們想要刪除百萬數據的時候可以先刪除索引(此時大概耗時三分多鍾)
- 然后刪除其中無用數據(此過程需要不到兩分鍾)
- 刪除完成后重新創建索引(此時數據較少了)創建索引也非常快,約十分鍾左右。
- 與之前的直接刪除絕對是要快速很多,更別說萬一刪除中斷,一切刪除會回滾。那更是坑了。
參考與感謝:
https://zhuanlan.zhihu.com/p/29150809
https://juejin.im/post/5e3eb616f265da570d734dcb#heading-105
https://blog.csdn.net/yin767833376/article/details/81511377