一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

MYSQL

內部模塊

連接器(JDBC、ODBC等) =>

[MYSQL 內部

<code>[Connection Pool] (授權、線程複用、連接限制、內存檢測等)
=>

[SQL Interface] (DML、DDL、Views等) [Parser] (Query Translation、Object privilege) [Optimizer] (Access Paths、 統計分析) [Caches & Buffers]
=>

[Pluggable Storage Engines]
複製代碼/<code>

]

=> [File]

一條SQL執行過程

先看看一條查詢SQL


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

  • (這裡提供一下官方對各存儲引擎的文檔說明 Mysql存儲引擎)

一條 update SQL執行

update的執行 從客戶端 => ··· => 執行引擎 是一樣的流程,都要先查到這條數據,然後再去更新。要想理解 UPDATE 流程我們先來看看,Innodb的架構模型。

Innodb 架構

上一張 MYSQL 官方InnoDB架構圖:


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

內存結構

這裡有個關鍵點,當我們去查詢數據時候會先 拿著我們當前查詢的 夜 去 緩衝池 中查詢 當前頁是否在緩衝池中。如果在,則直接獲取。

當是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是如何運作的

有四種情況:

  1. 有其他訪問,訪問到了當前頁的數據,就會合併到磁盤
  2. 後臺線程定時
  3. 系統正常shut down之前
  4. redo log寫滿的時候

一、redo log是什麼

談到redo,就要談到innodb的 crash safe,使用 WAL 的方式實現(write Ahead Logging,在寫之前先記錄日誌)

這樣就可以在,當數據庫崩潰的後,直接從 redo log中恢復數據,保證數據的正確性

  • redo log 默認存儲在兩個文件中 ib_logfile0 ib_logfile1,這兩個文件都是固定大小的。為什麼需要固定大小?這是因為redo log的順序讀取的特性造成的,必須是連續的存儲空間

二、隨機讀寫與順序讀寫

看一張圖


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

一般我們的數據都是分散在磁盤上的: 機械硬盤:

  1. 定位到磁道
  2. 等待旋轉到對應扇區
  3. 開始讀寫

固態;

  1. 直接定位到閃存芯片(這也是為啥固態比機械快)
  2. 開始讀寫

而我們去存儲時,是通過文件系統與磁盤打交道的,而他們打交道的方式就有兩個。隨機讀寫和順序讀寫

  1. 隨機讀寫存儲的數據是分佈在不同的 塊(默認 1block=8扇區=4K)
  2. 而順序存儲,顧名思義,數據是分佈在一串連續的塊中,這樣讀取速度就大大提升了

三、回到我們架構圖


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

看到buffer pool中的Log Buffer,其就是用來寫 redo log 之前存在的緩衝區

在這裡,redo log具體的執行策略有三種:

  1. 不用寫Log Buffer,只需要每秒寫redo log 磁盤數據一次,性能高,但會造成數據 1s 內的一致性問題。適用於強實時性,弱一致性,比如評論區評論
  2. 寫Log Buffer,同時寫入磁盤,性能最差,一致性最高。 適用於弱實時性,強一致性,比如支付場景
  3. 寫Log Buffer,同時寫到os buffer(其會每秒調用 fsync 將數據刷入磁盤),性能好,安全性也高。這個是實時性適中 一致性適中的,比如訂單類。

我們通過innodb_flush_log_at_trx_commit就可以設置執行策略。默認為 1

內存結構小結


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

  1. Buffer Pool 用於加速讀
  2. Change Buffer 用於沒有非唯一索引的加速寫
  3. Log Buffer 用於加速redo log寫
  4. 自適應Hash索引主要用於加快查詢 頁。在查詢時,Innodb通過監視索引搜索的機制來判斷當前查詢是否能走Hash索引。比如LIKE運算符和% 通配符就不能走。

硬盤結構

一、System Tablespace

存儲在一個叫ibdata1的文件中,其中包含:

  1. InnoDB Data Dictionary,存儲了元數據,比如表結構信息、索引等
  2. Doublewrite Buffer 當Buffer Pool寫入數據頁時,不是直接寫入到文件,而是先寫入到這個區域。這樣做的好處的是,一但操作系統,文件系統或者mysql掛掉,可以直接從這個Buffer中獲取數據。
  3. Change Buffer 當Mysql shut down的時候,修改就會被存儲在磁盤這裡
  4. Undo Logs 記錄事務修改操作

