1. 程式人生 > >《MySQL技術內幕:InnoDB儲存引擎》第2版筆記

《MySQL技術內幕:InnoDB儲存引擎》第2版筆記

第1章 MySQL體系結構和儲存引擎

1.1 定義資料庫和例項

  1. 在MySQL資料庫中,資料庫檔案可以是fm、MYD、MYI、ibd結尾的檔案。
  2. MySQL資料庫由後臺執行緒以及一個共享記憶體區組成。
  3. MySQL被設計為一個單程序多執行緒架構的資料庫,這點與SQL Server比較類似,但與Oracle多程序的架構有所不同(Oracle的Windows版本也是單程序多執行緒架構的)。
  4. 在上述例子中使用了mysqld_safe命令來啟動資料庫,當然啟動MySQL例項的方法還有很多,在各種平臺下的方式可能又會有所不同。
  5. 而在MySQL資料庫中,可以沒有配置檔案,在這種情況下,MySQL會按照編譯時的預設引數設定啟動例項。
  6. 可以看到,MySQL資料庫是按
/etc/my.cnf -> /etc/mysql/my.cnf -> /usr/local/mysql/etc/my.cnf -> ~/.my.cnf

的順序讀取配置檔案的。

1.2 MySQL體系結構

  1. 從概念上來說,資料庫是檔案的集合,是依照某種資料模型組織起來並存放於二級儲存器中的資料集合;資料庫例項是程式,是位於使用者與作業系統之間的一層資料管理軟體,使用者對資料庫資料的任何操作,包括資料庫定義、資料查詢、資料維護、資料庫執行控制等都是在資料庫例項下進行的,應用程式只有通過資料庫例項才能和資料庫打交道。
  2. MySQL資料庫區別於其他資料庫的最重要的一個特點就是其外掛式的表儲存引擎。
  3. 需要特別注意的是,儲存引擎是基於表的,而不是資料庫。
  4. MySQL由以下幾個部分組成:
1. Connectors: Native C API, JDBC, ODBC, NET, PHP, Perl, Python, Ruby, Cobol 這行不是!!
2. Management Service & Utilities: Backup&Recovery, Security, Replication, Cluster, Administration, Configuration, Migration&Metadata
3. Connection Pool: Authentication, Thread Reuse, Connection Limits, Check Memory, Caches
4. 
SQL Interface: DML, DDL, Stored Procedures View, Triggers, etc. 5. Parser: Quary Translation Object Privilege 6. Optimizer: Access Paths, Statistics 7. Caches & Buffers: Global and Engine Specific Caches & Buffers 8. Pluggable Storage Engines: Memory, Index & Storage Management 9. 外掛式儲存引擎包括:MyISAM, InnoDB, NDB, Archive, Federated, Memory, Merge, Partner, Community, Custom 9. Files & Logs: Redo, Undo, Data, Index, Binary, Error, Query and Slow 10. 物理檔案的檔案系統包括:NTFS, ufs, ext2/3, NFS, SAN, NAS

1.3 MySQL儲存引擎

有些第三方儲存引擎很強大,如大名鼎鼎的InnoDB儲存引擎(最早是第三方儲存引擎,後被Oracle收購),其應用就極其廣泛,甚至是MySQL資料庫OLTP(Online Transaction Processing線上事務處理)應用中使用最廣泛的儲存引擎。

1.3.1 InnoDB儲存引擎

  1. InnoDB儲存引擎支援事務,其設計目標主要面向線上事務處理的應用。其特點是行鎖設計、支援外來鍵,並支援類似於Oracle的非鎖定讀,即預設讀取操作不會產生鎖。從MySQL資料庫5.5.8版本開始,InnoDB儲存引擎是預設的儲存引擎。
  2. 從MySOL 4.1(包括4.1)版本開始,它可以將每個InnoDB儲存引擎的表單獨存放在一個獨立的ibd檔案中。此外,InnoDB儲存引擎支援用裸裝置(row disk)用來建立其表空間。
  3. InnoDB通過使用多版本併發控制(MVCC)來獲得高併發性,並且實現了SQL標準的4中隔離級別,預設為REPEATABLE級別。同時,使用一種被稱為next-key locking的策略來避免幻讀(phantom)現象的產生。除此之外,InnoDB儲存引擎還提供了插入緩衝(insert buffer)、二次寫(double write)、自適應雜湊索引(adaptive hash index)、預讀(read ahead)等高效能和高可用的功能。
  4. 如果沒有顯式地在表定義時指定主鍵,InnoDB儲存引擎會為每一行生成一個6位元組的ROWID,並以此作為主鍵。

1.3.2 MyISAM儲存引擎

  1. MyISAM儲存引擎不支援事務、表鎖設計,支援全文索引,主要面向一些OLAP資料庫應用。
  2. 資料庫系統和檔案系統很大的一個不同之處在於對事務的支援,然而MyISAM儲存引擎是不支援事務的。
  3. 此外,MyISAM儲存引擎的另一個與眾不同的地方是它的緩衝池只快取(cache)索引檔案,而不緩衝資料檔案,這點和大多數的資料庫都非常不同。
  4. MyISAM儲存引擎表由MYD和MYI組成,MYD用來存放資料檔案,MYI用來存放索引檔案。

1.3.3 NDB儲存引擎

  1. NDB儲存引擎是一個叢集儲存引擎,類似於Oracle的RAC叢集,不過與Oracle RAC share everything架構不同的是,其結構是share nothing的叢集結構,因此能提供更高的可用性。NDB的特點是資料全部放在記憶體中(從MySQL 5.1版本開始,可以將非索引資料存放在磁碟上),因此主鍵查詢(primary key lookups)的速度極快,並且通過新增NDB資料儲存節點(Data Node)可以線性地提高資料庫效能,是高可用、高效能的集群系統。
  2. 關於NDB儲存引擎,有一個問題值得注意,那就是NDB儲存引擎的連線操作(JOIN)是在MySQL資料庫層完成的,而不是在儲存引擎層完成的。這意味著,複雜的連線操作需要巨大的網路開銷,因此查詢速度很慢。如果解決了這個問題,NDB儲存引擎的市場應該是非常巨大的。

1.3.4 Memory儲存引擎

  1. Memory儲存引擎(之前稱HEAP儲存引擎)將表中的資料存放在記憶體中,如果資料庫重啟或發生崩潰,表中的資料都將消失。**它非常適合用於儲存臨時資料的臨時表,以及資料倉庫中的緯度表。**Memory儲存引擎預設使用雜湊索引,而不是我們熟悉的B+樹索引。
  2. 比如,只支援表鎖,併發效能較差,並且不支援TEXT和BLOB列型別。
  3. MySQL資料庫使用Memory儲存引擎作為臨時表來存放查詢的中間結果集(intermediate result)。如果中間結果集大於Memory儲存引擎表的容量設定,又或者中間結果包含有TEXT或BLOB列型別欄位,則MySQL資料庫會把其轉換到MyISAM儲存引擎表而存放到磁碟中。

