【劃重點】MySQL技術內幕:InnoDB存儲引擎

說明

本文絕大部份內容來源《MySQL技術內幕:InnoDB存儲引擎》一書,部分圖片來源網絡。#我是搬運工#html

InnoDB 體系結構

後臺線程

InnoDB存儲引擎是多線程模型,其後臺有多個不一樣的後臺線程,負責處理不一樣的任務。mysql

Master Thread

Master Thread 主要負責將緩存池中的數據異步刷新到磁盤,保證數據的一致性,包括髒頁的刷新、合併插入緩衝(INSERT BUFFER)、UNDO頁的回收。算法

IO Thread

IO Thread 主要負責 Async IO 請求的回調處理,包含 write、read、insert buffer 和 log IO thread。sql

Purge Thread

Purge Thread 負責回收已經使用並分配的 undo 頁,減輕 Master Thread 的工做。shell

Page Cleaner Thread

Page Cleaner Thread 做用是將以前版本中髒頁的刷新操做都放入到單獨的線程中來完成,減輕 Master Thread 的工做及對於用戶查詢線程的阻塞。數據庫

內存

緩衝區

一塊內存區域,經過內存的速度來彌補磁盤速度較慢對數據庫性能的影響;讀取數據時,首先將從磁盤讀到的數據存放在緩衝池中,下一次讀取直接從緩衝池中取。更新數據時,先更顯緩衝池的數據,而後經過後臺線程按期將有過更新的緩衝數據刷新到磁盤。從而減小磁盤IO的讀寫。緩存

clipboard.png

LRU List、Free List 和 Flush List

數據庫緩衝池經過 LRU (Last Recent Used) 算法管理,LRU List 用來管理已經讀取的頁,數據庫啓動時,LRU List 爲空列表,沒有任何的頁。此時頁都存放在 Free List 中,當須要從緩衝池中分頁時,首先從 Free List 中查找是否有可用的空閒頁,如有空閒頁則將該頁從 Free 列表中刪除並可以放入到 LRU List 中,不然淘汰 LRU List 中末尾的頁。在 LRU List 中的頁被修改後,稱該頁爲髒頁(dirty page)。髒頁存儲於 Flush List,表示緩衝池中的頁與磁盤頁不一致,等待被調度刷新。髒頁同時存在於 Flush List 與 LRU List 中。服務器

重作日誌緩衝 redo buffer cache

InnoDB 將重作日誌首先寫入 redo buffer cache,以後經過必定頻率寫入到重作日誌(redo logo)中。redo buffer cache 不須要設置太大,重作日誌緩衝在一下狀況下被刷入到重作日誌文件中:
(1) Master Thread 每一秒將重作日誌緩衝刷到重作日誌文件
(2) 每一個事務提交時會將重作日誌緩衝刷新到重作日誌文件
(3) 當重作日誌緩衝池剩餘空間小於50%時,重作日誌緩衝刷新到重作日誌網絡

額外的內存池

InnoDB 對內存的管理是經過一種稱爲內存堆的方式進行的,對一些數據結構進行內存分配時,須要從額外的內存池中申請,當該區域不夠時,會從緩衝池中進行申請。數據結構

InnoDB 關鍵特性

插入緩衝(insert buffer)

Insert Buffer

對於【非彙集索引】的更新或插入操做,不是直接插入到索引頁中,而是先判斷插入的非彙集索引頁是否在緩衝池中,若在,則直接插入,不然先放入到一個 Insert Buffer 中。再以必定頻率和狀況進行 Insert Buffer 和輔助索引頁子節點的merge操做,合併插入操做,提升非彙集索引的插入性能。

Change Buffer

Insert Buffer 的升級,InnoDb 1.0.x 版本開始引入,一樣適用對象爲非惟一的輔助索引。能夠對 DML 操做進行緩衝:insert、delete、update。

兩次寫(double write)

double write 帶給 InnoDB 存儲引擎的是數據頁的可靠性。

當數據庫發生宕機時,可能InnoDB存儲引擎正在寫入某個頁到列表中,而這個頁只寫了一部分,,好比16KB的頁,只寫了4KB,以後發生宕機,此時次出現【部分寫失效】(頁斷裂)的狀況,InnoDB 經過 double write 解決出現這種狀況時形成的數據丟失而且沒法恢復的問題。
double write 工做流程:髒頁刷新時,先拷貝至內存的 double write buffer,從緩衝區分兩次接入磁盤共享表空間紅,順序寫,緩衝區中的髒頁數據寫入實際的各個表空間,離散寫。

頁斷裂數據恢復流程:經過頁的 checksum,校驗 double write 在磁盤中的數據塊,經過 double write 緩衝區數據來修復。

clipboard.png

自適應哈希索引(Adaptive Hash Index)