二、File-Per-Table Tablespaces

每一張表都有一張 .ibd 的文件,存儲數據和索引。

  1. 有了每表文件表空間可以使得 ALTER TABLE與 TRUNCATE TABLE 性能得到很好的提升。比如 ALTER TABLE,相較於對駐留在共享表空間中的表,在修改表時,會進行表複製操作,這可能會增加表空間佔用的磁盤空間量。此類操作可能需要與表中的數據以及索引一樣多的額外空間。該空間不會像每表文件表空間那樣釋放回操作系統。
  2. 可以在單獨的存儲設備上創建每表文件表空間數據文件,以進行I / O優化,空間管理或備份。這就意味著表數據與結構容易在不同數據庫中遷移。
  3. 當發生數據損壞,備份或二進制日誌不可用或無法重新啟動MySQL服務器實例時,存儲在單個表空間數據文件中的表可以節省時間並提高成功恢復的機會。

當然有優點就有缺陷:

  1. 存儲空間的利用率低,會存在碎片,在Drop table的時候會影響性能(除非你自己管理了碎片)
  2. 因為每個表分成各自的表文件,操作系統不能同時進行fsync一次性刷入數據到文件中
  3. mysqld會持續保持每個表文件的 文件句柄, 以提供維持對文件的持續訪問

三、General Tablespaces

  1. 通用表空間又叫共享表空間,他可以存儲多個表的數據
  2. 如果存儲相同數量的表,消耗的存儲比 每表表空間 小
  3. 在MySQL 5.7.24中棄用了將表分區放置在常規表空間中的支持,並且在將來的MySQL版本中將不再支持。

四、Temporary Tablespaces

存儲在一個叫 ibtmp1 的文件中。正常情況下Mysql啟動的時候會創建臨時表空間,停止的時候會刪除臨時表空間。並且它能夠自動擴容。

五、Undo Tablespaces

  1. 提供修改操作的 原子性,即當修改到一半,出現異常,可以通過Undo 日誌回滾。
  2. 它存儲了,事務開始前的原始數據與這次的修改操作。
  3. Undo log 存在於回滾段(rollback segment)中,回滾段又存在系統表空間``撤銷表空間``臨時表空間中,如架構圖所示。

Redo Log

前面已經介紹過

總結一下,我們執行一句update SQL 會發生什麼

  1. 查詢到我們要修改的那條數據,我們這裡稱做 origin,返給執行器
  2. 在執行器中,修改數據,稱為 modification
  3. 將modification刷入內存,Buffer Pool的 Change Buffer
  4. 引擎層:記錄undo log (實現事務原子性)
  5. 引擎層:記錄redo log (崩潰恢復使用)
  6. 服務層:記錄bin log(記錄DDL)
  7. 返回更新成功結果
  8. 數據等待被工作線程刷入磁盤


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

Bin log

說了 Undo、Redo也順便說一下Bin log.

  1. 這一個log和 innodb引擎沒有多大關係,我們前面說的那兩種日誌,都在是innodb引擎層的。而Bin log是處於服務層的。所以他能被各個引擎所通用
  2. 他的主要作用是什麼呢?首先,Bin log 是以事件的形式,記錄了各個 DDL DML 語句,它是一種邏輯意義上的日誌。
  3. 能夠實現主從複製, 從服務器拿到主服務器的bin log日誌,然後執行。
  4. 做數據恢復,拿到某個時間段的日誌,重新執行一遍。

華麗的分割線

索引篇

跟隨一條SQL語句完成全局預覽後,我們來看看回過頭來讓SQL變得更加豐富,添加一個索引試試

Innodb中將文件存儲分為了四個級別

Pages, Extents, Segments, and Tablespaces

它們的關係是:

一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

  • 默認的 extent 大小為 1M 即 64個 16KB的Page。平常我們文件系統所說的頁大小是 4KB,包含 8 個 512Byte的扇區。

插入數據

如果我們在一個有序的字段上,建立索引,然後插入數據。 在存儲的時候,innodb就會按照順序一個個存儲到 夜 上,存滿一個頁再去申請新的頁,然後接著存。

但如果我們的字段是無序的,存儲的位置就會在不同的頁上。當我們的數據存儲到一個已經被 存滿的頁上時,就會造成頁分裂,從而形成碎片。

存儲結構 B樹變體 B+樹


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

