MySQL的內部組件結構如下:
大體來說,MySQL 可以分為 Server 層和存儲引擎層兩部分。
Server層
主要包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等。
存儲引擎層
存儲引擎層負責數據的存儲和提取。其架構模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎。也就是說如果在create table時不指定表的存儲引擎類型,默認會給你設置存儲引擎為InnoDB。
連接器
由于MySQL是開源的,它有非常多種類的客戶端:navicat,mysql front,jdbc,SQLyog等非常豐富的客戶端,包括各種編程語言實現的客戶端連接程序,這些客戶端要向mysql發起通信都必須先跟Server端建立通信連接,而建立連接的工作就是有連接器完成的。
第一步,先連接到這個數據庫上,這時候接待你的就是連接器。連接器負責跟客戶端建立連接、獲取權限、維持和管理連接。連接命令一般是這么寫的:
[root@192 ~]# mysql -h host[數據庫地址] -u root[用戶] -p root[密碼] -P 3306
連接命令中的 mysql 是客戶端工具,用來跟服務端建立連接。在完成經典的 TCP 握手后,連接器就要開始認證你的身份,這個時候用的就是你輸入的用戶名和密碼。
- 如果用戶名或密碼不對,會收到一個"Access denied for user"的錯誤,然后客戶端程序結束執行。
- 如果用戶名密碼認證通過,連接器會到權限表里面查出擁有的權限。之后,這個連接里面的權限判斷邏輯,都將依賴于此時讀到的權限。
這就意味著,一個用戶成功建立連接后,即使用管理員賬號對這個用戶的權限做了修改,也不會影響已經存在連接的權限。修改完成后,只有再新建的連接才會使用新的權限設置。
查詢緩存
連接建立完成后,你就可以執行 select 語句了。執行邏輯就會來到第二步:查詢緩存。
MySQL 拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。如果你的查詢能夠直接在這個緩存中找到 key,那么這個 value 就會被直接返回給客戶端。
如果語句不在查詢緩存中,就會繼續后面的執行階段。執行完成后,執行結果會被存入查詢緩存中。你可以看到,如果查詢命中緩存,MySQL 不需要執行后面的復雜操作,就可以直接返回結果,這個效率會很高。
大多數情況查詢緩存就是個雞肋,為什么呢?
因為查詢緩存往往弊大于利。查詢緩存的失效非常頻繁,只要有對一個表的更新,這個表上所有的查詢緩存都會被清空。因此很可能你費勁地把結果存起來,還沒使用呢,就被一個更新全清空了。對于更新壓力大的數據庫來說,查詢緩存的命中率會非常低。
一般建議在靜態表里使用查詢緩存,什么叫靜態表呢?就是一般極少更新的表。比如,一個系統配置表、字典表,那這張表上的查詢才適合使用查詢緩存。好在 MySQL 也提供了這種“按需使用”的方式,可以將my.cnf參數 query_cache_type 設置成 DEMAND。
my.cnf
#query_cache_type有3個值 0代表關閉查詢緩存OFF,1代表開啟ON,2(DEMAND)代表當sql語句中有SQL_CACHE關鍵詞時才緩存
query_cache_type=2
這樣對于默認的 SQL 語句都不使用查詢緩存。而對于確定要使用查詢緩存的語句,可以用 SQL_CACHE 顯式指定,像下面這個語句一樣:
mysql> select SQL_CACHE * from test where ID=5;
查看當前MySQL實例是否開啟緩存機制
mysql> show global variables like "%query_cache_type%";
MySQL 8.0已經移除了查詢緩存功能
分析器
如果沒有命中查詢緩存,就要開始真正執行語句了。首先,MySQL 需要知道你要做什么,因此需要對 SQL 語句做解析。
分析器先會做“詞法分析”。你輸入的是由多個字符串和空格組成的一條 SQL 語句,MySQL 需要識別出里面的字符串分別是什么,代表什么。
MySQL 從你輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字符串“T”識別成“表名 T”,把字符串“ID”識別成“列 ID”。
做完了這些識別以后,就要做“語法分析”。根據詞法分析的結果,語法分析器會根據語法規則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法。
如果你的語句不對,就會收到“You have an error in your SQL syntax”的錯誤提醒,比如下面這個語句 from 寫成了 “rom”。
mysql> select * fro test where id=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fro test where id=1' at line 1
下圖是分析器對SQL的分析過程步驟
SQL語句經過分析器分析之后,會生成一個這樣的語法樹
優化器
經過了分析器,MySQL 就知道你要做什么了。在開始執行之前,還要先經過優化器的處理。
優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序;以及一些MySQL自己內部的優化機制。
執行器
開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤,如下所示 (在工程實現上,如果命中查詢緩存,會在查詢緩存返回結果的時候,做權限驗證)。
mysql> select * from test where id=10;
如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。
Innodb底層原理與Mysql日志機制
redo log重做日志關鍵參數
innodb_log_buffer_size:設置redo log buffer大小參數,默認16M ,最大值是4096M,最小值為1M。
show variables like '%innodb_log_buffer_size%';
innodb_log_group_home_dir:設置redo log文件存儲位置參數,默認值為"./",即innodb數據文件存儲位置,其中的 ib_logfile0 和 ib_logfile1 即為redo log文件。
show variables like '%innodb_log_group_home_dir%';
innodb_log_files_in_group:設置redo log文件的個數,命名方式如: ib_logfile0, iblogfile1… iblogfileN。默認2個,最大100個。
show variables like '%innodb_log_files_in_group%';
innodb_log_file_size:設置單個redo log文件大小,默認值為48M。最大值為512G,注意最大值指的是整個 redo log系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size)不能大于最大值512G。
show variables like '%innodb_log_file_size%';
redo log 寫入磁盤過程分析
redo log 從頭開始寫,寫完一個文件繼續寫另一個文件,寫到最后一個文件末尾就又回到第一個文件開頭循環寫,如下面這個圖所示。
write pos 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。
checkpoint 是當前要擦除的位置,也是往后推移并且循環的,擦除記錄前要把記錄更新到數據文件里。
write pos 和 checkpoint 之間的部分就是空著的可寫部分,可以用來記錄新的操作。如果 write pos 追上checkpoint,表示redo log寫滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。
innodb_flush_log_at_trx_commit:這個參數控制 redo log 的寫入策略,它有三種可能取值:
- 設置為0:表示每次事務提交時都只是把 redo log 留在 redo log buffer 中,數據庫宕機可能會丟失數據。
- 設置為1(默認值):表示每次事務提交時都將 redo log 直接持久化到磁盤,數據最安全,不會因為數據庫宕機丟失數據,但是效率稍微差一點,線上系統推薦這個設置。
- 設置為2:表示每次事務提交時都只是把 redo log 寫到操作系統的緩存page cache里,這種情況如果數據庫宕機是不會丟失數據的,但是操作系統如果宕機了,page cache里的數據還沒來得及寫入磁盤文件的話就會丟失數據。
InnoDB 有一個后臺線程,每隔 1 秒,就會把 redo log buffer 中的日志,調用 操作系統函數 write 寫到文件系統的 page cache,然后調用操作系統函數 fsync 持久化到磁盤文件。
redo log寫入策略參看下圖:
# 查看innodb_flush_log_at_trx_commit參數值:
show variables like 'innodb_flush_log_at_trx_commit';
# 設置innodb_flush_log_at_trx_commit參數值(也可以在my.ini或my.cnf文件里配置):
set global innodb_flush_log_at_trx_commit=1;
binlog二進制歸檔日志
binlog二進制日志記錄保存了所有執行過的修改操作語句,不保存查詢操作。如果 MySQL 服務意外停止,可通過二進制日志文件排查,用戶操作或表結構操作,從而來恢復數據庫數據。
啟動binlog記錄功能,會影響服務器性能,但如果需要恢復數據或主從復制功能,則好處則大于對服務器的影響。
# 查看binlog相關參數
show variables like '%log_bin%';
MySQL5.7 版本中,binlog默認是關閉的,8.0版本默認是打開的。上圖中log_bin的值是OFF就代表binlog是關閉狀態,打開binlog功能,需要修改配置文件my.ini(windows)或my.cnf(linux),然后重啟數據庫。
在配置文件中的[mysqld]部分增加如下配置:
# log-bin設置binlog的存放位置,可以是絕對路徑,也可以是相對路徑,這里寫的相對路徑,則binlog文件默認會放在data數據目錄下
log-bin=mysql-binlog
# Server Id是數據庫服務器id,隨便寫一個數都可以,這個id用來在mysql集群環境中標記唯一mysql服務器,集群環境中每臺mysql服務器的id不能一樣,不加啟動會報錯
server-id=1
# 其他配置
binlog_format = row # 日志文件格式,下面會詳細解釋
expire_logs_days = 15 # 執行自動刪除距離當前15天以前的binlog日志文件的天數, 默認為0, 表示不自動刪除
max_binlog_size = 200M # 單個binlog日志文件的大小限制,默認為 1GB
重啟數據庫后再去看data數據目錄會多出兩個文件,第一個就是binlog日志文件,第二個是binlog文件的索引文件,這個文件管理了所有的binlog文件的目錄。
當然也可以執行命令查看有多少binlog文件
show binary logs;
show variables like '%log_bin%';
log_bin:binlog日志是否打開狀態
log_bin_basename:是binlog日志的基本文件名,后面會追加標識來表示每一個文件,binlog日志文件會滾動增加
log_bin_index:指定的是binlog文件的索引文件,這個文件管理了所有的binlog文件的目錄。
sql_log_bin:sql語句是否寫入binlog文件,ON代表需要寫入,OFF代表不需要寫入。如果想在主庫上執行一些操作,但不復制到slave庫上,可以通過修改參數sql_log_bin來實現。比如說,模擬主從同步復制異常。
binlog 的日志格式
用參數 binlog_format 可以設置binlog日志的記錄格式,mysql支持三種格式類型:
- STATEMENT:基于SQL語句的復制,每一條會修改數據的sql都會記錄到master機器的bin-log中,這種方式日志量小,節約IO開銷,提高性能,但是對于一些執行過程中才能確定結果的函數,比如UUID()、SYSDATE()等函數如果隨sql同步到slave機器去執行,則結果跟master機器執行的不一樣。
- ROW:基于行的復制,日志中會記錄成每一行數據被修改的形式,然后在slave端再對相同的數據進行修改記錄下每一行數據修改的細節,可以解決函數、存儲過程等在slave機器的復制問題,但這種方式日志量較大,性能不如Statement。舉個例子,假設update語句更新10行數據,Statement方式就記錄這條update語句,Row方式會記錄被修改的10行數據。
- MIXED:混合模式復制,實際就是前兩種模式的結合,在Mixed模式下,MySQL會根據執行的每一條具體的sql語句來區分對待記錄的日志形式,也就是在Statement和Row之間選擇一種,如果sql里有函數或一些在執行時才知道結果的情況,會選擇Row,其它情況選擇Statement,推薦使用這一種。
binlog寫入磁盤機制
binlog寫入磁盤機制主要通過 sync_binlog 參數控制,默認值是 0。
- 為0的時候,表示每次提交事務都只 write 到page cache,由系統自行判斷什么時候執行 fsync 寫入磁盤。雖然性能得到提升,但是機器宕機,page cache里面的 binlog 會丟失。
- 也可以設置為1,表示每次提交事務都會執行 fsync 寫入磁盤,這種方式最安全。
- 還有一種折中方式,可以設置為N(N>1),表示每次提交事務都write 到page cache,但累積N個事務后才 fsync 寫入磁盤,這種如果機器宕機會丟失N個事務的binlog。
發生以下任何事件時, binlog日志文件會重新生成:
- 服務器啟動或重新啟動
- 服務器刷新日志,執行命令flush logs
- 日志文件大小達到 max_binlog_size 值,默認值為 1GB
刪除 binlog 日志文件
刪除當前的binlog文件
reset master;
# 刪除指定日志文件之前的所有日志文件,下面這個是刪除6之前的所有日志文件,當前這個文件不刪除
purge master logs to 'mysql-binlog.000006';
# 刪除指定日期前的日志索引中binlog日志文件
purge master logs before '2023-01-21 14:00:00';
查看 binlog 日志文件
可以用mysql自帶的命令工具 mysqlbinlog 查看binlog日志內容
# 查看bin-log二進制文件(命令行方式,不用登錄mysql)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 # 查看bin-log二進制文件(帶查詢條件)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 start-datetime="2023-01-21 00:00:00" stop-datetime="2023-02-01 00:00:00" start-position="5000" stop-position="20000"
執行mysqlbinlog命令
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007
查出來的binlog日志文件內容如下:
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#230127 21:13:51 server id 1 end_log_pos 123 CRC32 0x084f390f Start: binlog v 4, server v 5.7.25-log created 230127 21:13:51 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
# at 123
#230127 21:13:51 server id 1 end_log_pos 154 CRC32 0x672ba207 Previous-GTIDs
# [empty]
# at 154
#230127 21:22:48 server id 1 end_log_pos 219 CRC32 0x8349d010 Anonymous_GTID last_committed=0 sequence_number=1 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 219
#230127 21:22:48 server id 1 end_log_pos 291 CRC32 0xbf49de02 Query thread_id=3 exec_time=0 error_code=0
SET TIMESTAMP=1674825768/*!*/;
SET @@session.pseudo_thread_id=3/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1342177280/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 291
#230127 21:22:48 server id 1 end_log_pos 345 CRC32 0xc4ab653e Table_map: `test`.`account` mapped to number 99
# at 345
#230127 21:22:48 server id 1 end_log_pos 413 CRC32 0x54a124bd Update_rows: table id 99 flags: STMT_END_F
### UPDATE `test`.`account`
### WHERE
### @1=1
### @2='lilei'
### @3=1000
### SET
### @1=1
### @2='lilei'
### @3=2000
# at 413
#230127 21:22:48 server id 1 end_log_pos 444 CRC32 0x23355595 Xid = 10
COMMIT/*!*/;
# at 444
。。。
能看到里面有具體執行的修改偽sql語句以及執行時的相關情況。
binlog日志文件恢復數據
用binlog日志文件恢復數據其實就是回放執行之前記錄在binlog文件里的sql,舉一個數據恢復的例子
# 先執行刷新日志的命令生成一個新的binlog文件mysql-binlog.000008,后面我們的修改操作日志都會記錄在最新的這個文件里
flush logs;
# 執行兩條插入語句
INSERT INTO `test`.`account` (`id`, `name`, `balance`) VALUES ('4', 'zhuge', '666');
INSERT INTO `test`.`account` (`id`, `name`, `balance`) VALUES ('5', 'zhuge1', '888');
# 假設現在誤操作執行了一條刪除語句把剛新增的兩條數據刪掉了
delete from account where id > 3;
現在需要恢復被刪除的兩條數據,我們先查看binlog日志文件
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000008
文件內容如下:
。。。。。。
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 219
#230127 23:32:24 server id 1 end_log_pos 291 CRC32 0x4528234f Query thread_id=5 exec_time=0 error_code=0
SET TIMESTAMP=1674833544/*!*/;
SET @@session.pseudo_thread_id=5/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1342177280/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 291
#230127 23:32:24 server id 1 end_log_pos 345 CRC32 0x7482741d Table_map: `test`.`account` mapped to number 99
# at 345
#230127 23:32:24 server id 1 end_log_pos 396 CRC32 0x5e443cf0 Write_rows: table id 99 flags: STMT_END_F
### INSERT INTO `test`.`account`
### SET
### @1=4
### @2='zhuge'
### @3=666
# at 396
#230127 23:32:24 server id 1 end_log_pos 427 CRC32 0x8a0d8a3c Xid = 56
COMMIT/*!*/;
# at 427
#230127 23:32:40 server id 1 end_log_pos 492 CRC32 0x5261a37e Anonymous_GTID last_committed=1 sequence_number=2 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 492
#230127 23:32:40 server id 1 end_log_pos 564 CRC32 0x01086643 Query thread_id=5 exec_time=0 error_code=0
SET TIMESTAMP=1674833560/*!*/;
BEGIN
/*!*/;
# at 564
#230127 23:32:40 server id 1 end_log_pos 618 CRC32 0xc26b6719 Table_map: `test`.`account` mapped to number 99
# at 618
#230127 23:32:40 server id 1 end_log_pos 670 CRC32 0x8e272176 Write_rows: table id 99 flags: STMT_END_F
### INSERT INTO `test`.`account`
### SET
### @1=5
### @2='zhuge1'
### @3=888
# at 670
#230127 23:32:40 server id 1 end_log_pos 701 CRC32 0xb5e63d00 Xid = 58
COMMIT/*!*/;
# at 701
#230127 23:34:23 server id 1 end_log_pos 766 CRC32 0xa0844501 Anonymous_GTID last_committed=2 sequence_number=3 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 766
#230127 23:34:23 server id 1 end_log_pos 838 CRC32 0x687bdf88 Query thread_id=7 exec_time=0 error_code=0
SET TIMESTAMP=1674833663/*!*/;
BEGIN
/*!*/;
# at 838
#230127 23:34:23 server id 1 end_log_pos 892 CRC32 0x4f7b7d6a Table_map: `test`.`account` mapped to number 99
# at 892
#230127 23:34:23 server id 1 end_log_pos 960 CRC32 0xc47ac777 Delete_rows: table id 99 flags: STMT_END_F
### DELETE FROM `test`.`account`
### WHERE
### @1=4
### @2='zhuge'
### @3=666
### DELETE FROM `test`.`account`
### WHERE
### @1=5
### @2='zhuge1'
### @3=888
# at 960
#230127 23:34:23 server id 1 end_log_pos 991 CRC32 0x386699fe Xid = 65
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
。。。。。。
找到兩條插入數據的sql,每條sql的上下都有BEGIN和COMMIT,我們找到第一條sql BEGIN前面的文件位置標識 at 219(這是文件的位置標識),再找到第二條sql COMMIT后面的文件位置標識 at 701
我們可以根據文件位置標識來恢復數據,執行如下sql:
mysqlbinlog --no-defaults --start-position=219 --stop-position=701 --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test# 補充一個根據時間來恢復數據的命令,我們找到第一條sql BEGIN前面的時間戳標記 SET TIMESTAMP=1674833544,再找到第二條sql COMMIT后面的時間戳標記 SET TIMESTAMP=1674833663,轉成datetime格式
mysqlbinlog --no-defaults --start-datetime="2023-1-27 23:32:24" --stop-datetime="2023-1-27 23:34:23" --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test
被刪除數據被恢復!
注意:如果要恢復大量數據,比如程序員經常說的刪庫跑路的話題,假設我們把數據庫所有數據都刪除了要怎么恢復了,如果數據庫之前沒有備份,所有的binlog日志都在的話,就從binlog第一個文件開始逐個恢復每個binlog文件里的數據,這種一般不太可能,因為binlog日志比較大,早期的binlog文件會定期刪除的,所以一般不可能用binlog文件恢復整個數據庫的。
一般我們推薦的是每天(在凌晨后)需要做一次全量數據庫備份,那么恢復數據庫可以用最近的一次全量備份再加上備份時間點之后的binlog來恢復數據。
備份數據庫一般可以用mysqldump 命令工具
mysqldump -u root 數據庫名>備份文件名; #備份整個數據庫
mysqldump -u root 數據庫名 表名字>備份文件名; #備份整個表mysql -u root test < 備份文件名 #恢復整個數據庫,test為數據庫名稱,需要自己先建一個數據庫test
為什么會有redo log和binlog兩份日志呢?
因為最開始 MySQL 里并沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是MyISAM 沒有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,所以InnoDB 使用另外一套日志系統——也就是 redo log 來實現 crash-safe 能力。
有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為crash-safe。
undo log回滾日志
InnoDB對undo log文件的管理采用段的方式,也就是回滾段(rollback segment) 。每個回滾段記錄了 1024 個 undo log segment ,每個事務只會使用一個undo log segment。
在MySQL5.5的時候,只有一個回滾段,那么最大同時支持的事務數量為1024個。在MySQL 5.6開始,InnoDB支持最大128個回滾段,故其支持同時在線的事務限制提高到了 128*1024 。
innodb_undo_directory:設置undo log文件所在的路徑。該參數的默認值為"./",即innodb數據文件存儲位置,目錄下ibdata1文件就是undo log存儲的位置。
innodb_undo_logs: 設置undo log文件內部回滾段的個數,默認值為128。
innodb_undo_tablespaces: 設置undo log文件的數量,這樣回滾段可以較為平均地分布在多個文件中。設置該參數后,會在路徑innodb_undo_directory看到undo為前綴的文件。
undo log日志什么時候刪除
新增類型的,在事務提交之后就可以清除掉了。
修改類型的,事務提交之后不能立即清除掉,這些日志會用于mvcc。只有當沒有事務用到該版本信息時才可以清除。
為什么Mysql不能直接更新磁盤上的數據而設置這么一套復雜的機制來執行SQL了?
因為來一個請求就直接對磁盤文件進行隨機讀寫,然后更新磁盤文件里的數據性能可能相當差。
因為磁盤隨機讀寫的性能是非常差的,所以直接更新磁盤文件是不能讓數據庫抗住很高并發的。
Mysql這套機制看起來復雜,但它可以保證每個更新請求都是更新內存BufferPool,然后順序寫日志文件,同時還能保證各種異常情況下的數據一致性。
更新內存的性能是極高的,然后順序寫磁盤上的日志文件的性能也是非常高的,要遠高于隨機讀寫磁盤文件。
正是通過這套機制,才能讓我們的MySQL數據庫在較高配置的機器上每秒可以抗下幾干甚至上萬的讀寫請求。
錯誤日志
Mysql還有一個比較重要的日志是錯誤日志,它記錄了數據庫啟動和停止,以及運行過程中發生任何嚴重錯誤時的相關信息。當數據庫出現任何故障導致無法正常使用時,建議首先查看此日志。
在MySQL數據庫中,錯誤日志功能是默認開啟的,而且無法被關閉。
# 查看錯誤日志存放位置
show variables like '%log_error%';
通用查詢日志
通用查詢日志記錄用戶的所有操作,包括啟動和關閉MySQL服務、所有用戶的連接開始時間和截止時間、發給 MySQL 數據庫服務器的所有 SQL 指令等,如select、show等,無論SQL的語法正確還是錯誤、也無論SQL執行成功還是失敗,MySQL都會將其記錄下來。
通用查詢日志用來還原操作時的具體場景,可以幫助我們準確定位一些疑難問題,比如重復支付等問題。
general_log:是否開啟日志參數,默認為OFF,處于關閉狀態,因為開啟會消耗系統資源并且占用磁盤空間。一般不建議開啟,只在需要調試查詢問題時開啟。
general_log_file:通用查詢日志記錄的位置參數。
show variables like '%general_log%';
# 打開通用查詢日志
SET GLOBAL general_log=on;