1.3.5 Archive儲存引擎

  1. Archive儲存引擎只支援INSERT和SELECT操作,從MySQL 5.1開始支援索引。Archive儲存引擎使用zlib演算法將資料行(row)進行壓縮後儲存,壓縮比一般可達1:10。**正如其名字所示,Archive儲存引擎非常適合儲存歸檔資料,如日誌資訊。**Archive儲存引擎使用行鎖來實現高併發的插入操作,但是其本身並不是事務安全的儲存引擎,其設計目標主要是提供高速的插入和壓縮功能。

1.3.6 Federated儲存引擎

  1. Federated儲存引擎表並不存放資料,它只是指向一臺遠端MySQL資料庫伺服器上的表。這非常類似於SQL Server的連結伺服器和Oracle的透明閘道器,不同的是,當前Federated儲存引擎只支援MySQL資料庫表,不支援異構資料庫表。

1.3.7 Maria儲存引擎

  1. Maria儲存引擎是新開發引擎,設計目標主要是用來取代原有的MyISAM儲存引擎,從而成為MySQL的預設儲存引擎。

1.3.8 其他儲存引擎

  1. 為什麼MySQL資料庫不支援全文索引?不!MySQL支援,MyISAM、InnoDB(1.2版本)和Sphinx儲存引擎都支援全文索引。
  2. MySQL資料庫速度快是因為不支援事務?錯!雖然MySQL的MyISAM儲存引擎不支援事務,但是InnoDB支援。“快”是相對於不同應用來說的,對於ETL這種操作,MyISAM會有其優勢,但是在OLTP環境中,InnoDB儲存引擎的效率更好。

1.4 各儲存引擎之間的比較

  1. InnoDB的comment: Supports transactions, row-level locking, and foreign keys
  2. MEMORY的comment: Hash based, stored in memory, useful for temporary tables
  3. 下面將通過MySQL提供的示例資料庫來簡單顯示各儲存引擎之間的不同。這裡將分別執行一下語句,然後統計每次使用各儲存引擎後表的大小。(下面的salaries就是示例資料庫中的表咯)
mysql>CREATE TABLE mytest Engine=MyISAM
        ->AS SELECT * FROM salaries; 
mysql>ALTER TABLE mytest Engine=InnoDB;
mysql>ALTER TABLE mytest Engine=ARCHIVE;

通過每次的統計,可以發現當最初表使用MyISAM儲存引擎時,表的大小為40.7MB,使用InnoDB儲存引擎時表增大到了113.6MB,而使用Archive儲存引擎時表的大小卻只有20.2MB。

據我所知,知道MySQL示例資料庫的人很少,可能是因為這個示例資料庫沒有在安裝的時候提示使用者是否安裝(如Oracle和SQL Server)以及這個示例資料庫的下載竟然和文件放在一起。

1.5 連線MySQL

  1. 需要理解的是,連線MySQL操作是一個連線程序和MySQL資料庫例項進行通訊。
  2. 如果對程序通訊比較瞭解,可以知道常用的程序通訊方式有管道、命名管道、命名字、TCP/IP套接字、UNIX域套接字。

1.5.1 TCP/IP

  1. 例如使用者可以在Windows伺服器下請求一臺遠端Linux伺服器下的MySQL例項:
C:\>mysql -h192.168.0.101 -u david -p

這裡需要注意的是,在通過TCP/IP連線到MySQL例項時,MySQL資料庫會先檢查一張許可權檢視,用來判斷髮起請求的客戶端IP是否允許連線到MySQL例項。該檢視在mysql架構下,表名為user:

mysql>USE mysql
mysql>SELECT host,user,password FROM user

從這張許可權表中可以看到。MySQL允許david這個使用者在任何IP段下連線該例項,並且不需要密碼。

1.5.2 命名管道和共享記憶體

  1. 在Windows 2000、Windows XP、Windows2003和Windows Vista以及在此之上的平臺上,如果兩個需要程序通訊的程序在同一臺伺服器上,那麼可以使用命名管道,Microsoft SQL Server資料庫預設安裝後的本地連線也是使用命名管道。在MySQL資料庫中須在配置檔案中啟用–enable-named-pipe選項。在MySQL 1.4之後的版本中,MySQL還提供了共享記憶體的連線方式,這是通過在配置檔案中新增–shared-memory實現的。如果想使用共享記憶體的方式,在連線時,MySQL客戶端還必須使用–protocol=memory選項。

1.5.3 UNIX域套接字

  1. UNIX域套接字其實不是一個網路協議,所以只能在MySQL客戶端和資料庫例項在一臺伺服器上的情況下使用。使用者可以在配置檔案中指定套接字檔案的路徑,如–socket=/tmp/mysql.sock。當資料庫例項啟動後,使用者可以通過下列命令來進行UNIX域套接字檔案的查詢:
mysql>SHOW VARIABLES LIKE 'socket';

在知道了UNIX域套接字檔案的路徑後,就可以使用該方式進行連線了,如下所示:

[root@stargazer ~]# mysql -udavid -S /tmp/mysql.sock

(總結一下使用方法:1.使用者需要在資料庫中找到UNIX域套接字檔案的路徑 2.有了這個路徑後,就可以用UNIX域套接字檔案方式進行連線了)只能這麼說,使用者是程式設計師,所以能登陸MySQL,找到UNIX域套接字檔案在哪兒,然後他要寫程式啦,就在這個程式裡用找到的UNIX域套接字路徑來寫程式碼,以實現連線MySQL資料庫的功能。

第2章 InnoDB儲存引擎

2.1 InnoDB儲存引擎概述

  1. InnoDB儲存引擎同MySQL資料庫一樣,在GNU GPL 2下發行。

2.2 InnoDB儲存引擎的版本

  1. 所以在MySQL 5.1中,可以支援兩個版本的InnoDB,一個是靜態編譯的InnoDB版本,可將其視為老版本的InnoDB;另一個是動態載入的InnoDB版本,官方稱為InnoDB Plugin,可將其視為InnoDB 1.0.x版本。MySQL 5.5版本中又將InnoDB的版本升級到了1.1.x。
  2. 此外,由於不支援多回滾段,InnoDB Plugin支援的最大支援併發事務數量也被限制在1023。而且隨著MySQL 5.5版本的釋出,InnoDB Plugin也變成了一個歷史產品。

2.3 InnoDB體系結構

  1. 後臺執行緒的主要作用是負責重新整理記憶體池中的資料,保證緩衝池中的記憶體快取的是最近的資料。此外將已修改的資料檔案重新整理到磁碟檔案,同時保證在資料庫發生異常的情況下InnoDB能恢復到正常執行狀態。

2.3.1 後臺執行緒

1. Master Thread

  1. Master Thread是一個非常核心的後臺執行緒,主要負責將緩衝池的資料非同步重新整理到磁碟,保證資料的一致性,包括髒頁的重新整理、合併插入緩衝、UNDO頁的回收等。

2. IO Thread

  1. 在InnoDB儲存引擎中大量使用了AIO(Async IO)來處理寫IO請求,這樣可以極大提高資料庫的效能。而IO Thread的工作主要是負責這些IO請求的回撥處理。InnoDB 1.0版本之前共有4個IO Thread,分別是write、read、insert buffer和log IO Thread。
  2. 從InnoDB 1.0.x版本開始,read thread和write thread分別增大到了4個,並且不再使用innodb_file_io_threads引數,而是分別使用innodb_read_io_threads和innodb_write_io_threads引數進行設定。