幾種不同的索引組織形式

  1. 聚簇索引,如上面B+樹圖所示,子節點上存儲行數據,並且索引的排列的順序和索引鍵值順序一致的話就是 聚簇索引。主鍵索引就是聚簇索引,除了主鍵索引,其他所以都是輔助索引
  2. 輔助索引,如果我們創建了一個輔助索引,它的葉子節點上只存儲自己的值和主鍵索引的值。這就意味著,如果我們通過輔助索引查詢所有數據,就會先去查找輔助索引中的主鍵鍵值,然後再去主鍵索引裡面,查到相關數據。這個過程稱為回表
  3. rowid 如果沒有主鍵索引怎麼辦呢? 沒有主鍵,但是有一個 Unique key 而且都不是 null的,則會根據這個 key來創建聚簇索引。 那上面兩種都沒有呢,別擔心,innodb自己維護了一個叫 rowid 的東西,根據這個id來創建 聚簇索引

索引如何起作用

搞清楚什麼是索引,結構是什麼之後。 我們來看看,什麼時候我們要用到索引,理解了這些能更好地幫助我們創建正確高效的索引

  1. 離散度低不建索引,也就是數據之間相差不大的就沒必要建立索引。(因為建立索引,在查詢的時候,innodb大多數據都是相同的,我走索引 和全表沒什麼差別就會直接全表查詢)。比如 性別字段。這樣反而浪費了大量的存儲空間。
  2. 聯合字段索引,比如 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
  3. 索引條件下推(index_condition_pushdown) 有這樣一條SQL,select * from stu where name = lzw and class_name like '%xx' 如果沒有索引條件下推,因為後面是 like '%xx'的查詢條件,所以這裡首先根據 name 走 idx聯合索引 查詢到幾條數據後,再回表查詢到全量row數據,然後在server層進行 like 過濾找到數據 如果有,則直接在引擎層對like也進行過濾了,相當於把server層這個過濾操作下推到引擎層了。如圖所示:


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

建立索引注意事項

  1. 在where、order、join的on 使用次數多的時候,加上索引
  2. 離散度高的字段才能建立索引
  3. 聯合索引把離散度高的放前面(因為首先根據第一個字段匹配,能迅速定位數據位置。)
  4. 頻繁更新的字段不能建索引(造成頁分裂,索引按順序存儲,如果存儲頁滿了,再去插入就會造成頁分裂)
  5. 使用比如replace、sum、count等函數的時候不會使用索引,所以沒必要額外建
  6. 出現隱式轉化的時候,比如字符串轉int,也用不到索引
  7. 特別長的字段,可以截取前面幾位創建索引(可以通過 select count(distinct left(name, 10))/count(*) 來看離散度,決定到底提取前幾位)
  • tips: 執行一個SQL,不能確切說他是否能不能用到索引,畢竟這一切都是 優化器決定的。比如你使用了 Cost Base Optimizer 基於開銷的優化器,那種開銷小就用哪種優化。

又一個華麗的分割線

鎖篇

四大特性

  1. 原子性(通過Undo log實現)
  2. 一致性
  3. 隔離性
  4. 持久性(崩潰恢復,Redo log + double write 實現)

讀一致性問題應該由數據庫的事務隔離級別來解決 (SQL92 標準)

前提,在一個事務中:

  1. 髒讀(讀到了別人還沒有commit的數據,然後別人又回滾掉了)
  2. 不可重複讀(第一次讀取到了數據,然後別人修改commit了,再次去讀取就讀到了別人已經commit的數據)
  3. 幻讀(在範圍查詢的時候,讀到別人新添加的數據)

SQL92 標準規定: (併發度從左到右,依次降低)

是否存在 | Read Uncommited | Read Committed | Repeatable Read | Serializable | 髒讀 | ✔️ | × | × | × | 不可重複度 | ✔️ | ✔️ | × | × | 幻讀 | ✔️ | ✔️ | ✔️(Innodb中×) | × |

  • tips: Innodb中,Repeatable Read的幻讀,也不可能存在,是因為它自己解決了

Innodb中如何解決 可重複讀(RR) 中產生幻讀的情況


鎖模型

  1. LBCC (Lock Based Concurrency Control) 讀之前加個鎖,但這樣可能會導致性能問題 => 讀的時候加鎖導致其他事務都不能讀寫了,性能低下
  2. MVCC(Multi Version Concurrency Control) 讀的時候記錄當時快照,別人來讀取快照就行 => 性能消耗,存儲消耗

