MYSQL
內部模塊
連接器(JDBC、ODBC等) =>
[MYSQL 內部
[Connection Pool] (授權、線程復用、連接限制、內存檢測等)
=>
[SQL Interface] (DML、DDL、Views等) [Parser] (Query Translation、Object privilege) [Optimizer] (Access Paths、 統計分析) [Caches & Buffers]
=>
[Pluggable Storage Engines]
]
=> [File]
一條SQL執行過程
先看看一條查詢SQL
- (這里提供一下官方對各存儲引擎的文檔說明 Mysql存儲引擎)
一條 update SQL執行
update的執行 從客戶端 => ··· => 執行引擎
是一樣的流程,都要先查到這條數據,然后再去更新。要想理解 UPDATE
流程我們先來看看,Innodb的架構模型。
Innodb 架構
上一張 MYSQL 官方InnoDB架構圖:
內存結構
這里有個關鍵點,當我們去查詢數據時候會先 拿著我們當前查詢的 頁
去 緩沖池
中查詢 當前頁
是否在緩沖池
中。如果在,則直接獲取。
當是update
時,則會直接修改 Buffer
中的值。這個時候,緩沖池中的數據就和磁盤中存儲的數據不一致
了,稱為臟頁
。每隔一段時間,Innodb存儲引擎就會把臟頁數據
刷入磁盤。
一般來說當更新一條數據,我們需要將數據給讀取到buffer
中修改,然后寫回磁盤,其中有一次 IO
操作。
圖中緩沖池中有一塊區域叫做:change buffer
。
當更新一個沒有 unique index
的數據時,直接將修改的數據放到 change buffer
,然后通過 merge
操作完成更新,從而減少了 IO
操作。
- 為什么要沒有唯一索引的數據更新時才能這樣呢,因為我們更新數據后,可能更新后的數據和已經存在的數據有重復,所以必須從磁盤中
把所有數據讀出來比對
才行。 - 所以當我們的數據是
寫多讀少
的時候,就可以通過 增加innodb_change_buffer_max_size
來調整change buffer
在buffer pool
中所占的比例,默認25(即:25%)
問題又來了,merge是如何運作的
有四種情況:
- 有其他訪問,訪問到了當前頁的數據,就會合并到磁盤
- 后臺線程定時
- 系統正常shut down之前
-
redo log
寫滿的時候
一、redo log是什么
談到redo,就要談到innodb的 crash safe
,使用 WAL 的方式實現(write Ahead Logging,在寫之前先記錄日志)
這樣就可以在,當數據庫崩潰的后,直接從 redo log
中恢復數據,保證數據的正確性
- redo log 默認存儲在兩個文件中
ib_logfile0
ib_logfile1
,這兩個文件都是固定大小的
。為什么需要固定大小?這是因為redo log
的順序讀取
的特性造成的,必須是連續的存儲空間
二、隨機讀寫與順序讀寫
看一張圖
一般我們的數據都是分散在磁盤上的:
機械硬盤:
- 定位到磁道
- 等待旋轉到對應扇區
- 開始讀寫
固態;
- 直接定位到閃存芯片(這也是為啥固態比機械快)
- 開始讀寫
而我們去存儲時,是通過文件系統
與磁盤打交道的,而他們打交道的方式就有兩個。隨機讀寫
和順序讀寫
- 隨機讀寫存儲的數據是分布在不同的
塊
(默認 1block=8扇區=4K) - 而順序存儲,顧名思義,數據是分布在
一串連續的塊
中,這樣讀取速度就大大提升了
三、回到我們架構圖
看到buffer pool
中的Log Buffer
,其就是用來寫 redo log 之前存在的緩沖區
在這里,redo log具體的執行策略有三種:
- 不用寫
Log Buffer
,只需要每秒寫redo log 磁盤數據一次,性能高,但會造成數據 1s 內的一致性問題。適用于強實時性
,弱一致性
,比如評論區評論
- 寫
Log Buffer
,同時寫入磁盤,性能最差,一致性最高。 適用于弱實時性
,強一致性
,比如支付場景
- 寫
Log Buffer
,同時寫到os buffer
(其會每秒調用fsync
將數據刷入磁盤),性能好,安全性也高。這個是實時性適中
一致性適中
的,比如訂單類
。
我們通過innodb_flush_log_at_trx_commit
就可以設置執行策略。默認為 1
內存結構小結
- Buffer Pool 用于加速讀
- Change Buffer 用于沒有非唯一索引的加速寫
- Log Buffer 用于加速redo log寫
-
自適應Hash索引
主要用于加快查詢頁
。在查詢時,Innodb通過監視索引搜索的機制來判斷當前查詢是否能走Hash索引
。比如LIKE運算符和% 通配符就不能走。
硬盤結構
一、System Tablespace
存儲在一個叫ibdata1
的文件中,其中包含:
- InnoDB Data Dictionary,存儲了元數據,比如表結構信息、索引等
- Doublewrite Buffer 當
Buffer Pool
寫入數據頁時,不是直接寫入到文件,而是先寫入到這個區域。這樣做的好處的是,一但操作系統,文件系統或者mysql掛掉,可以直接從這個Buffer
中獲取數據。 - Change Buffer 當Mysql shut down的時候,修改就會被存儲在磁盤這里
- Undo Logs 記錄事務修改操作
二、File-Per-Table Tablespaces
每一張表都有一張 .ibd
的文件,存儲數據和索引。
- 有了
每表文件表空間
可以使得ALTER TABLE
與TRUNCATE TABLE
性能得到很好的提升。比如ALTER TABLE
,相較于對駐留在共享表空間中的表,在修改表時,會進行表復制操作
,這可能會增加表空間占用的磁盤空間量
。此類操作可能需要與表中的數據以及索引一樣多的額外空間。該空間不會像每表文件表空間
那樣釋放回操作系統。 - 可以在單獨的存儲設備上創建每表文件表空間數據文件,以進行I / O優化,空間管理或備份。這就意味著表數據與結構容易在不同數據庫中遷移。
- 當發生數據損壞,備份或二進制日志不可用或無法重新啟動MySQL服務器實例時,存儲在單個表空間數據文件中的表可以節省時間并提高成功恢復的機會。
當然有優點就有缺陷:
- 存儲空間的利用率低,會存在碎片,在
Drop table
的時候會影響性能(除非你自己管理了碎片) - 因為每個表分成各自的表文件,操作系統不能同時進行
fsync
一次性刷入數據到文件中 - mysqld會持續保持每個表文件的
文件句柄
, 以提供維持對文件的持續訪問
三、General Tablespaces
- 通用表空間又叫
共享表空間
,他可以存儲多個表
的數據 - 如果存儲相同數量的表,消耗的存儲比
每表表空間
小
- 在MySQL 5.7.24中棄用了將表分區放置在常規表空間中的支持,并且在將來的MySQL版本中將不再支持。
四、Temporary Tablespaces
存儲在一個叫 ibtmp1
的文件中。正常情況下Mysql啟動的時候會創建臨時表空間,停止的時候會刪除臨時表空間。并且它能夠自動擴容。
五、Undo Tablespaces
- 提供修改操作的
原子性
,即當修改到一半,出現異常,可以通過Undo 日志回滾。 - 它存儲了,事務開始前的原始數據與這次的修改操作。
- Undo log 存在于回滾段(rollback segment)中,回滾段又存在
系統表空間``撤銷表空間``臨時表空間
中,如架構圖所示。
Redo Log
前面已經介紹過
總結一下,我們執行一句update SQL 會發生什么
- 查詢到我們要修改的那條數據,我們這里稱做
origin
,返給執行器 - 在執行器中,修改數據,稱為
modification
- 將
modification
刷入內存,Buffer Pool
的Change Buffer
- 引擎層:記錄undo log (實現事務原子性)
- 引擎層:記錄redo log (崩潰恢復使用)
- 服務層:記錄bin log(記錄DDL)
- 返回更新成功結果
- 數據等待被工作線程刷入磁盤
Bin log
說了 Undo
、Redo
也順便說一下Bin log
.
- 這一個log和
innodb
引擎沒有多大關系,我們前面說的那兩種日志,都在是innodb引擎層的。而Bin log
是處于服務層
的。所以他能被各個引擎所通用 - 他的主要作用是什么呢?首先,
Bin log
是以事件的形式,記錄了各個DDL DML
語句,它是一種邏輯意義上的日志。 - 能夠實現
主從復制
,從
服務器拿到主
服務器的bin log
日志,然后執行。 - 做
數據恢復
,拿到某個時間段的日志,重新執行一遍。
華麗的分割線
索引篇
跟隨一條SQL語句完成全局預覽后,我們來看看回過頭來讓SQL變得更加豐富,添加一個索引
試試
Innodb中將文件存儲分為了四個級別
Pages, Extents, Segments, and Tablespaces
它們的關系是:
- 默認的
extent
大小為1M
即64
個16KB
的Page
。平常我們文件系統所說的頁大小是4KB
,包含8
個512Byte
的扇區。
插入數據
如果我們在一個有序的字段上,建立索引,然后插入數據。
在存儲的時候,innodb就會按著順序一個個存儲到 頁
上,存滿一個頁再去申請新的頁,然后接著存。
但如果我們的字段是無序的,存儲的位置就會在不同的頁上。當我們的數據存儲到一個已經被 存滿
的頁
上時,就會造成頁分裂
,從而形成碎片
。
存儲結構 B樹變體 B+樹
幾種不同的索引組織形式
- 聚簇索引,如上面
B+樹
圖所示,子節點上存儲行數據
,并且索引的排列的順序
和索引鍵值順序
一致的話就是聚簇索引
。主鍵索引就是聚簇索引,除了主鍵索引,其他所以都是輔助索引
- 輔助索引,如果我們創建了一個
輔助索引
,它的葉子節點上只存儲自己的值
和主鍵索引的值
。這就意味著,如果我們通過輔助索引查詢所有數據,就會先去查找輔助索引
中的主鍵鍵值
,然后再去主鍵索引
里面,查到相關數據
。這個過程稱為回表
-
rowid
如果沒有主鍵索引
怎么辦呢?- 沒有主鍵,但是有一個 Unique key 而且都不是 null的,則會根據這個 key來創建
聚簇索引
。 - 那上面兩種都沒有呢,別擔心,innodb自己維護了一個叫
rowid
的東西,根據這個id來創建聚簇索引
- 沒有主鍵,但是有一個 Unique key 而且都不是 null的,則會根據這個 key來創建
索引如何起作用
搞清楚什么是索引,結構是什么之后。
我們來看看,什么時候我們要用到索引,理解了這些能更好的幫助我們創建正確高效的索引
離散度低不建索引,也就是數據之間相差不大的就沒必要建立索引。(因為建立索引,在查詢的時候,innodb大多數據都是相同的,我走索引 和全表沒什么差別就會直接
全表查詢
)。比如 性別字段。這樣反而浪費了大量的存儲空間。-
聯合字段索引,比如
idx(name, class_name)
- 當執行
select * from stu where class_name = xx and name = lzw
查詢時,也能走idx
這個索引的,因為優化器將SQL優化為了name = lzw and class_name = xx
- 當需要有
select ··· where name = lzw
的時候,不需要創建一個單獨的name
索引,會直接走idx
這個索引 -
覆蓋索引
。如果我們此次查詢的所有數據
全都包含在索引里面了,就不需要再回表
去查詢了。比如:select class_name from stu where name =lzw
- 當執行
-
索引條件下推(index_condition_pushdown)
- 有這樣一條SQL,
select * from stu where name = lzw and class_name like '%xx'
- 如果沒有
索引條件下推
,因為后面是like '%xx'
的查詢條件,所以這里首先根據name
走idx聯合索引
查詢到幾條數據后,再回表
查詢到全量row數據
,然后在server層
進行 like 過濾找到數據 - 如果有,則直接在
引擎層
對like也進行過濾了,相當于把server層
這個過濾操作下推到引擎層
了。如圖所示:
- 有這樣一條SQL,
建立索引注意事項
- 在where、order、join的on 使用次數多的時候,加上索引
- 離散度高的字段才能建立索引
- 聯合索引把離散度高的放前面(因為首先根據第一個字段匹配,能迅速定位數據位置。)
- 頻繁更新的字段不能建索引(造成
頁分裂
,索引按順序存儲,如果存儲頁滿了,再去插入就會造成頁分裂) - 使用比如replace、sum、count等
函數
的時候不會使用索引,所以沒必要額外建 - 出現隱式轉化的時候,比如字符串轉int,也用不到索引
- 特別長的字段,可以截取前面幾位創建索引(可以通過
select count(distinct left(name, 10))/count(*)
來看離散度,決定到底提取前幾位)
- tips: 執行一個SQL,不能確切說他是否能不能用到索引,畢竟這一切都是
優化器決定的
。比如你使用了Cost Base Optimizer
基于開銷的優化器,那種開銷小就用哪種優化。
又一個華麗的分割線
鎖篇
四大特性
- 原子性(通過Undo log實現)
- 一致性
- 隔離性
- 持久性(崩潰恢復,Redo log + double write 實現)
讀一致性問題應該由數據庫的事務隔離級別來解決 (SQL92 標準)
前提,在一個事務中:
- 臟讀(讀到了別人還沒有commit的數據,然后別人又回滾掉了)
- 不可重復讀(第一次讀取到了數據,然后別人修改commit了,再次去讀取就讀到了別人已經commit的數據)
- 幻讀(在范圍查詢的時候,讀到別人新添加的數據)
SQL92 標準規定:
(并發度從左到右,依次降低)
是否存在 | Read Uncommited | Read Committed | Repeatable Read | Serializable |
臟讀 | ?? | × | × | × |
不可重復度 | ?? | ?? | × | × |
幻讀 | ?? | ?? | ??(Innodb中×) | × |
- tips: Innodb中,Repeatable Read的幻讀,也不可能存在,是因為它自己解決了
Innodb中如何解決 可重復讀(RR) 中產生幻讀的情況
鎖模型
- LBCC (Lock Based Concurrency Control) 讀之前加個鎖,但這樣可能會導致性能問題 => 讀的時候加鎖導致其他事務都不能讀寫了,性能低下
- MVCC(Multi Version Concurrency Control) 讀的時候記錄當時快照,別人來讀取快照就行 => 性能消耗,存儲消耗
這兩種方案在Innodb中結合使用。這里簡要說明一下 RR 的 MVCC實現
,圖中 回滾id 初始值不應該為0而是NULL,這里為了方便寫成0
-
RC的MVCC實現是對 同一個事務的多個讀 創建一個版本
而RR 是 同一個事務任何一條都創建一個版本
通過MVCC
與LBCC
的結合,InnoDB能解決對于不加鎖
條件下的 幻讀的情況。而不必像 Serializable
一樣,必須讓事務串行
進行,無任何并發
。
下面我們來深入研究一下InnoDB鎖
是如何實現 RR
事務隔離級別的
鎖深入 MVCC在Innodb的實現
一、Innodb 的鎖
- Shared and Exclusive Locks 共享和排它鎖 =>(S、X)
- Intention Locks 意向鎖 => 這里指的是兩把鎖,其實就是
表級別
的 共享和排它鎖 => (IS、IX)
上面這四把鎖
是最基本鎖的類型
- Record Locks 記錄鎖
- Gap Locks 間隙鎖
- Next-key Locks 臨鎖
這三把鎖,理解成對于上面四把鎖
實現的三種算法方式,我們這里暫且把它們稱為:高階鎖
- Insert Intention Locks 插入鎖
- AUTO-INC Locks 自增鍵鎖
- Predicate Locks for Spatial Indexes 專用于給Spatial Indexes用的
上面三把是額外擴展的鎖
二、讀寫鎖深入解釋
- 要使用共享鎖,在語句后面加上
lock in share mode
。排它鎖默認Insert、Update、Delete
會使用。顯示使用在語句后加for update
。 - 意向鎖都是由數據庫自己維護的。(主要作用是給表
打一個標記
,記錄這個表是否被鎖住了) => 如果沒有這個鎖,別的事務想鎖住這張表的時候,就要去全表掃描是否有鎖,效率太低。所以才會有意向鎖的存在。
補充:Mysql中鎖,到底鎖的是什么
鎖的是索引,那么這個時候可能有人要問了:那如果我不創建索引呢?
索引的存在,我們上面講過了,這里再回顧一下,有下面幾種情況
- 你建了一個 Primary key, 就是聚集索引 (存儲的是
完整的數據
) - 沒有主鍵,但是有一個 Unique key 而是都不是 null的,則會根據這個 key來創建
聚簇索引
- 那上面兩種都沒有呢,別擔心,innodb自己維護了一個叫
rowid
的東西,根據這個id來創建聚簇索引
所以一個表里面,必然會存在一個索引,所以鎖當然總有索引拿來鎖住了。
當要給一張你沒有顯示創建索引
的表,進行加鎖查詢
時,數據庫其實是不知道到底要查哪些數據的,整張表可能都會用到。所以索性就鎖整張表
。
- 如果是給
輔助索引
加寫鎖,比如select * from where name = ’xxx‘ for update
最后要回表
查主鍵上的信息,所以這個時候除了鎖輔助索引
還要鎖主鍵索引
三、高階鎖深入解釋
首先上三個概念,有這么一組數據:主鍵是 1,3,6,9
在存儲時候有如下:x 1 x 3 x x 6 x x x 9 x···
記錄鎖,鎖的是每個記錄,也就是 1,3,6,9
間隙鎖,鎖的是記錄間隙,每個 x
,(-∞,1), (1,3), (3,6), (6,9), (9,+∞)
臨鎖,鎖的是 (-∞,1], (1,3], (3,6], (6,9], (9,+∞] 左開右閉的區間
首先這三種鎖都是 排它鎖
, 并且 臨鍵鎖 = 記錄鎖 + 間隙鎖
- 當
select * from xxx where id = 3 for update
時,產生記錄鎖 - 當
select * from xxx where id = 5 for update
時,產生間隙鎖 => 鎖住了(3,6),這里要格外注意一點:間隙鎖之間是不沖突的。 - 當
select * from xxx where id = 5 for update
時,產生臨鍵鎖 => 鎖住了(3,6], mysql默認使用臨鍵鎖,如果不滿足 1 ,2 情況 則他的行鎖的都是臨鍵鎖
- 回到開始的問題,在這里
Record Lock 行鎖
防止別的事務修改或刪除,Gap Lock 間隙鎖
防止別的事務新增,Gap Lock 和 Record Lock
結合形成的Next-Key鎖
共同解決RR級別
在寫數據時的幻讀問題。
說到了鎖那么必然逃不過要說一下死鎖
發生死鎖后的檢查
-
show status like 'innodb_row_lock_%'
- Innodb_row_lock_current_waits 當前正在有多少等待鎖
- Innodb_row_lock_time 一共等待了多少時間
- Innodb_row_lock_time_avg 平均等多少時間
- Innodb_row_lock_time_max 最大等多久
- Innodb_row_lock_waits 一共出現過多少次等待
-
select * from information_schema.INNODB_TRX
能查看到當前正在運行和被鎖住的事務 -
show full processlist
=select * from information_schema.processlist
能查詢出是哪個用戶
在哪臺機器host的哪個端口上
連接哪個數據庫
執行什么指令
的狀態與時間
死鎖預防
- 保證訪問數據的順序
- 避免where的時候不用索引(這樣會鎖表,不僅死鎖更容易產生,而且性能更加低下)
- 一個非常大的事務,拆成多個小的事務
- 盡量使用等值查詢(就算用范圍查詢也要限定一個區間,而不要只開不閉,比如 id > 1 就鎖住后面所有)
分庫分表
動態選擇數據源
編碼層 -- 實現 AbstracRoutingDataSource
=>
框架層 -- 實現 Mybatis Plugin
=>
驅動層 -- Sharding-JDBC(配置多個數據源,根據自定義實現的策略對數據進行分庫分表存儲)核心流程,SQL解析=>執行優化=>SQL數據庫路由=>SQL改變(比如分表,改表名)=>SQL執行=>結果歸并)
=>
代理層 -- Mycat(將所有與數據庫的連接獨立出來。全部由Mycat連接,其他服務訪問Mycat獲取數據)
=>
服務層 -- 特殊的SQL版本
MYSQL如何做優化
說到底我們學習這么多知識都是為了能更好使用MYSQL,那就讓我們來實操一下,建立一個完整的優化體系
要想獲得更好的查詢性能,可以從這張查詢執行過程
入手
一、客戶端連接池
添加連接池,避免每次都新建、銷毀連接
那我們的連接池是不是越多越好呢?
有興趣的盆友可以看看這篇文章:About Pool Sizing
我大概總結一下:
- 我們并發的執行SQL,并不會因為連接數量增多而變快。為什么呢?如果我有10000連接同時并發執行,難道不比你10個連接執行快得多嗎? 答案是否定的,不僅不快反而越來越慢。
- 在計算機中,我們都知道只有
CPU
才能真正去執行線程
。而操作系統因為用時間分片
的技術,讓我們以為一個CPU內核
執行了多個線程
。 - 但其實上一個
CPU
在某個時間段
只能執行一個線程
,所以無論我們怎么增加并發,CPU
還是只能在這個時間段里處理這么多數據。 - 那就算
CPU
處理不了這么多數據,又怎么會變慢?因為時間分片
,當多個線程看起來在"同時執行"
,其實他們之間的上下文切換
十分耗時 - 所以,一旦線程的數量超過了CPU核心的數量,再增加線程數系統就只會更慢,而不是更快。
- 在計算機中,我們都知道只有
- 當然,這只是其中最核心的原因,磁盤同樣也會對速度有影響,同時也對我們連接數配置有影響。
- 比如我們用的機械硬盤,我們要通過旋轉,尋址到某個位置,再進行
I/O
操作,這個時候,CPU
就可以把時間,分片給其他線程
,以提升處理效率和速度 - 所以,如果你用的是機械硬盤,我們通常可以多添加一些連接數,保持高并發
- 但如果你用的是 SSD 呢,因為
I/O
等待時間非常短,所以我們就不能添加過多連接數
- 比如我們用的機械硬盤,我們要通過旋轉,尋址到某個位置,再進行
- 通過來說你需要遵循這么一個公式:
線程數 = ((核心數 * 2) + 有效磁盤數)
。比如一臺i7 4core 1hard disk
的機器,就是 4 * 2 + 1 = 9 - 看到這個公式不知道大家是不是很眼熟,這不僅適用于數據庫連接,也適用于任何
很多CPU計算和I/O的場景
比如:設置最大線程數等
二、數據庫整體設計方案
第三方緩存
如果并發非常大,就不能讓他們全打到數據庫上,在客戶端連接數據庫查詢時,添加如Redis
這種三方緩存
集群方式部署數據庫
既然我們一個數據庫承受不了巨大的并發,那為什么不多添加幾臺機器呢?
主從復制原理圖
從圖中我們不難看出、Mysql主從復制 讀寫分離
異步復制
的特性。
- tips: 在把
Binary Log
寫入relay log
之后,slave
都會把最新讀取到的Binary Log Position
記錄到master info
上,下一次就直接從這個位置去取。
不同方式的主從復制
上面這種異步
的主從復制,很明顯的一個問題就是,更新不及時的問題。當寫入一個數據后,馬上有用戶讀取,讀取的還是之前的數據,也就是存在著延時。
要解決延時的問題,就需要引入 事務
- 全同步復制,事務方式執行,主節點先寫入,然后讓所有slave寫,必須要所有 從節點 把數據寫完,才返回寫成功,這樣的話會大大影響寫入的性能
- 半同步復制,只要有一個salve寫入數據,就算成功。(如果需要半同步復制,主從節點都需要安裝semisync_mater.so和 semisync_slave.so插件)
- GTID(global transaction identities)復制,主庫并行復制的時候,從庫也并行復制,解決主從同步復制延遲,實現自動的
failover
動作,即主節點掛掉,選舉從節點后,能快速自動避免數據丟失。
集群高可用方案
- 主從 HAPrxoy + keeplive
- NDB
- Glaera Cluster for MySQL
- MHA(Master-Mater replication manager for MySQL),MMM(MySQL Master High Available)
- MGR(MySQL Group Replication) => MySQL Cluster
分表
對數據進行分類劃分,分成不同表,減少對單一表造成過多鎖操作
影響性能
表結構
- 設計合理字段類型
- 設計合理字段長度
三、優化器與執行引擎
慢日志
開啟show_query_log
,執行時間超過變量long_query_time
的SQL會被記錄下來。
可以使用mysqldumpslow /var/lib/mysql/mysql-slow.log
,還有很多插件可以提供比這個更優雅的分析,這里就不詳細講了。
explain分析SQL
任何SQL在寫完之后都應該explain
一下
1. 驅動表 - 比如濫用left/right join
導致性能低下
- 使用
left/right join
會直接指定驅動表,在MYSQL中,默認使用Nest loop join
進行表關聯(即通過驅動表
的結果集作為循環基礎數據,然后通過此集合中的每一條數據篩選下一個關聯表的數據,最后合并結果,得出我們常說的臨時表
)。 - 如果
驅動表
的數據是百萬千萬
級別的,可想而知這聯表查詢得有多慢。但是反過來,如果以小表
作為驅動表
,借助千萬級表
的索引
查詢就能變得很快。 - 如果你不確定到底該用誰來作為
驅動表
,那么請交給優化器來決定,比如:select xxx from table1, table2, table3 where ···
,優化器會將查詢記錄行數少的表作為驅動表。 - 如果你就是想自己指定
驅動表
,那么請拿好Explain
武器,在Explain
的結果中,第一個就是基礎驅動表
- 排序。同樣的,對不同
表
排序也是有很大的性能差異,我們盡量對驅動表
進行排序,而不要對臨時表,也就是合并后的結果集
進行排序。即執行計劃中出現了using temporary
,就需要進行優化。
2. 執行計劃各參數含義
- select_type(查詢的類型):
普通查詢
和復雜查詢
(聯合查詢、子查詢等)-
SIMPLE
,查詢不包含子查詢或者UNION -
PRIMARY
,如果查詢包含復雜查詢
的子結構,那么就需要用到主鍵查詢 -
SUBQUERY
,在select
或者where
中包含子查詢
-
DERIVED
,在from
中包含子查詢 -
UNION RESULT
,從union
表查詢子查詢
-
- table 使用到的表名
- type(訪問類型),找到所需行的方式,從上往下,查詢速度
越來越快
-
const或者system
常量級別的掃描,查詢表最快的一種,system是const的一種特殊情況(表中只有一條數據) -
eq_ref
唯一性索引掃描 -
ref
非唯一性索引掃描 -
range
索引的范圍掃描,比如 between、<、>等范圍查詢 -
index
(index full)掃描全部索引樹 -
ALL
掃描全表 -
NULL
,不需要訪問表或者索引
-
- possible_keys,給出使用哪個索引能找到表中的記錄。這里被列出的索引
不一定使用
- key:到底
哪一個索引被真正使用
到了。如果沒有則為NULL - key_len:使用的索引所占用的字節數
- ref:哪個字段或者常數和
索引(key)
一起被使用 - rows:一共掃描了多少行
- filtered(百分比):有多少數據在server層還進行了過濾
- Extra:額外信息
-
only index
信息只需要從索引中查出,可能用到了覆蓋索引,查詢非常快 -
using where
如果查詢沒有使用索引,這里會在server
層過濾再使用where
來過濾結果集 -
impossible where
啥也沒查出來 -
using filesort
,只要沒有通過索引來排序,而是使用了其他排序的方式就是 filesort -
using temporary
(需要通過臨時表來對結果集進行暫時存儲,然后再進行計算。)一般來說這種情況都是進行了DISTINCT、排序、分組
-
using index condition
索引下推,上文講過,就是把server層
這個過濾操作下推到引擎層
-
四、存儲引擎
- 當僅僅是
插入與查詢
比較多的時候,可以使用MyISAM
存儲引擎 - 當只是使用臨時數據,可以使用
memory
- 當
插入、更新、查詢
等并發數很多時,可以使用InnoDB
總結
從五個層次回答MYSQL優化,由上至下
- SQL與索引
- 存儲引擎與表結構
- 數據庫架構
- MySQL配置
- 硬件與操作系統
除此之外,查數據慢,要不僅僅拘留于一味的 "優化" 數據庫,而是要從業務應用層面去分析。比如對數據進行緩存,對請求進行限流等。
我們下篇文章見