InnoDB 會監控對錶上各索引頁的查詢。若是觀察到建議哈希索引能夠帶來速度的提高,則創建哈希索引,稱之爲自適應哈希索引(AHI)。AHI 是經過緩衝池的 B+ 樹構造來的,所以創建的速度很是快,並且不須要對整張表構建哈希索引,InnoDB 會根據訪問頻率和模式來自動建立自適應哈希索引,無需人爲設置干預。

自適應哈希索引只適用於等值查詢,好比 where smsId = 'XXXXXX',不支持範圍查找。

異步 IO(Asynchronous IO)

InnoDB 採用異步IO(AIO)的方式來處理磁盤操做,進而提升對磁盤的操做性能。InnoDB 存儲引擎中,read ahead 方式的讀取是經過 AIO 完成,髒頁的刷新,即磁盤的寫入操做也是由 AIO 完成。

刷新臨接頁(Flush Neighbor Page)

當刷新一個髒頁到磁盤時,InnoDB 會檢測該頁所在區的全部頁,若是是髒頁,則一塊兒進行刷新。經過 AIO 合併多個 IO 寫入,減小磁盤的 IO,可是可能形成將不怎麼髒的頁的磁盤寫入,對於 SSD 磁盤,自己有着較高的 IOPS,則建議關閉該特性,InnoDB 1.2.x 版本提供參數 innodb_flush_neighbors,設置爲 0 可關閉該特性。而對於普通磁盤,建議開啓。

Checkpoint 技術

Checkpoint 技術的目的是解決如下問題:

  • 縮短數據庫的恢復時間
  • 緩衝池不夠用時,刷新髒頁
  • 重作日誌不可用時,刷新髒頁

Checkpoint 類型

  • Sharp Checkpoint:發生在數據庫關閉時,將全部髒頁刷新到磁盤。
  • Fuzzy Chckpoint:數據庫運行時使用該方式進行頁的刷新,刷新部分髒頁進磁盤。

InnoDB 中可能發生的 Fuzzy Checkpoint

Master Thread Checkpoint

Master Thread 以每秒或每十秒的速度從緩衝池的髒頁列表中刷新必定比例的頁到磁盤,異步進行,用戶查詢線程不會阻塞。

FLUSH_LRU_LIST Checkpoint

InnoDB 存儲引擎需保證差很少 100 個空閒頁可用,空閒也不足時,InnoDB 會將 LRU 列表尾端的頁移除,若是尾端頁存在髒頁,則須要進行 Checkpoint。

Async/Sync Flush Checkpoint

重作日誌不可用時進行,強制將一些頁刷新回磁盤,從髒頁列表中選取。根據不一樣的狀態使用不一樣的刷新方式(同步或異步)。

Dirty Page too much Checkpoint

髒頁數量太多,好比佔據緩衝池比例大於 75% 時,強制進行刷新,比例可調。

MySQL 文件

參數文件

告訴 MySQL 實例啓動時在哪裏能夠找到數據庫文件,而且指定某些初始化參數,這些參數定義了某種內存結構的大小等設置。在默認狀況下,MySQL 實例會按照必定的順序在指定的位置進行讀取,經過如下命令能夠尋找:

mysql --help | grep my.cnf

MySQL 數據庫中的參數分類

  • 動態參數:MySQL 運行期間中能夠進行實時修改
  • 靜態參數:MySQL 運行期間不可修改,只讀

日誌文件

記錄了影響 MySQL 數據庫的各類類型活動,常見的日誌文件有:

錯誤日誌

對 MySQL 的啓動、運行、關閉過程進行記錄,可根據錯誤日誌定位問題。不只記錄錯誤信息,同時也記錄一些告警信息或正確的信息。

# 查看日誌文件存儲路徑
mysql> show variables like 'log_error';
+---------------+--------------------------------+
| Variable_name | Value                          |
+---------------+--------------------------------+
| log_error     | /data/mysql_data/data/r002.err |
+---------------+--------------------------------+

慢查詢日誌

幫助查找存在問題的 SQL 語句,記錄執行時間超過某個時間長度的 SQL 語句。

# 查詢記錄執行時間長度(秒)
mysql> show variables like 'long_query_time' \g;
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
# 慢查詢記錄開關
mysql> show variables like 'log_slow_queries' \g;
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| log_slow_queries | ON    |
+------------------+-------+

慢查詢日誌文件可經過 mysqldumpslow 解析結果並查看。

查詢日誌

查詢日誌記錄了全部對 MySQL 數據庫請求的信息,不管這些請求是否獲得了正確的執行。默認文件名爲:主機名.log。

二進制日誌