3. Purge Thread

  1. 事務被提交後,其所使用的undolog可能不再需要,因此需要Purge Thread來回收已經使用並分配的undo頁。在InnoDB 1.1版本之前,purge操作僅在InnoDB儲存引擎的Master Thread中完成。而從InnoDB 1.1版本開始,purge操作可以獨立到單獨的執行緒中進行,以此來減輕Master Thread的工作,從而t提高CPU的使用率以及提升儲存引擎的效能。使用者可以在MySQL資料庫的配置檔案中新增如下命令來啟用獨立的Purge Thread:
[mysqld]
innodb_purge_threads=1

從InnoDB 1.2版本開始,InnoDB支援多個Purge Thread,這樣做的目的是為了進一步加快undo頁的回收。

4. Page Cleaner Thread

  1. Page Cleaner Thread是在InnoDB 1.2.x版本中引入的。其作用是將之前版本中髒頁的重新整理操作都放入到單獨的執行緒中來完成。

2.3.2 記憶體

1. 緩衝池

  1. InnoDB儲存引擎是基於磁碟儲存的,並將其中的記錄按照頁的方式進行管理。因此可將其視為基於磁碟的資料庫系統(Disk-base Database)。
  2. 這裡需要注意的是,頁從緩衝池重新整理回磁碟的操作並不是在每次頁發生更新時觸發,而是通過一種稱為Checkpoint的機制重新整理回磁碟。
  3. 因此為了讓資料庫使用更多的記憶體,強烈建議資料庫伺服器都採用64位的作業系統。
  4. 對於InnoDB儲存引擎而言,其緩衝池的配置通過引數innodb_buffer_pool_size來設定。
  5. 具體來看,緩衝池中快取的資料頁型別有:索引頁、資料頁、undo頁、插入緩衝(insert buffer)、自適應雜湊索引(adaptive hash index)、InnoDB儲存的鎖資訊(lock info)、資料字典資訊(data dictionary)等。
  6. 從InnoDB 1.0.x版本開始,允許有多個緩衝池例項。每個頁根據雜湊值平均分配到不同緩衝池例項中。可以通過引數innodb_buffer_pool_instances來進行配置,該值預設為1。
  7. 從MySQL 5.6版本開始,還可以通過information_schema架構下的表INNODB_BUFFER_POOL_STATS來觀察緩衝的狀態,如執行下列命令可以看到各個緩衝池的使用狀態:
mysql> SELECT POOL_ID,POOL_SIZE,FREE_BUFFERS,DATABASE_PAGES
        -> FROM INNODB_BUFFER_POOL_STATS\G;

2. LRU List、Free List和Flush List

  1. 通常來說,資料庫中的緩衝池是通過LRU(Latest Recent Used)演算法來進行管理的。即最頻繁使用的頁在LRU列表的前端,而最少使用的頁在LRU列表的尾端。
  2. 在InnoDB的儲存引擎中,LRU列表中還加入了midpoint位置。新讀取到的頁,雖然是最新訪問的頁,但並不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。
  3. 在預設配置下,該位置在LRU列表長度的5/8處。midpoint位置可由引數innodb_old_blocks_pct控制。
  4. 在InnoDB儲存引擎中,把midpoint之後的列表稱為old列表,之前的列表稱為new列表。可以簡單地理解為new列表中的頁都是最為活躍的熱點資料。
  5. 那為什麼不採用樸素的LRU演算法,直接將讀取的頁放入到LRU列表的首部呢?這是因為若直接將讀取到的頁放入到LRU的首部,那麼某些SQL操作可能會使緩衝池中的頁被刷新出,從而影響緩衝池的效率。常見的這類操作為索引或資料的掃描操作。這類操作需要訪問表中的多個頁,甚至是全部的頁,而這些頁通常來說又僅在這次查詢操作中需要澳,並不是活躍的熱點資料。如果頁被放入LRU列表的首部,那麼非常可能將所需要的熱點資料頁從LRU列表中移除,而在下一次需要讀取該頁時,InnoDB儲存引擎需要再次訪問磁碟。
  6. 為了解決這個問提,InnoDB儲存殷勤引入了另一個引數來進一步管理LRU列表,這個引數是innodb_old_blocks_time,用於表示頁讀取到mid位置後需要等待多久才會被加入到LRU列表的熱端。因此當需要執行上述所說的SQL操作時,可以通過下面的方法儘可能使LRU列表中熱點資料不被刷出:
mysql> SET GLOBAL innodb_old_blocks_time=1000;

# data or index scan operation
...

mysql> SET GLOBAL innodb_old_blocks_time=0;

LRU列表用來管理已經讀取的頁,但當資料庫剛啟動時,LRU列表是空的,即沒有任何的頁。這時頁都存放在Free列表中。

當頁從LRU列表的old部分加入到new部分時,稱此時發生的操作為page made young,而因為innodb_old_blocks_time的設定而導致頁沒有從old部分移動到new部分的操作稱為page not made young。

可能的情況是Free buffers與Database pages的數量之和不等於Buffer pool size。因為緩衝池中的頁還可能會被分配給自適應雜湊索引、Lock資訊、Insert Buffer等頁,而這部分頁不需要LRU演算法進行維護,因此不存在於LRU列表中。

這裡還有一個重要的觀察變數——Buffer pool hit rate,表示緩衝池的命中率,這個例子中衛100%,說明緩衝池執行狀態非常良好。通常該值不應該小於95%。若發生Buffer pool hit rate的值小於95%這種情況,使用者需要觀察是否是由於全表掃描引起的LRU列表被汙染的問題。

執行命令SHOW ENGINE INNODB STATUS顯示的不是當前的狀態,而是過去某個時間範圍內InnoDB儲存引擎的狀態。從上面的額例子可以發現,Per second averages calculated from the last 24 seconds代表的資訊為過去24秒內的資料庫狀態。

從InnoDB 1.2版本開始,還可以通過表INNODB_BUFFER_POOL_STATS來觀察緩衝池的執行狀態,如:

mysql> SELECT POOL_ID,HIT_RATE,PAGES_MADE_YOUNG,PAGES_NOT_MADE_YOUNG
    ->FROM information_schema.INNODB_BUFFER_POOL_STATS\G;

此外,還可以通過表INNODB_BUFFER_PAGE_LRU來觀察每個LRU列表中每個頁的具體資訊,例如通過下面的語句可以看到緩衝池LRU列表中SPACE為1的表的頁型別:

mysql> SELECT TABLE_NAME,SPACE,PAGE_NUMBER,PAGE_TYPE
    -> FROM INNODB_BUFFER_PAGE_LRU WHERE SPACE = 1;

InnoDB儲存引擎從1.0.x版本開始支援壓縮頁的功能,即將原本16KB的頁壓縮為1KB、2KB、4KB和8KB。對於非16KB的頁,是通過unzip_LRU列表進行管理的。

可以看到LRU列表中一共有1539個頁,而unzip_LRU列表中有156個頁。這裡需要注意的是,LRU中的頁包含了unzip_LRU列表中的頁。