這兩種方案在Innodb中結合使用。這裡簡要說明一下 RR 的 MVCC實現,圖中 回滾id 初始值不應該為0而是NULL,這裡為了方便寫成0


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

  • RC的MVCC實現是對 同一個事務的多個讀 創建一個版本 而 RR 是 同一個事務任何一條都創建一個版本

通過MVCC與LBCC的結合,InnoDB能解決對於不加鎖條件下的 幻讀的情況。而不必像 Serializable 一樣,必須讓事務串行進行,無任何併發。

下面我們來深入研究一下InnoDB鎖是如何實現 RR 事務隔離級別的

鎖深入 MVCC在Innodb的實現

一、Innodb 地鎖

  1. Shared and Exclusive Locks 共享和排它鎖 =>(S、X)
  2. Intention Locks 意向鎖 => 這裡指的是兩把鎖,其實就是表級別的 共享和排它鎖 => (IS、IX)

上面這四把鎖是最基本鎖的類型

  1. Record Locks 記錄鎖
  2. Gap Locks 間隙鎖
  3. Next-key Locks 臨鎖

這三把鎖,理解成對於上面四把鎖實現的三種算法方式,我們這裡暫且把它們稱為:高階鎖

  1. Insert Intention Locks 插入鎖
  2. AUTO-INC Locks 自增鍵鎖
  3. Predicate Locks for Spatial Indexes 專用於給Spatial Indexes用的

上面三把是額外擴展的鎖

二、讀寫鎖深入解釋

  1. 要使用共享鎖,在語句後面加上lock in share mode 。排它鎖默認 Insert、Update、Delete會使用。顯示使用在語句後加for update。
  2. 意向鎖都是由數據庫自己維護的。(主要作用是給表打一個標記,記錄這個表是否被鎖住了) => 如果沒有這個鎖,別的事務想鎖住這張表的時候,就要去全表掃描是否有鎖,效率太低。所以才會有意向鎖的存在。

補充:Mysql中鎖,到底鎖的是什麼

鎖的是索引,那麼這個時候可能有人要問了:那如果我不創建索引呢?

索引的存在,我們上面講過了,這裡再回顧一下,有下面幾種情況

  1. 你建了一個 Primary key, 就是聚集索引 (存儲的是 完整的數據)
  2. 沒有主鍵,但是有一個 Unique key 而是都不是 null的,則會根據這個 key來創建 聚簇索引
  3. 那上面兩種都沒有呢,別擔心,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,+∞] 左開右閉的區間

首先這三種鎖都是 排它鎖, 並且 臨鍵鎖 = 記錄鎖 + 間隙鎖

  1. 當 select * from xxx where id = 3 for update 時,產生記錄鎖
  2. 當 select * from xxx where id = 5 for update 時,產生間隙鎖 => 鎖住了(3,6),這裡要格外注意一點:間隙鎖之間是不衝突的。
  3. 當 select * from xxx where id = 5 for update 時,產生臨鍵鎖 => 鎖住了(3,6], mysql默認使用臨鍵鎖,如果不滿足 1 ,2 情況 則他的行鎖的都是臨鍵鎖
  • 回到開始的問題,在這裡 Record Lock 行鎖防止別的事務修改或刪除,Gap Lock 間隙鎖防止別的事務新增,Gap Lock 和 Record Lock結合形成的Next-Key鎖共同解決RR級別在寫數據時的幻讀問題。

說到了鎖那麼必然逃不過要說一下死鎖

發生死鎖後的檢查

  1. 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 一共出現過多少次等待
  2. select * from information_schema.INNODB_TRX 能查看到當前正在運行和被鎖住的事務
  3. show full processlist = select * from information_schema.processlist 能查詢出是 哪個用戶 在哪臺機器host的哪個端口上 連接哪個數據庫 執行什麼指令 的 狀態與時間

死鎖預防

  1. 保證訪問數據的順序
  2. 避免where的時候不用索引(這樣會鎖表,不僅死鎖更容易產生,而且性能更加低下)
  3. 一個非常大的事務,拆成多個小的事務
  4. 儘量使用等值查詢(就算用範圍查詢也要限定一個區間,而不要只開不閉,比如 id > 1 就鎖住後面所有)

分庫分表

動態選擇數據源