二進制日誌(binary log)記錄了對 MySQL 數據庫執行更改的全部操做,不包含只讀操做。二進制文件主要有如下幾種做用:

  • 恢復:某些數據的恢復須要二進制日誌,例如,在一個數據庫全備文件恢復後,用戶能夠經過二進制日誌進行 point-in-time 的恢復。
  • 複製:經過複製和執行二進制日誌使一臺遠程的 MySQL 數據庫與另外一臺 MySQL 數據庫進行實時同步。
  • 審計:用戶能夠經過二進制日誌中的信息來進行審計,判斷是否有對數據庫進行注入攻擊。

套接字文件

在 UNIX 系統下本地鏈接 MySQL 能夠採用 UNIX 域套接字方式。

# 查看套接字文件地址
mysql> show variables like 'socket';
+---------------+-----------------------+
| Variable_name | Value                 |
+---------------+-----------------------+
| socket        | /var/mysql/mysql.sock |
+---------------+-----------------------+

pid 文件

在 MySQL 實例啓動時,生成的進程ID會被寫入到一個文件中,即 pid 文件。

# 查看 pid 文件
mysql> show variables like 'pid_file';
+---------------+--------------------------------+
| Variable_name | Value                          |
+---------------+--------------------------------+
| pid_file      | /data/mysql_data/data/r002.pid |
+---------------+--------------------------------+

表結構定義文件

MySQL 數據的存儲是根據表進行的,每一個表都有與之對應的文件,是以 frm 爲後綴名的文件,記錄了表的表結構定義。

InnoDB 存儲引擎文件

InnoDB 存儲引擎獨有的文件,與InnoDB 存儲引擎密切相關,包括表空間文件、重作日誌文件。

表空間文件

InnoDB 採用將存儲的數據按表空間進行存放的設計。默認配置下有一個初始化大小爲 10MB 的 ibdata1 文件,可自動增加。能夠經過參數 innodb_data_file_path 對其進行設置。若設置了參數 innodb_file_per_table,則用戶能夠將每一個基於 InnoDB 存儲引擎的表產生一個獨立表空間。獨立表空間命名規則:表名.ibd

# 查看是否開啓獨立表空間存儲
mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON   |
+-----------------------+-------+

須要注意的是,這些單獨的表空間文件僅存儲該表的數據、索引和插入緩衝 BITMAP 等信息,其他信息仍是存放在默認表空間中。下圖顯示了 InnoDB 存儲引擎對於文件的存儲方式:

clipboard.png

重作日誌文件

默認狀況下,InnoDB 存儲引擎的數據目錄下會有兩個名爲 ib_logfile0 和 ib_logfile1 的文件,即 InnoDB 存儲引擎的重作日誌文件(redo log file),記錄了對於 InnoDB 存儲引擎的事務日誌。

當實例或介質存儲失敗時,例如因爲主機斷電致使實例失敗,InnoDB 存儲引擎會使用重作日誌恢復到斷電前的時刻,一次來保證數據的完整性。InnoDB 存儲引擎會逐個循環寫日誌文件,當前寫的日誌文件被寫滿後,切到下一個日誌文件,當下一個日誌文件也被寫滿後,循環寫前一個日誌文件。日誌文件數量及大小可配置(innodb_log_files_in_group、innodb_log_file_size)。

關於重作日誌文件的大小設置:
(1) 不能設置太大,若是設置得很大,在恢復時可能須要很長的時間
(2) 不能設置太小,可能會致使一個事務的日誌須要屢次切換重作日誌文件,也會致使頻繁的發生 async checkpoint,致使性能抖動。

寫入重作日誌文件的操做不是直接寫,而是先寫入一個重作日誌緩衝(redo log buffer)中,而後按照必定的順序寫入日誌文件:

clipboard.png

MySQL 二進制文件

MySQL 二進制文件記錄 MySQL 數據庫執行的更新操做。包含二進制日誌文件和二進制索引文件。

mysql-bin.index
mysql-bin.000001
mysql-bin.000002
mysql-bin.XXXXXX

mysql-bin.000001 即爲二進制日誌文件,日誌文件超過必定大小(根據 max_binlog_size 肯定)時生成新的文件,後綴名 +1。binlog 相關的參數有以下:

max_binlog_size    # 單個 binlog 日誌文件的最大值
binlog_cache_size  # 事務提交時的二進制日誌寫入的緩衝區大小
sync_binlog           # 表示每寫入多少次緩衝區就同步至磁盤
binlog-do-db       # 表示須要寫入哪些庫的日誌
binlog-ignore-db   # 表示忽略寫入哪些庫的日誌
bin_log_format     # 表示二進制日誌的記錄格式,包含 STATEMENT、ROW、MIXED

二進制日誌記錄格式

STATEMENT:MySQL 5.1 以前的存儲格式,5.1 版本之後可選格式,記錄日誌的邏輯 SQL 語句。
ROW:二進制日誌再也不是簡單的 SQL 語句,而是記錄錶行的更改狀況,包含一行數據更改前與更改後列的內容。
MIXED:默認採用 STATEMENT 格式進行二進制日誌文件的記錄,可是在一些狀況下回使用 ROW 格式,如如下狀況:
1)表的存儲引擎爲 NDB
2)使用了 UUID()、USER()、CURRENT_USER()、FOUND_ROWS()、ROW_COUNT() 等不肯定函數
3)使用了 INSERT DELAY 語句
4)使用了用戶定義函數(UDF)
5)使用了臨時表

STATEMENT 與 ROW 模式對比

  • 一般狀況下 STATEMENT 由於只記錄邏輯 SQL 語句,相關 ROW 模式下日誌存儲大小較小,特別是批量更新狀況下,ROW 模式的日誌文件遠遠大於 STATEMENT 模式下的日誌文件,在作日誌複製時,因爲要傳輸 binlog 文件的內容,STATEMENT 模式的傳輸要優於 ROW 模式
  • 若是更新的 SQL 語句中存在不肯定的函數調用等狀況,用 STATEMENT 模式記錄的 SQL 語句作同步會致使數據不一致,所以使用場景有所侷限。

二進制文件(binlog)與重作日誌文件(redo log)

  • 二進制日誌記錄全部與 MySQL 數據庫有關的日誌記錄,包括 InnoDB、MyISAM以及其餘存儲引擎的日誌。而 InnoDB 存儲引擎的重作日誌只記錄 InnoDB 存儲引擎自己的事務日誌。
  • 記錄的內容不一樣,二進制日誌文件記錄的是關於一個事務的具體操做內容,即該日誌的邏輯日誌。而重作日誌文件記錄的是關於每一個頁(Page )的更改的物理狀況。
  • 寫入時間不一樣,二進制日誌文件僅在事務提交後進行寫入,即只寫磁盤一次,不論事務有多大。而在事務進行的過程當中,卻不斷有重作日誌條目被寫入到重作日誌文件中。

MySQL 分區表

InnoDB 邏輯存儲結構

clipboard.png

從 InnoDB 存儲引擎的邏輯存儲結構看,全部數據都被邏輯的存放在表空間(tablespace),表空間又分爲段(segment)、區(extend)、頁(page)組成。而表的行數據則存儲在頁中,一個頁存儲多個行。

分區

MySQL 數據庫在 5.1 版本時添加了對分區的支持。分區功能並非在存儲引擎層完成的,所以不只 InnoDB 存儲引擎支持分區,MyISAM、NDB 等都支持。也並非全部存儲引擎都支持分區,如 CSV、MERGE、FEDORATED 等就不支持。

分區的做用

對一部分 SQL 語句性能帶來明顯的提升,可是分區主要用於數據庫高可用性的管理。

分區類型

RANGE 分區

行數據基於屬於一個給定連續區間的列值被放入分區。

mysql> create table test_range (id int) ENGINE = INNODB PARTITION BY RANGE (id)(
    -> PARTITION P0 VALUES LESS THAN (10),
    -> PARTITION P1 VALUES LESS THAN (20));
Query OK, 0 rows affected (0.03 sec)

建立分區後,存儲文件的獨立表空間將根據分區存儲,以下圖所示:

-rw-rw----  1 _mysql  _mysql    96K Jan 28 17:05 test_range#P#P0.ibd
-rw-rw----  1 _mysql  _mysql    96K Jan 28 17:05 test_range#P#P1.ibd
-rw-rw----  1 _mysql  _mysql   8.4K Jan 28 17:05 test_range.frm
-rw-rw----  1 _mysql  _mysql    28B Jan 28 17:05 test_range.par

對錶添加數據時,會根據指定的列將數據存儲到對應的分區存儲文件中,如列對應的值不在對應範圍內,將寫入失敗:

mysql> insert into test_range values (30);
ERROR 1526 (HY000): Table has no partition for value 30

LIST 分區

LIST 分區與 RANGE 分區很是類似,只是分區列的值是離散的,而非連續的。以下:

mysql> create table test_list (a INT, b INT) ENGINE = INNODB PARTITION BY LIST (b)(
    -> PARTITION P0 VALUES IN (1, 3 ,5, 7, 9),
    -> PARTITION P1 VALUES IN (0, 2, 4, 6, 8));
Query OK, 0 rows affected (0.03 sec)

一樣的,添加數據時,對應的列必須在指定的範圍內,不然將寫入失敗:

mysql> insert into test_list (a, b) values (1, 11);
ERROR 1526 (HY000): Table has no partition for value 11

HASH 分區

HASH 分區的目的是將數據均勻的分佈到預先定義的各個分區中,保證各分區的數據量大體同樣。用於須要對將要進行哈希分區的列值指定一個列值或表達式,以及指定被分區的表將要被分割成的分區數量:

mysql> create table test_hash (a INT, b DATETIME) ENGINE = INNODB
    -> PARTITION BY HASH (YEAR(b))
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)

KEY 分區

KEY 分區與 HASH 分區類似,HASH 分區使用用戶定義的函數進行分區,KEY 分區使用 MySQL 數據庫提供的函數進行分區。

mysql> create table test_key (a INT, b DATETIME) ENGINE = INNODB
    -> PARTITION BY KEY (b)
    -> PARTITIONS 4;
Query OK, 0 rows affected (0.04 sec)

COLUMNS 分區

以上四種區分方式均存在同樣的分區條件:數據必須是整型(INT)的,若是不是整型,須要將對應的值轉化爲整型,如 YEAR(),TO_DAYS() 等函數。MySQL 5.5 版本開始支持 COLUMNS 分區,可視爲 RANGE 分區和 LIST 分區的一種進化。

能夠直接使用非整形的數據進行分區,如全部的整型類型 SMALLINT、BIGINT,日期類型 DATE、DATETIME,字符串類型 CHAR、VARCHAR,相應的 FLOAT、DECIMAL、BLOB、TEXT、TIMESTAMP 不予支持。

分區和分區性能

數據庫的應用分爲兩類:OLTP(在線事務處理)和 OLAP(在線分析處理)。

對於 OLAP 的應用,分區的確能夠很好地提升查詢性能。體如今掃描表時不須要掃描全表,掃描單個分區時效率最高;同時也依賴於應用的處理以及分區方式,如不合理的分區,將帶來巨大的性能降低。好比對主鍵進行 HASH 分區,查詢的時候經過非主鍵字段匹配查詢,則一樣是全量數據掃描,可是因爲分區的數量較多,會大量增長系統的 IO 調用。

對於 OLTP 的應用,分區並不會明顯的下降 B+ 樹索引高度,通常的 B+ 樹須要 2~3 次的磁盤 IO,分區並不能明顯提高寫入速度。可是設計很差的分區會帶來嚴重的性能問題。

MySQL 索引

InnoDB 存儲引擎支持如下幾種常見索引:

  • B+ 樹索引
  • 哈希索引
  • 全文索引

B+ 樹

B+ 樹的概念在此不作介紹,B+ 樹的操做演示地址:https://www.cs.usfca.edu/~gal...

B+ 樹索引

MySQL 並非經過 B+ 樹索引直接找到數據行,而是找到數據行所在的頁,將頁加載到內存,最後查找到行數據。一個數據頁包含多行數據。

B+ 樹索引包含數據頁與索引頁。數據頁存放完整的行數據,索引頁存放鍵值以及指向數據頁的偏移量,而非完整的行記錄。

B+ 樹索引分類

彙集索引(Clustered Index)

InnoDB 存儲引擎是索引組織表,即表中數據按照主鍵順序存放,彙集索引就是按照主鍵構造一顆 B+ 樹,同時葉子節點存放表的行記錄數據,也將彙集索引的葉子節點稱爲數據頁。簡而言之,數據是索引的一部分。

MySQL 經過索引構造數據,因此一張數據表中只能有一個彙集索引。

輔助索引(Secondary Index)

也成爲非彙集索引,葉子節點並不包含數據行的所有數據。葉子節點中的索引行中包含一個書籤,該書籤就是相應行數據的彙集索引鍵,所以經過非彙集索引查找行數據須要通過兩級索引才能查找到具體的數據內容。好比,非主鍵索引查找行數據,先經過非主鍵索引查找到主鍵,再經過主鍵查找行數據。

哈希索引

MySQL 中的 HASH 索引爲自適應的,無需人工干擾,MySQL 內部會針對查詢業務自動建立 HASH 索引,以提升業務的查詢效率。

HASH 索引僅適用的等值匹配查詢,對於範圍查找無能爲力。

全文索引

InnoDB 1.2.x 版本開始,InnoDB 存儲引擎開始支持全文索引,經過倒排索引來實現。由於業務極少使用 MySQL 的全文索引,一般若是須要作全文搜索,可選擇 Elasticsearch。

Cardinality 值

Cardinality 值對列建立索引的選擇性提供了較好的參考,Cardinality 爲一個預估值,非準確值,某一個列的 Cardinality 值表明該列在整張表中不重複值的數量。

忽略業務因素以及數據類型,表中某個列是否適合建立索引,體如今該列全部的值是否相對分散,重複數據越少,相對來講越適合添加索引。所以,當某個列 Cardinality值/錶行數 約接近 1,表明重複數據越少,爲該列建索引的選擇性便越高。

InnoDB 存儲引擎對於 Cardinality 的更新是非實時的,而且獲取到的值爲預估值,經過採樣統計來獲取該值。一般具體業務只需關心該值是否接近於表的行數,以判斷某個列是否適合建立索引。