首先,在unzip_LRU列表中隊不同壓縮頁大小的頁進行分別管理。其次,通過夥伴演算法進行記憶體的分配。例如對需要從緩衝池中申請頁為4KB的大小,其過程如下:

檢查4KB的unzip_LRU列表,檢查是否有可用的空閒頁;
若有,則直接使用;
否則,檢查8KB的unzip_LRU列表;
若能夠得到空閒頁,將頁分成2個4KB頁,存放到4KB的unzip_LRU列表;
若不能得到空閒頁,從LRU列表中申請一個16KB的頁,將頁分成1個8KB的頁、2個4KB的頁,分別存放到對應的unzip_LRU列表中。

# 觀察unzip_LRU列表中的頁
mysql> SELECT TABLE_NAME,SPACE,PAGE_NUMBER,COMPRESSED_SIZE 
    -> FROM INNODB_BUFFER_PAGE_LRU
    -> WHERE COMPRESSED_SIZE <> 0;

這時資料庫會通過CHECKPOINT機制將髒頁重新整理回磁碟,而Flush列表中的頁即為髒頁列表。需要注意的是,髒頁既存在於LRU列表中,也存在於Flush列表中。

同LRU列表一樣,Flush列表也可以通過命令SHOW_ENGINE_INNODB_STATUS來檢視,前面例子中的Modified db pages 24673就顯示了髒頁的數量。

# 觀察髒頁
mysql> SELECT TABLE_NAME,SPACE,PAGE_NUMBER,PAGE_TYPE
    -> FROM INNODB_BUFFER_PAGE_LRU
    -> WHERE OLDEST_MODIFICATION>0;

TABLE_NAME為NULL表示該頁屬於系統表空間。

3. 重做日誌緩衝

  1. 重做日誌緩衝一般不需要設定得很大,因為一般情況下每一秒鐘會將重做日誌緩衝重新整理到日誌檔案,因此使用者只需要保證每秒產生的事務量在這個緩衝大小之內即可。該值可由配置引數innodb_log_buffer_size控制,預設為8MB。
  2. 在通常情況下,8MB的重做日誌緩衝池足以滿足絕大部分的應用,因為重做日誌在下列三種情況下會將重做日誌緩衝中的內容重新整理到外部磁碟的重做日誌檔案中:
    Master Thread每一秒將重做日誌緩衝重新整理到重做日誌檔案
    每個事務提交時會將重做日誌緩衝重新整理到重做日誌檔案
    當重做日誌緩衝池剩餘空間小於1/2時,重做日誌緩衝重新整理到重做日誌檔案

4. 額外的記憶體池

  1. 在對一些資料結構的記憶體進行分配時,需要從額外的記憶體池中進行申請,當該區域的記憶體不夠時,會從緩衝池中進行申請。例如,分配了緩衝池(innodb_buffer_pool),但是每個緩衝池中的幀緩衝(frame buffer)還有對應的緩衝控制物件(buffer control block),這些物件記錄了一些諸如LRU、鎖、等待等資訊,而這個物件的記憶體需要從額外記憶體池中申請。

2.4 Checkpoint技術

  1. 為了避免發生資料丟失的問題,當前事務資料庫系統普遍都採用了Write Ahead Log策略,即當事務提交時,先寫重做日誌,再修改頁。當由於發生宕機而導致資料丟失時,通過重做日誌來完成資料的恢復。
  2. 當資料庫發生宕機時,資料庫不需要重做所有的日誌,因為Checkpoint之前的頁都已經重新整理回磁碟。故資料庫只需對Checkpoint後的重做日誌進行恢復。
  3. 此外,當緩衝池不夠用時,根據LRU演算法會溢位最近最少使用的頁,若此頁為髒頁,那麼需要強制執行Checkpoint,將髒頁也就是頁的新版本刷回磁碟。
  4. 重做日誌可以被重用的部分是指這些重做日誌已經不再需要,即當資料庫發生宕機時,資料庫恢復操作不需要這部分的重做日誌,因此這部分就可以被覆蓋重用。若此時重做日誌還需要使用,那麼必須強制產生Checkpoint,將緩衝池中的頁至少重新整理到當前重做日誌的位置。
  5. 在InnoDB儲存引擎內部,有兩種Checkpoint,分別為:

    Sharp Checkpoint
    Fuzzy Checkpoint

Sharp Checkpoint發生在**資料庫關閉**時將所有的髒頁都重新整理回磁碟,這是預設的工作方式,即引數innodb_fast_shutdown=1。 故在InnoDB儲存引擎內部使用Fuzzy Checkpoint進行頁的重新整理,即只重新整理一部分髒頁,而不是重新整理所有的髒頁回磁碟。 在InnoDB儲存引擎中可能發生如下幾種情況的Fuzzy Checkpoint:

Master Thread Checkpoint
FLUSH_LRU_LIST Checkpoint
Async/Sync Flush Checkpoint
Dirty Page too much Checkpoint

FLUSH_LRU_LIST Checkpoint是因為InnoDB儲存引擎需要保證LRU列表中需要有差不多100個空閒頁可供使用。在InnoDB 1.1.x版本之前,需要檢查LRU列表中是否有足夠的可用空間操作發生在使用者查詢執行緒中,顯然這會阻塞使用者的查詢操作。 而在MySQL 5.6版本,也就是InnoDB 1.2.x版本開始,這個檢查被放在了一個單獨的Page Cleaner執行緒中進行,並且使用者可以通過引數innodb_lru_scan_depth控制LRU列表中可用頁的數量,該值預設為1024。 Async/Sync Flush Checkpoint指的是重做日誌檔案不可用的情況,這時需要強制將一些頁重新整理回磁碟,而此時髒頁是從髒頁列表中選取的。若將已經寫入重做日誌的LSN即為redo_lsn,將已經重新整理回磁碟最新頁的LSN記為checkpoint_lsn,則可定義:
checkpoint_age = redo_lsn - checkpoint_lsn
再定義以下的變數:
async_water_mark = 75% * total_redo_log_file_size
sync_water_mark = 90% * total_redo_log_file_size
若每個重做日誌檔案的大小為1GB,並且定義了兩個重做日誌檔案,則重做日誌檔案的總大小為2GB。那麼async_water_mark=1.5GB,sync_water_mark=1.8GB。則

當checkpoint_age < async_water_mark時,不需要重新整理任何髒頁到磁碟;
當async_water_mark < checkpoint_age < sync_water_mark時觸發Async Flush,從Flush列表中重新整理足夠的髒頁回磁碟,使得重新整理後滿足checkpoint_age < async_water_mark;
checkpoint_age > sync_water_mark這種情況很少發生,除非設定的重做日誌檔案太小,並且在進行類似LOAD DATA的BULK INSERT操作。此時觸發Sync Flush操作,從Flush列表中重新整理足夠的髒頁回磁碟,使得重新整理後滿足checkpoint_age < async_water_mark。

可見,Async/Sync Flush Checkpoint是為了保證重做日誌的迴圈使用的可用性。在InnoDB 1.2.x版本之前,Async Flush Checkpoint會阻塞發現問題的使用者查詢執行緒,而Sync Flush Checkpoint會阻塞所有的使用者查詢執行緒,並且等待髒頁重新整理完成。從InnoDB 1.2.x版本開始——也就是MySQL 5.6版本,這部分的重新整理操作同樣放入到了單獨的Page Cleaner Thread中,故不會阻塞使用者查詢執行緒。 innodb_max_dirty_pages_pct值為75表示,當緩衝池中髒頁的數量佔據75%時,強制執行Checkpoint,重新整理一部分的髒頁到磁碟。