編碼層 -- 實現 AbstracRoutingDataSource => 框架層 -- 實現 Mybatis Plugin => 驅動層 -- Sharding-JDBC(配置多個數據源,根據自定義實現的策略對數據進行分庫分表存儲)核心流程,SQL解析=>執行優化=>SQL數據庫路由=>SQL改變(比如分表,改表名)=>SQL執行=>結果歸併) => 代理層 -- Mycat(將所有與數據庫的連接獨立出來。全部由Mycat連接,其他服務訪問Mycat獲取數據) => 服務層 -- 特殊的SQL版本

MYSQL如何做優化

說到底我們學習這麼多知識都是為了能更好使用MYSQL,那就讓我們來實操一下,建立一個完整的優化體系


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

要想獲得更好的查詢性能,可以從這張查詢執行過程入手

一、客戶端連接池

添加連接池,避免每次都新建、銷燬連接 那我們的連接池是不是越多越好呢? 有興趣的朋友可以看看這篇文章:About Pool Sizing

我大概總結一下:

  1. 我們併發的執行SQL,並不會因為連接數量增多而變快。為什麼呢?如果我有10000連接同時併發執行,難道不比你10個連接執行快得多嗎? 答案是否定的,不僅不快反而越來越慢。 在計算機中,我們都知道只有CPU才能真正去執行線程。而操作系統因為用時間分片的技術,讓我們以為一個CPU內核執行了多個線程。 但其實上一個CPU在某個時間段只能執行一個線程,所以無論我們怎麼增加併發,CPU還是隻能在這個時間段裡處理這麼多數據。 那就算CPU處理不了這麼多數據,又怎麼會變慢?因為時間分片,當多個線程看起來在"同時執行",其實他們之間的上下文切換十分耗時 所以,一旦線程的數量超過了CPU核心的數量,再增加線程數系統就只會更慢,而不是更快。
  2. 當然,這只是其中最核心的原因,磁盤同樣也會對速度有影響,同時也對我們連接數配置有影響。 比如我們用的機械硬盤,我們要通過旋轉,尋址到某個位置,再進行I/O操作,這個時候,CPU就可以把時間,分片給其他線程,以提升處理效率和速度 所以,如果你用的是機械硬盤,我們通常可以多添加一些連接數,保持高併發 但如果你用的是 SSD 呢,因為I/O等待時間非常短,所以我們就不能添加過多連接數
  3. 通過來說你需要遵循這麼一個公式:線程數 = ((核心數 * 2) + 有效磁盤數)。比如一臺 i7 4core 1hard disk的機器,就是 4 * 2 + 1 = 9
  4. 看到這個公式不知道大家是不是很眼熟,這不僅適用於數據庫連接,也適用於任何很多CPU計算和I/O的場景 比如:設置最大線程數等

二、數據庫整體設計方案

第三方緩存

如果併發非常大,就不能讓他們全打到數據庫上,在客戶端連接數據庫查詢時,添加如Redis這種三方緩存

集群方式部署數據庫

既然我們一個數據庫承受不了巨大的併發,那為什麼不多添加幾臺機器呢? 主從複製原理圖


一文讓你搞懂MYSQL底層原理。-內部結構、索引、鎖、集群

從圖中我們不難看出、Mysql主從複製 讀寫分離 異步複製的特性。

  • tips: 在把Binary Log寫入relay log之後,slave都會把最新讀取到的Binary Log Position記錄到master info上,下一次就直接從這個位置去取。

不同方式的主從複製

上面這種異步的主從複製,很明顯的一個問題就是,更新不及時的問題。當寫入一個數據後,馬上有用戶讀取,讀取的還是之前的數據,也就是存在著延時。 要解決延時的問題,就需要引入 事務

  1. 全同步複製,事務方式執行,主節點先寫入,然後讓所有slave寫,必須要所有 從節點 把數據寫完,才返回寫成功,這樣的話會大大影響寫入的性能
  2. 半同步複製,只要有一個salve寫入數據,就算成功。(如果需要半同步複製,主從節點都需要安裝semisync_mater.so和 semisync_slave.so插件)
  3. GTID(global transaction identities)複製,主庫並行複製的時候,從庫也並行複製,解決主從同步複製延遲,實現自動的failover動作,即主節點掛掉,選舉從節點後,能快速自動避免數據丟失。

集群高可用方案

  1. 主從 HAPrxoy + keeplive
  2. NDB
  3. Glaera Cluster for MySQL
  4. MHA(Master-Mater replication manager for MySQL),MMM(MySQL Master High Available)
  5. MGR(MySQL Group Replication) => MySQL Cluster