MySQL 鎖

Innodb 存儲引擎鎖類型

行級鎖
共享鎖(S Lock):容許事務讀一行數據
排他鎖(X Lock):容許事務刪除或更新一行數據
表級鎖
意向共享鎖(IS Lock):事務想要得到一張表中某幾行的共享鎖
意向排他鎖(IS Lock):事務想要得到一張表中某幾行的排他鎖

InnoDB 存儲引擎中鎖的兼容性:

IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

以下圖所示,當 InnoDB 須要作細粒度加鎖時,好比對某一行加 X 鎖,須要先對該行所在的表、頁加 IX 鎖。若其中任何一個部分致使等待,那麼該操做須要等待粗粒度鎖的完成。

clipboard.png

一致性非鎖定讀:Consistent Nonlocking Read

InnoDB 存儲引擎經過行多版本控制的方式來讀取當前執行時間數據庫中行的數據。若是讀取的行正在執行 UPDATE 或 DELETE 操做,這時讀取操做不會所以等待行上鎖的釋放。相應的,InnoDB 存儲引擎會去讀取行的一個快照數據。

非鎖定讀機制極大的提升了數據庫的併發性,在 InnoDB 存儲引擎的默認設置下,讀取不會佔用和等待表上的鎖。快照數據即當前行記錄的歷史版本,每行記錄可能有多個版本,由此帶來的併發控制,稱之爲多版本併發控制(Multi Version Concurrency Control,MVCC)。

一致性鎖定讀:Consistent Locking Read

在某些狀況下,用戶須要顯示的對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。這要求數據庫支持加鎖語句,即便對於 SELECT 的支付操做。InnoDB 存儲引擎對於 SELECT 語句支持兩種一致性的鎖定讀操做:

# 對讀取的行記錄添加一個 X 鎖
SELECT ... FOR UPDATE;

# 對讀取的行記錄添加一個 S 鎖
SELECT ... LOCK IN SHARE MODE;

鎖的算法

1)Record Lock:單個行記錄上的鎖

# 鎖定 id = 5 的行記錄
SELECT ... FROM ... WHERE id = 5 FOR UPDATE;

2)Gap Lock:間隙鎖,鎖定一個範圍,單不包含記錄自己

# 鎖定 id < 5 的全部記錄
SELECT ... FROM ... WHERE id < 5 FOR UPDATE;

3)Next-Key Lock:Gap Lock + Record Lock,鎖定一個範圍,而且鎖定記錄自己

# 鎖定 id <= 5 的全部記錄
SELECT ... FROM ... WHERE id <= 5 FOR UPDATE;

幻像問題:Phantom Problem

Phantom Problem 是指同一事務下,連續執行兩次一樣的 SQL 語句可能致使不一樣的結果,第二次的 SQL 語句可能會返回以前不存在的行。好比:

# 表 t 中存在 id 爲 1,2,3,4 四條數據,事務隔離級別爲  READ-COMMITTED
BEGIN;
SELECT * FROM t WHERE id > 2;    # 此時查詢結果有id爲 3,4 的記錄
# 此時其餘線程增長一條數據 id = 5
SELECT * FROM t WHERE id > 2;    # 此時查詢結果有id爲 3,4,5 的記錄

上述查詢結果,在一個事務中出現同一個查詢返回不一樣的結果,違反了事務的隔離性,即當前事務可以看到其餘事務的結果。

InnoDB 存儲引擎默認的事務隔離級別是 REPEATABLE READ,在該事務隔離級別下采用 Next Key Locking 的方式來加鎖解決。同時應用也能夠經過 InnoDB 存儲引擎的 Next Key Locking 機制在應用層面實現惟一性的檢查。例如:

SELECT * FROM t WHERE col = xxx LOCK IN SHARE MODE;

鎖問題

髒讀

髒讀指一個事務能夠讀到另外一個事務中未提交的修改數據,違反了數據庫的隔離性。

髒讀發生的條件是事務隔離級別是 READ UNCOMMITTED,目前大部分數據庫都至少設置成 READ COMMITTED。

不可重複讀

不可重複讀指在一個事務內屢次讀取同一數據集合,出現了不一樣的數據結果。

不可重複讀發生在事務隔離級別爲 READ COMMITTED,事務 A 讀取一個結果集,事務 B 一樣讀取到該結果集並對其進行修改,提交事務,事務 A 再次讀取結果集時,兩次結果不一致。通常狀況下,不可重複的問題是可接受的,由於讀取的是已經提交的數據,自己不會帶來很大問題。

InnoDB 存儲引擎的隔離級別爲 READ REPEATABLE 時,採用 Next Key Lock 算法,避免了不可重複讀的現象。

丟失更新

一個事務的更新操做結果被另外一個事務的更新操做結果所覆蓋,從而致使數據的不一致。

數據庫層面能夠阻止丟失更新問題的發生,可是應用中存在一個邏輯意義的丟失更新問題。例如,多個線程同時讀取到某條數據,以後均對數據進行修改再更新庫,此時會出現最後一個線程的更新結果覆蓋了先執行的更新結果。應用層面能夠經過對查詢的數據進行加鎖,如前文提到的一致性鎖定讀方式,對須要更新的數據進行加鎖,其餘線程即會出現阻塞串行等待。

死鎖

死鎖是指兩個或兩個以上的事務在執行過程當中,因搶奪鎖資源而形成的互相等待的現象。

解決死鎖的方式:
1)超時
當一個等待時間超過設置的某一閾值時,對該事務進行回滾,InnoDB 中經過參數 innodb_lock_wait_timeout 設置超時時間。

超時處理機制簡單,但不判斷事務所佔權重,好比一個事務更新的行很是多,回滾也須要佔用更多的時間,同時與該事務搶佔資源的事務可能僅更新少許數據,回滾該事務應當更合理。

2) wait-for graph(等待圖)死鎖檢測
主動檢測死鎖,判斷事務之間的等待狀態是否存在閉環。若檢測到存在死鎖的狀況,InnoDB 存儲引擎選擇回滾 undo 量最小的事務。

鎖升級

鎖升級指將當前鎖的粒度下降。好比數據庫把一個表的 1000 個行鎖升級爲一個頁鎖,或者將頁鎖升級爲表鎖。從而避免鎖的開銷。

InnoDB 存儲引擎根據頁進行加鎖,並採用位圖方式, 開銷由頁的量決定,所以 InnoDB 引擎不會產生鎖升級的問題。

MySQL 事務

事務的實現

InndoDB 是事務的存儲引擎,其經過 Forece Log at Commit 機制實現事務的持久性,即當事務提交時,必須先將該事務的全部日誌寫入到重作日誌文件進行持久化,待事務的 COMMIT 操做完成纔算完成。這裏的日誌是指重作日誌,由兩部分組成,即 redo log 和 undo log。

事務的 ACID 特性實現:隔離性,經過鎖實現;原子性、一致性、持久性,經過數據庫的 redo log 和 undo log 實現。

redo log 與 undo log

redo 和 undo 的做用均可以視爲是一種恢復操做,redo 恢復提交事務修改的頁操做,而 undo 回滾行記錄到某個特定的版本,用來幫助事務回滾及 MVCC 的功能。所以二者記錄的內容不一樣,redo 一般是物理日誌,記錄的是頁的物理修改操做。redo log 基本上都是順序寫的,undo 是邏輯日誌,根據每行記錄進行記錄。undo log 是須要進行隨機讀寫的。

redo

重作日誌用來實現事務的持久性,即事務 ACID 的 D。其由兩部分組成:一是內存中的重作日誌緩衝(redo log buffer),其是容易丟失的;二是重作日誌文件(redo log file),其是持久的。

爲了確保每第二天志都寫入重作日誌文件,在每次將重作日誌緩衝寫入重作日誌文件後,InnoDB 存儲引擎都須要調用一次 fsync 操做。所以磁盤的性能決定了事務提交的性能,也就是數據庫的性能。

參數 innodb_flush_log_at_trx_commit 用來控制重作日誌刷新到磁盤的策略,其參數含義以下:

1 -> 表示事務提交時必須調用一次 fsync
0 -> 表示事務提交是不進行寫入重作日誌操做,這個操做盡在 master thread 中完成,而 master thread 中每 1 秒進行一次重作日誌文件的 fsync 操做
2 -> 表示事務提交時將重作日誌寫入重作日誌文件,但僅寫入文件系統的緩存中,不進行 fsync 操做。

MySQL 默認參數爲 1,保證最高的數據可靠性,爲 0 或 2 時能夠提供更好的事務性能,可是存在數據庫宕機時數據丟失風險。

undo

重作日誌記錄了事務的行爲,能夠很好的經過其對頁進行「重作」操做。可是事務有時還須要進行回滾操做,這時就須要 undo。在對數據庫進行修改時,InnoDB 存儲引擎不但會產生 redo,還會產生必定量的 undo,這樣若是執行的事務或語句因爲某種緣由失敗了,又或者是用戶主動 ROLLBACK 請求回滾,就能夠利用 undo 進行數據回滾到修改以前的樣子。

purge

delete 和 update 操做並不直接刪除原有的數據,執行 delete 語句時,受影響的數據被標記爲邏輯刪除,真正刪除這行記錄的操做在 purge 操做中完成。

purge 用於最終完成 delete 和 update 操做。這樣設計是由於 InnoDB 存儲引擎支持 MVCC,因此記錄不能再事務提交時當即進行處理。而是否能夠刪除該條記錄經過 purge 來判斷,若該行記錄已再也不被任務其餘事務引用,那麼就能夠進行真正的 delete 操做。

group commit

若事務爲非只讀事務,則每次事務提交時須要進行一次 fsync 操做,以保證重作日誌都已經寫入磁盤。爲了提升磁盤 fsync 的效率,當前數據庫提供了 group commit 的操做,即一次 fsync 能夠刷新確保多個事務日誌被寫入文件。對於 InnoDB 存儲引擎來講,事務提交時會進行兩個階段的操做:
1)修改內存中事務對應的信息,而且將日誌寫入重作日誌緩衝
2)調用 fsync 確保日誌都從重作日誌緩衝寫入磁盤

步驟 2)相對步驟 1)是一個較慢的過程,當有事務進行步驟 2)時,其餘事務能夠進行步驟 1)的操做,正在提交的事務完成提交操做後,再次進行步驟 2)時,能夠將多個事務的重作日誌經過一次 fsync 刷新到磁盤,這樣就大大的減小了磁盤的壓力,從而提升了數據庫的總體性能。

MySQL 備份與恢復

備份分類

根據不一樣的類型來劃分:

  • Hot Backup(熱備)
  • Cold Backup(冷備)
  • Warm Backup(溫備)

按照備份後文件的內容劃分:

  • 邏輯備份
  • 裸文件備份

按照備份數據庫的內容劃分:

  • 徹底備份
  • 增量備份
  • 日誌備份

冷備

對於 InnoDB 存儲引擎的冷備,只須要備份 MySQL 數據庫的 frm 文件,共享表空間文件,獨立表空間文件(*.ibd),重作日誌文件。另外建議按期備份 MySQL 數據庫的配置文件 my.cnf,有利於恢復的操做。

冷備的優勢:

  • 備份簡單,只須要複製相關文件便可
  • 備份文件易於在不一樣操做系統,不一樣 MySQL 版本上進行恢復
  • 恢復至關簡單,只須要把文件恢復到指定位置便可
  • 回覆速度快,不須要執行任何 SQL 語句,不須要重建索引

冷備的缺點:

  • InnoDB 存儲引擎冷備的文件一般比邏輯文件大不少
  • 冷備也不老是能夠輕易的跨平臺

邏輯備份

mysqldump

用來完成轉存數據庫的備份及不一樣數據庫之間的移植,如從 MySQL 低版本數據庫升級到 MySQL 高版本數據庫,又或者從 MySQL 移植到 Oracle,SQL Server 等。

mysqldump [arguments] > file_name
mysqldump --all-databases > dump.sql
mysqldump --databases db1 db2 db3 > dump.sql

SELECT ... INTO OUTFILE

邏輯備份方法,更準確的說是導出一張表中的數據。

SELECT * INTO OUTFILE '/home/yw/a.txt' FROM test;

邏輯備份的恢復

SOURCE

mysqldump 的恢復操做簡單,僅需執行導出的 SQL 語句便可。

source /home/yw/dump.sql

LOAD DATA INFILE

恢復經過 SELECT INTO OUTFILE 導出的數據

LOAD DATA INTO TABLE test IGNORE 1 LINES INFILE '/home/yw/a.txt'

二進制日誌備份與恢復

二進制日誌很是關鍵,用戶能夠經過它完成 point-in-time 的恢復工做,MySQL 的 replication 一樣須要二進制日誌,在默認狀況下並不開啓二進制日誌,要使用二進制日誌必須啓用它。InnoDB 存儲引擎推薦的二進制日誌的服務器配置以下:

[mysqld]
log-bin = mysql-bin
sync_binlog = 1
innodb_support_xa = 1

在備份二進制日誌文件前,能夠經過 FLUSH LOGS 命令生成一個新的二進制日誌文件,而後備份以前的二進制日誌。

恢復二進制日誌也很是簡單,經過 mysqlbinlog 便可:

mysqlbinlog [options] log_file
mysqlbinlog binlog.0000001 | mysql -uroot -p test

也能夠先將二進制文件導出到一個文件,而後經過 source 進行導入:

shell > mysqlbinlog binlog.0000001 > /home/yw/binlog.sql
...
mysql > source /home/yw/binlog.sql

複製的工做原理

複製(replication)是 MySQL 數據庫提供的一種高可用高性能的解決方案,replication 的工做原理分爲如下 3 個步驟:

  • 主服務器把數據更改記錄到二進制日誌(binlog)中
  • 從服務器把主服務器的二進制日誌複製到本身的中繼日誌(relay log)中
  • 從服務器重作中繼日誌中的日誌,把更改用到本身的數據庫上,以達到最終一致性

複製的工做原理以下圖所示:

clipboard.png

相關文章
相關標籤/搜索