2.5 Master Thread工作方式

2.5.1 InnoDB 1.0.x版本之前的Master Thread

  1. Master Thread具有最高的執行緒優先級別。其內部由多個迴圈(loop)組成:主迴圈(loop)、後臺迴圈(background loop)、重新整理迴圈(flush loop)、暫停迴圈(suspend loop)。
  2. Loop被稱為主迴圈,因為大多數的操作是在這個迴圈中,其中有兩大部分的操作——每秒鐘的操作和每10秒鐘的操作。
  3. 每秒一次的操作包括:

    日誌緩衝重新整理到磁碟,即使這個事務還沒有提交(總是)
    合併插入緩衝(可能)
    至多重新整理100個InnoDB的緩衝池中的髒頁到磁碟(可能)
    如果當前沒有使用者活動,則切換到background loop(可能)

**即使某個事務還沒有提交,InnoDB儲存引擎仍然每秒會將重做日誌緩衝中的內容重新整理到重做日誌檔案。這一點是必須要知道的,因為這很好地解釋為什麼再大的事務提交(commit)的時間也是很短的。** InnoDB儲存引擎會判斷當前一秒內發生的IO次數是否小於5次,如果小於5次,InnoDB認為當前的IO壓力很小,可以執行合併插入緩衝的操作。 InnoDB儲存引擎通過判斷當前緩衝池中髒頁的比例(buf_get_modified_ratio_pct)是否超過了配置檔案中innodb_max_dirty_pages_pct這個引數(預設為90,代表90%),如果超過了這個閾值,InnoDB儲存引擎認為需要做磁碟同步的操作,將100個髒頁寫入磁碟。 接著來看每10秒的操作,包括如下內容:

重新整理100個髒頁到磁碟(可能的情況下)
合併至多5個插入緩衝(總是)
將日誌緩衝重新整理到磁碟(總是)
刪除無用的Undo頁(總是)
重新整理100個或者10個髒頁到磁碟(總是)

在以上的過程中,InnoDB儲存引擎會先判斷過去10秒之內磁碟的IO操作是否小於200次,如果是,InnoDB儲存引擎認為當前有足夠的磁碟IO操作能力,因此將100個髒頁重新整理回磁碟。 **(從現在開始我將省略InnoDB後面的“儲存引擎”四個字,珍愛生命)** **對錶進行update、delete這類操作時,原先的行被標記為刪除,但是因為一致性讀(consistent read)的關係,需要保留這些行版本的資訊。但是在full purge過程中,InnoDB會判斷當前事務系統中已被刪除的行是否可以刪除,比如有時候可能還有查詢操作需要讀取之前版本的undo資訊,如果可以刪除,InnoDB會立即將其刪除。**從原始碼中可以發現,InnoDB儲存引擎在執行full purge操作時,每次最多嘗試回收20個undo頁。 然後,InnoDB儲存引擎會判斷緩衝池中髒頁的比例(buf_get_modified_ratio_pct),如果有超過70%的髒頁,則重新整理100個髒頁到磁碟,如果髒頁的比例小於70%,則只需重新整理10%的髒頁到磁碟。**(應該是10個吧)** 接著來看background loop,若當前沒有使用者活動(資料庫空閒時)或者資料庫關閉(shutdown),就會切換到這個迴圈。background loop會執行以下操作:

刪除無用的Undo頁(總是)
合併20個插入緩衝(總是)
跳回到主迴圈(總是)(應該不是總是吧?)
不斷重新整理100個頁直到符合條件(可能,跳轉到flush loop中完成)

若flush loop中也沒有什麼事情可以做了,InnoDB會切換到suspend loop,將Master Thread掛起,等待事件的發生。

2.5.2 InnoDB 1.2.x版本之前的Master Thread

  1. 即使磁碟能在1秒內處理多於100個頁的寫入和20個插入緩衝的合併,但是由於hard coding,Master Thread也只會選擇重新整理100個髒頁和合並20個插入緩衝。同時,當發生宕機需要恢復時,由於很多資料還沒有重新整理回磁碟,會導致恢復的時間可能需要很久,尤其是對於insert buffer來說。
  2. 因此InnoDB Plugin(從InnoDB 1.0.x版本開始)提供了引數innodb_io_capacity,用來表示磁碟IO的吞吐量,預設值為200。對於重新整理到磁碟頁的數量,會按照innodb_io_capacity的百分比來進行控制。規則如下:

    在合併插入緩衝時,合併插入緩衝的數量為innodb_io_capacity值得5%
    在從緩衝區重新整理髒頁時,重新整理髒頁的數量為innodb_io_capacity


但是該值“太大”了,因為InnoDB在每秒重新整理緩衝池和flush loop時會判斷這個值,如果該值大於innodb_max_dirty_pages_pct,才重新整理100個髒頁,如果有很大的記憶體,或者資料庫伺服器的壓力很大,這時重新整理髒頁的速度反而會降低。 Google在這個問題上進行了測試,證明20並不是一個最優值。而從InnoDB 1.0.x版本開始,innodb_max_dirty_pages_pct預設值變為了75,和Google測試的80比較接近。這樣既可以加快重新整理髒頁的頻率,又能保證了磁碟IO的負載。 隨著innodb_adaptive_flushing引數的引入,InnoDB儲存引擎會通過一個名為buf_flush_get_desired_flush_rate的函式來判斷需要重新整理髒頁最合適的數量。粗略地翻閱原始碼後發現buf_flush_get_desired_flush_rate通過判斷產生重做日誌的速度來決定最合適的重新整理髒頁數量。因此,當髒頁的比例小於innodb_max_dirty_pages_pct時,也會重新整理一定量的髒頁。 還有一個改變是:之前每次進行full purge操作時,最多回收20個Undo頁,從InnoDB 1.0.x版本開始引入了引數innodb_purge_batch_size,該引數可以控制每次full purge回收的Undo頁的數量。 可以看到當前主迴圈運行了2188次,但是迴圈中的每秒掛起(sleep)的操作只運行了1537次。這是因為InnoDB對其內部進行了一些優化,當壓力大時並不總是等待1秒。因此,並不能認為1_second和sleeps的值總是相等的。在某些情況下,可以通過兩者之間的差值的比較來反映當前資料庫的負載壓力。

2.6 InnoDB關鍵特性

  1. InnoDB的關鍵特性包括:
    插入緩衝(Insert Buffer)
    兩次寫(Double Write)
    自適應雜湊索引(Adaptive Hash Index)
    非同步IO(Async IO)
    重新整理鄰接頁(Flush Neighbor Page)

2.6.1 插入緩衝

1. Insert Buffer

  1. 其實不然,InnoDB緩衝池中有Insert Buffer資訊固然不錯,但是Insert Buffer和資料頁一樣,也是物理頁的一個組成部分。
  2. 其中a列是自增長的,若對a列插入NULL值,則由於其具有AUTO_INCREMENT屬性,其值會自動增長。同時頁中的記錄按a的值進行順序存放。在一般情況下,不需要隨機讀取另一個頁中的記錄。因此,對於這類情況的插入操作,速度是非常快的。
  3. 並不是所有的主鍵插入都是順序的。若主鍵類是UUID這樣的類,那麼插入和輔助索引一樣,同樣是隨機的。即使主鍵是自增型別,但是插入的是指定的值,而不是NULL值,那麼同樣可能導致插入並非連續的情況。
  4. 但是不可能每張表上只有一個聚集索引,更多情況下,一張表上有多個非聚集的輔助索引(secondary index)。比如,使用者需要按照b這個欄位進行查詢,並且b這個欄位不是唯一的,即表是按如下的SQL語句定義的:
CREATE TABLE t {
    a INT AUTO_INCREMENT,
    b VARCHAR(30),
    PRIMARY KEY(a),
    key(b)
);

在進行插入操作時,資料頁的存放還是按主鍵a進行順序存放的,但是對於非聚集索引葉子節點的插入不再是順序的了,這時就需要離散地訪問非聚集索引頁,由於隨機讀取的存在而導致了插入操作效能下降。當然這並不是這個b欄位上索引的錯誤,而是因為B+樹的特性決定了非聚集索引插入的離散性。

InnoDB開創性地設計了Insert Buffer,對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer物件中,好似欺騙。資料庫這個非聚集的索引已經插入到葉子節點,而實際並沒有,只是存放在另一個位置。然後再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge操作,這時通常能將多個插入合併到一個操作中(因為在一個索引頁中),這就大大提高了對於非聚集索引插入的效能。

然而Insert Buffer的使用需要同時滿足以下兩個條件:

索引是輔助索引(secondary index)
索引不是唯一(unique)的

輔助索引不能是唯一的,因為在插入緩衝時,資料庫並不去查詢索引頁來判斷插入的記錄的唯一性。

seg size顯示了當前Insert Buffer的大小為11336×16KB,大約為177MB;free list len代表了空閒列表的長度;size代表了已經合併記錄頁的數量。

Inserts代表了插入的記錄數;merged recs代表了合併的插入記錄數量;merges代表合併的次數,也就是實際讀取頁的次數。merges:merged recs大約為1:3,代表了插入緩衝將對於非聚集索引頁的離散IO邏輯請求大約降低了2/3。

正如前面所說的,目前Insert Buffer存在一個問題是:在寫密集的情況下,插入緩衝會佔用過多的緩衝池記憶體(innodb_buffer_pool),預設最大可以佔用到1/2的緩衝池記憶體。

2. Change Buffer

  1. 從這個版本開始,InnoDB儲存引擎可以對DML操作——INSERT、DELETE、UPDATE都進行緩衝,他們分別是:Insert Buffer、Delete Buffer、Purge Buffer。
  2. 當然和之前Insert Buffer一樣,Change Buffer適用的物件依然是非唯一的輔助索引。
  3. 因為Delete Buffer對應UPDATE操作的第一個過程,即將記錄標記為刪除。Purge Buffer對應UPDATE操作的第二個過程,即將記錄真正的刪除。
  4. innodb_change_buffer_max_size值預設為25,表示最多使用1/4的緩衝池記憶體空間。而需要注意的是,該引數的最大有效值為50。

3. Insert Buffer的內部實現

  1. 可能令絕大部分使用者感到吃驚的是,Insert Buffer的資料結構是一棵B+樹。在MySQL 4.1之前的版本中每張表有一棵Insert Buffer B+樹。而在現在的版本中,全域性只有一棵Insert Buffer B+樹,負責對所有的表的輔助索引進行Insert Buffer。
  2. 非頁節點存放的是查詢的search key(鍵值),其構造是:| space | marker | offset |。
  3. search key一共佔用9個位元組,**其中space表示待插入記錄所在表的表空間id,在InnoDB儲存引擎中,每個表有一個唯一的space id,可以通過space id查詢得知是哪張表。**space佔用4位元組。marker佔用1位元組,它是用來相容老版本的Insert Buffer。offset表示頁所在的偏移量,佔用4位元組。
  4. 當一個輔助索引要插入到頁(space, offset)時,如果這個頁不在緩衝池中,那麼InnoDB首先根據上述規則構造一個search key,接下來查詢Insert Buffer這棵B+樹,然後再將這條記錄插入到Insert Buffer B+樹的葉子節點中。
  5. IBUF_REC_OFFSET_COUNT是儲存兩個位元組的整數,用來排序每個記錄進入Insert Buffer的順序。因為從InnoDB 1.0.x開始支援Change Buffer,所以這個值同樣記錄進入Insert Buffer**(應該是Change Buffer吧?)**的順序。通過這個順序回放(replay)才能得到記錄的正確值。
  6. 從Insert Buffer葉子節點的第5列開始,就是實際插入記錄的各個欄位了。因此較之原插入記錄,Insert Buffer B+樹的葉子節點記錄需要額外13位元組的開銷。
  7. 因為啟用Insert Buffer索引後,輔助索引頁(space,page_no)中的記錄可能被插入到Insert Buffer B+樹中,所以為了保證每次Merge Insert Buffer頁必須成功,還需要有一個特殊的頁用來標記每個輔助索引頁(space,page_no)的可用空間。這個頁的型別為Insert Buffer Bitmap。
  8. 每個Insert Buffer Bitmap頁用來追蹤16384個輔助索引頁,也就是256個區(Extent)。每個Insert Buffer Bitmap頁都在16384個頁的第二個頁中。每個輔助索引頁在Insert Buffer Bitmap頁中佔用4位:
    IBUF_BITMAP_FREE 2 表示該輔助索引頁中的可用空間數量
    IBUF_BITMAP_BUFFERED 1 1表示該輔助索引頁有記錄被快取在Insert Buffer B+樹中
    IBUF_BITMAP_IBUF 1 1表示該頁為Insert Buffer B+樹的索引頁

4. Merge Insert Buffer

  1. 概括地說,Merge Insert Buffer的操作可能發生在以下幾種情況下:

    輔助索引頁被讀取到緩衝池時
    Insert Buffer Bitmap頁追蹤到該輔助索引頁已無空間可用時
    Master Thread

**第一種情況為當輔助索引頁被讀取到緩衝池中時,例如這在執行正常的SELECT查詢操作,這時需要檢查Insert Buffer Bitmap頁,然後確認該輔助索引頁是否有記錄存放於Insert Buffer B+樹中。若有,則將Insert Buffer B+樹中該頁的記錄插入到該輔助索引頁中。可以看到對該頁多次的記錄操作通過一次操作合併到了原有的輔助索引頁中,因此效能會有大幅提高。** **Insert Buffer Bitmap頁用來追蹤每個輔助索引頁的可用空間,並至少有1/32頁的空間。若插入輔助索引記錄時檢測到插入記錄後可用空間會小於1/32頁,則會強制進行一次合併操作,即強制讀取輔助索引頁,將Insert Buffer B+樹種該頁的記錄及待插入的記錄插入到輔助索引頁中。**

2.6.2 兩次寫

  1. doublewrite由兩部分組成,一部分是記憶體中的doublewrite buffer,大小為2MB,另一部分是物理磁碟上共享表空間中連續的128個頁,即2個區(extent),大小同樣為2MB。在對緩衝池的髒頁進行重新整理時,並不直接寫磁碟,而是會通過memcpy函式將髒頁先複製到記憶體中的doublewrite buffer,之後通過doublewrite buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁碟上,然後馬上呼叫fsync函式,同步磁碟,避免緩衝寫帶來的問題。在這個過程中,因為doublewrite頁是連續的,因此這個過程是順序寫的,開銷並不是很大。在完成doublewrite頁的寫入後,再將doublewrite buffer中的頁寫入各個表空間檔案中,此時的寫入則是離散的。
  2. 如果作業系統在將頁寫入磁碟的過程中發生了崩潰,在恢復過程中,InnoDB儲存引擎可以從共享表空間中的doublewrite中找到該頁的一個副本,將其複製到表空間檔案,再應用重做日誌。
  3. 有些檔案系統本身就提供了部分寫失效的防範機制,如ZFS檔案系統。在這種情況下,使用者就不要啟用doublewrite了。

2.6.3 自適應雜湊索引

  1. InnoDB儲存引擎會自動根據訪問的頻率和模式來自動地為某些熱點頁建立雜湊索引。
  2. 訪問模式一樣指的是查詢的條件一樣,若交替進行上述兩種查詢,那麼InnoDB儲存引擎不會對該頁構造AHI。此外AHI還有如下的要求:
    以該模式訪問了100次
    頁通過該模式訪問了N次,其中N=頁中記錄*1/16

2.6.4 非同步IO

  1. 使用者可以在發出一個IO請求後立即再發出另一個IO請求,當全部IO請求傳送完畢後,等待所有IO操作的完成,這就是AIO。
  2. 而AIO會判斷到這三個頁是連續的(顯然可以通過(space,page_no)得知)。因此AIO底層會發送一個IO請求,從(8,6)開始,讀取48KB的頁。
  3. 在InnoDB1.1.x之前,AIO的實現通過InnoDB儲存引擎中的程式碼來模擬實現。而從InnoDB1.1.x開始(InnoDB Plugin不支援),提供了核心級別的AIO的支援,稱為Native AIO。
  4. Windows系統和Linux系統都提供Native AIO支援,而Mac OSX系統則未提供。
  5. 引數innodb_use_native_aio用來控制是否啟用Native AIO,在Linux作業系統下,預設值為ON。
  6. 官方的測試顯示,啟用Native AIO,恢復速度可以提供75%。
  7. 在InnoDB儲存引擎中,read ahead方式的讀取都是通過AIO完成,髒頁的重新整理,即磁碟的寫入操作則全部由AIO完成。

2.6.5 重新整理鄰接頁

  1. 其工作原理是:當重新整理一個髒頁時,InnoDB儲存引擎會檢測該頁所在區(extent)的所有頁,如果是髒頁,那麼一起進行重新整理。這樣做的好處顯而易見,通過AIO可以將多個IO寫入操作合併為一個IO操作,故該工作機制在傳統機械磁碟下有著顯著的優勢。
  2. 對於傳統機械硬碟建議啟用該特性,而對於固態硬碟有著超高的IOPS效能的磁碟,則建議將該引數設定為0,即關閉此特性。

2.7 啟動、關閉與恢復

  1. 在關閉時,引數innodb_fast_shutdown影響著表的儲存引擎為InnoDB的行為。該引數可取值為0,1,2,預設值為1。

    0表示在MySQL資料庫關閉時,InnoDB需要完成所有的full purge和merge insert buffer,並且將所有的髒頁重新整理回磁碟。這需要一些時間,有時甚至需要幾個小時來完成。如果在進行InnoDB升級時,必須將這個引數設定為0,然後再關閉資料庫。
    1是引數innodb_fast_shutdown的預設值,表示不需要完成上述的full purge和merge insert buffer操作,但是在緩衝池中的一些資料髒頁還是會重新整理回磁碟。
    2表示不完成full purge和merge insert buffer操作,也不將緩衝池中的資料髒頁寫回磁碟,而是將日誌都寫入日誌檔案。這樣不會有任何事務的丟失,但是下次MySQL資料庫啟動時,會進行恢復操作(recovery)。

引數innodb_force_recovery影響了整個InnoDB儲存引擎恢復的狀況。該引數值預設為0,代表當發生需要恢復時,進行所有的恢復操作,當不能進行有效恢復時,如資料頁發生了corruption,MySQL資料庫可能發生宕機(crash),並把錯誤寫入錯誤日誌中去。 引數innodb_force_recovery還可以設定為6個非零值:1~6。大的數字表示包含了前面所有小數字表示的影響:

1(SRV_FORCE_IGNORE_CORRUPT):忽略檢查到的corrupt頁
2(SRV_FORCE_NO_BACKGROUND):阻止Master Thread執行緒的執行,如Master Thread執行緒需要進行full purge操作,而這會導致crash
3(SRV_FORCE_NO_TRX_UNDO):不進行事務的回滾操作
4(SRV_FORCE_NO_IBUF_MERGE):不進行插入緩衝的合併操作
5(SRV_FORCE_NO_UNDO_LOG_SCAN):不檢視撤銷日誌(Undo Log),InnoDB儲存引擎會將未提交的事務視為已提交。
6(SRV_FROCE_NO_LOG_REDO):不進行前滾的操作。

需要注意的是,在設定了引數innodb_force_recovery大於0後,使用者可以對標進行select、create和drop操作,但insert、update和delete這類DML操作是不允許的。 可以看到,採用預設的策略,即將innodb_force_recovery設為0,InnoDB會在每次啟動後對發生問題的表進行恢復操作。通過錯誤日誌檔案,可知這次回滾操作需要回滾8867280行記錄,差不多總共進行了9分鐘。 這裡出現了“!!!”,InnoDB警告已經將innodb_force_recovery設定為3,不會進行回滾操作了,因此資料庫很快啟動完了。但是使用者應該小心當前資料庫的狀態,並仔細確認是否不需要回滾事務的操作。

第3章 檔案

  1. 引數檔案:告訴MySQL例項啟動時去哪裡可以找到資料庫檔案,並且指定某些初始化引數,這些引數定義了某種記憶體結構的大小等設定,還會介紹各種引數的型別。
  2. 日誌檔案:用來記錄MySQL例項對某種條件做出響應時寫入的檔案,如錯誤日誌檔案、二進位制檔案、慢查詢日誌檔案、查詢日誌檔案等。
  3. socket檔案:當使用UNIX域套接字方式進行連線時需要的檔案
  4. MySQL表結構檔案:用來存放MySQL表結構定義檔案
  5. 儲存引擎檔案:因為MySQL表儲存引擎的關係,每個儲存引擎都會有自己的檔案來儲存各種資料。這些儲存引擎真正儲存了記錄和索引等資料。

3.1 引數檔案

  1. MySQL稍微有所不同,MySQL例項可以不需要引數檔案,這時所有的引數值取決於編譯MySQL時指定的預設值和原始碼中指定引數的預設值。但是,如果MySQL例項在預設的資料庫目錄下找不到mysql架構(應該是指mysql.host),則啟動同樣會失敗。
  2. MySQL的mysql架構中記錄了訪問該例項的許可權,當找不到這個架構時,MySQL例項不會成功啟動。

3.1.1 什麼是引數

  1. 可以通過命令SHOW_VARIABLES檢視資料庫中的所有引數,也可以通過LIKE來過濾引數名。從MySQL5.1版本開始,還可以通過information_schema架構下的GLOBAL_VARIABLES檢視來進行查詢:
mysql> SELECT * FROM GLOBAL_VARIABLES
    -> WHERE VARIABLE_NAME LIKE 'innodb_buffer%' \G;
mysql> SHOW VARIABLES LIKE 'innodb_buffer%' \G;

3.1.2 引數型別

  1. 動態引數意味著可以在MySQL例項執行中進行更改,靜態引數說明在整個例項生命週期內都不得進行更改,就好像是隻讀(read only)的。
  2. 這裡看到global和session關鍵字,它們表明該引數的修改是基於當前會話還是整個例項的生命週期。有些動態引數只能在會話中進行修改,如autocommit;而有些引數修改完後,在整個例項宣告週期中都會生效,如binlog_cache_size;而有些引數既可以在會話中又可以在整個例項的生命週期內生效,如read_buffer_size。舉例:
mysql> SET read_buffer_size=524288;
mysql> SELECT @@session.read_buffer_size\G;
# @@session.read_buffer_size: 524288
mysql> SELECT @@global.read_buffer_size\G;
# @@global.read_buffer_size: 2093056

使用者同樣可以直接使用[email protected]@global|@@session來更改。

mysql> SET @@global.read_buffer_size=1048576

這裡需要注意的是,對變數的全域性值進行了修改,在這次的例項生命週期內都有效,但MySQL例項本身並不會對引數檔案中的值進行修改。

3.2 日誌檔案

3.2.1 錯誤日誌

  1. 使用者可以通過命令SHOW VARIABLES LIKE ‘log_error’來定位該檔案。可以看到錯誤檔案的路徑和檔名,在預設情況下錯誤檔案的檔名為伺服器的主機名。如上面看到的,該主機名為stargazer,所以錯誤檔名為stargazer.err。

3.2.2 慢查詢日誌

  1. 3.2.1小節提到可以通過錯誤日誌得到一些關於資料庫優化的資訊,而慢查詢日誌(slow log)可幫助DBA定位可能存在問題的SQL語句,從而進行SQL語句層面的優化。例如,可以在MySQL啟動時設一個閾值,將執行時間超過該值的所有SQL語句都記錄到慢查詢日誌檔案中。DBA每天或每過一段時間對其進行檢查,確認是否有SQL語句需要進行優化。該閾值可以通過引數long_query_time來設定,預設值為10,代表10秒。
  2. 在預設情況下,MySQL資料庫並不啟動慢查詢日誌,使用者需要手工將這個引數設為ON。
  3. 另一個和慢查詢日誌有關的引數是log_queries_not_using_indexes,如果執行的SQL語句沒有使用索引,則MySQL資料庫同樣會將這條SQL語句記錄到慢查詢日誌檔案。
  4. MySQL 5.6.5版本開始新增一個引數log_throttle_queries_not_using_indexes,用來表示每分鐘允許記錄到slow log的且未使用索引的SQL語句次數。該值預設為0,表示沒有限制。在生產環境下,若沒有使用索引,此類SQL語句會頻繁地被記錄到slow log,從而導致slow log檔案的大小不斷增加,故DBA可通過此引數進行配置。
  5. 如果使用者希望得到執行時間最長的10條SQL語句,可以執行如下命令:
$ mysqldumpslow -s al -n 10 david.log

MySQL 5.1開始可以將慢查詢的日誌記錄放入一張表中,這使得使用者的查詢更加方便和直觀。慢查詢表在mysql架構下,名為slow_log,其表結構定義如下:

mysql> SHOW CREATE TABLE mysql.slow_log\G;
# ...

引數log_output指定了慢查詢輸出的格式,預設為FILE,可以將它設為TABLE,然後就可以查詢mysql架構下的slow_log表了。

檢視slow_log表的定義發現該表使用的是CSV引擎,對大資料量下的查詢效率可能不高。使用者可以把slow_log表的引擎轉換到MyISAM,並在start_time列上新增索引以進一步提高查詢的效率。但是,如果已經啟動了慢查詢,將會提示錯誤。所以先關掉(SET GLOBAL slow_query_log=off),再改。

InnoSQL版本加強了對於SQL語句的捕獲方式。在原版MySQL的基礎上在slow log中增加了對於邏輯讀取(logical reads)和物理讀取(physical reads)的統計。這裡的物理讀取是指從磁碟進行IO讀取的次數,邏輯讀取包含所有的讀取。

3.2.3 查詢日誌

  1. 查詢日誌記錄了所有對MySQL資料庫請求的資訊,無論這些請求是否得到了正確的執行。預設檔名為:主機名.log。

3.2.4 二進位制日誌

  1. 二進位制日誌(binary log)記錄了對MySQL資料庫執行更改的所有操作,但是不包括SELECT和SHOW這類操作,因為這類操作對資料本身並沒有修改。然而,若操作本身並沒有導致資料庫發生變化,那麼該操作可能也會寫入二進位制檔案。
  2. 從上述例子可以看到,MySQL資料庫首先進行UPDATE操作,從返回的結果看到Changed為0,這意味著該操作並沒有導致資料庫的變化。但是通過命令SHOW BINLOG EVENT可以看出在二進位制日誌中的確進行了記錄。
  3. 總的來說,二進位制日誌主要有以下幾種作用:

    恢復(recovery):某些資料的恢復需要二進位制日誌,例如,在一個數據庫全部檔案恢復後,使用者可以通過二進位制日誌進行point-in-time的恢復。
    複製(replication):其原理與恢復類似,通過複製和執行二進位制日誌使一臺遠端的MySQL資料庫(一般稱為slave或standby)與一臺MySQL資料庫(一般稱為master或primary)進行實時同步。
    審計(audit):使用者可以通過二進位制日誌中的資訊來進行審計,判斷是否有對資料庫進行注入的攻擊。

通過配置引數log_bin[=name]可以啟動二進位制日誌。如果不指定name,則預設二進位制日誌檔名為主機名,字尾名為二進位制日誌的序列號,所在路徑為資料庫所在目錄(datadir)。 bin_log.index為二進位制的索引檔案,用來儲存過往產生的二進位制日誌序號,在通常情況下,不建議手動修改這個檔案。 **根據MySQL官方手冊中的測試表明,開啟二進位制日誌會使效能下降1%。但考慮到可以使用複製(replication)和point-in-time的恢復,這些效能損失絕對是可以且應該被接受的。** 引數max_binlog_size指定了單個二進位制檔案的最大值,如果超過該值,則產生新的二進位制檔案,字尾名+1,並記錄到.index檔案。從MySQL 5.0開始的預設值為1 073 741 824,代表1G(在之前版本中max_binlog_size預設大小為1.1G)。 **當使用事務的表儲存引擎(如InnoDB儲存引擎)時,所有未提交的二進位制日誌會被記錄到一