分表

對數據進行分類劃分,分成不同表,減少對單一表造成過多的操作影響性能

表結構

  1. 設計合理字段類型
  2. 設計合理字段長度

三、優化器與執行引擎

慢日誌

開啟show_query_log,執行時間超過變量long_query_time的SQL會被記錄下來。 可以使用mysqldumpslow /var/lib/mysql/mysql-slow.log,還有很多插件可以提供比這個更優雅的分析,這裡就不詳細講了。

explain分析SQL

任何SQL在寫完之後都應該explain一下

1. 驅動表 - 比如濫用left/right join導致性能低下

  1. 使用left/right join會直接指定驅動表,在MYSQL中,默認使用Nest loop join進行表關聯(即通過驅動表的結果集作為循環基礎數據,然後通過此集合中的每一條數據篩選下一個關聯表的數據,最後合併結果,得出我們常說的臨時表)。
  2. 如果驅動表的數據是 百萬千萬級別的,可想而知這聯表查詢得有多慢。但是反過來,如果以小表作為驅動表,藉助千萬級表的索引查詢就能變得很快。
  3. 如果你不確定到底該用誰來作為驅動表,那麼請交給優化器來決定,比如:select xxx from table1, table2, table3 where ···,優化器會將查詢記錄行數少的表作為驅動表。
  4. 如果你就是想自己指定驅動表,那麼請拿好Explain武器,在Explain的結果中,第一個就是基礎驅動表
  5. 排序。同樣的,對不同表排序也是有很大的性能差異,我們儘量對驅動表進行排序,而不要對臨時表,也就是合併後的結果集進行排序。即執行計劃中出現了 using temporary,就需要進行優化。

2. 執行計劃各參數含義

  1. select_type(查詢的類型):普通查詢和複雜查詢(聯合查詢、子查詢等) SIMPLE,查詢不包含子查詢或者UNION PRIMARY,如果查詢包含複雜查詢的子結構,那麼就需要用到主鍵查詢 SUBQUERY,在select或者where中包含 子查詢 DERIVED,在from中包含子查詢 UNION RESULT,從union表查詢子查詢
  2. table 使用到的表名
  3. type(訪問類型),找到所需行的方式,從上往下,查詢速度越來越快 const或者system 常量級別的掃描,查詢表最快的一種,system是const的一種特殊情況(表中只有一條數據) eq_ref 唯一性索引掃描 ref 非唯一性索引掃描 range 索引的範圍掃描,比如 between、等範圍查詢 index (index full)掃描全部索引樹 ALL 掃描全表 NULL,不需要訪問表或者索引
  4. possible_keys,給出使用哪個索引能找到表中的記錄。這裡被列出的索引不一定使用
  5. key:到底哪一個索引被真正使用到了。如果沒有則為NULL
  6. key_len:使用的索引所佔用的字節數
  7. ref:哪個字段或者常數和索引(key)一起被使用
  8. rows:一共掃描了多少行
  9. filtered(百分比):有多少數據在server層還進行了過濾
  10. Extra:額外信息 only index 信息只需要從索引中查出,可能用到了覆蓋索引,查詢非常快 using where 如果查詢沒有使用索引,這裡會在server層過濾再使用 where來過濾結果集 impossible where 啥也沒查出來 using filesort ,只要沒有通過索引來排序,而是使用了其他排序的方式就是 filesort using temporary(需要通過臨時表來對結果集進行暫時存儲,然後再進行計算。)一般來說這種情況都是進行了DISTINCT、排序、分組 using index condition 索引下推,上文講過,就是把server層這個過濾操作下推到引擎層

四、存儲引擎

  1. 當僅僅是插入與查詢比較多的時候,可以使用MyISAM存儲引擎
  2. 當只是使用臨時數據,可以使用memory
  3. 當插入、更新、查詢等併發數很多時,可以使用InnoDB

總結

從五個層次回答MYSQL優化,由上至下

  1. SQL與索引
  2. 存儲引擎與表結構
  3. 數據庫架構
  4. MySQL配置
  5. 硬件與操作系統

除此之外,查數據慢,要不僅僅拘留於一味的 "優化" 數據庫,而是要從業務應用層面去分析。比如對數據進行緩存,對請求進行限流等。

我們下篇文章見


分享到:


相關文章: