由 之前的文章 可以了解到,二進(jìn)制日志在復(fù)制中起到舉足輕重的作用,所以這一篇文章著重了解一下Mysql復(fù)制背后核心組件:二進(jìn)制日志的廬山真面目。
二進(jìn)制日志的結(jié)構(gòu)
從概念上講,二進(jìn)制日志是一系列二進(jìn)制日志事件。它包括一系列的binlog文件和一個(gè)binlog索引文件,當(dāng)前服務(wù)器正在寫(xiě)入的binlog文件稱(chēng)之為active binlog。其文件名是通過(guò)配置文件中的log-bin和log-bin-index來(lái)定義的。
每個(gè)binlog文件是由若干binlog事件組成,以Format_description事件開(kāi)始,以Rotate事件作為文件尾。
Format_description事件包含寫(xiě)binlog文件的服務(wù)器信息,以及關(guān)于文件狀態(tài)的關(guān)鍵信息。如果服務(wù)器關(guān)閉或者重新啟動(dòng),會(huì)創(chuàng)建一個(gè)新的binlog文件,同時(shí)寫(xiě)入新的Format_description事件,這個(gè)事件是必須的,因?yàn)榉?wù)器關(guān)閉和重啟都會(huì)產(chǎn)生更新。服務(wù)器寫(xiě)完binlog文件后,在文件結(jié)尾添加一個(gè)Rotate事件,該事件包含下一個(gè)binlog文件的文件名及其開(kāi)始讀取的位置。除了Format_description和Rotate事件之外,binlog文件的其他事件都被分成 group進(jìn)行管理。在事務(wù)存儲(chǔ)引擎中,每個(gè)組大致對(duì)應(yīng)一個(gè)事務(wù),對(duì)于非事務(wù)存儲(chǔ)引擎,每個(gè)語(yǔ)句本身就是一個(gè)組。通常情況下,每個(gè)組要么全部執(zhí)行,要么去不執(zhí)行。如果由于某種原因Slave在組執(zhí)行的過(guò)程中停機(jī),那么將從該組的起點(diǎn)而不是剛剛執(zhí)行的語(yǔ)句開(kāi)始復(fù)制。
binlog事件的結(jié)構(gòu)
二進(jìn)制日志版本4(binlog format 4)是在MySQL 5.0中引入,是專(zhuān)門(mén)為擴(kuò)展而設(shè)計(jì)的。這里主要討論二進(jìn)制日志版本4。(MySQL 3.23 4.0 4.1版本都是使用二進(jìn)制日志版本3)
每個(gè)binlog事件由三個(gè)部分組成:
- 通用頭(common header):大小固定。事件的基本信息,其中重要的字段是事件類(lèi)型和事件大小。
- 提交頭(post header):大小固定。提交頭與特定的事件類(lèi)型相關(guān)
- 事件體(Event body):大小可變。事件體存儲(chǔ)事件的主要數(shù)據(jù),因事件類(lèi)型不同而異。
具體看一下Format_description事件:
- binlog文件格式版本
- 服務(wù)器版本字符串:一般包括三部分,即版本號(hào)、連字符和其他構(gòu)建項(xiàng)。例如:5.1.42-debug-log
- 通用頭的長(zhǎng)度:存儲(chǔ)了通用頭的長(zhǎng)度。這里是指Format_description事件,所以不同binlog文件該字段的值不同。除了Format_description和Rotate事件外,其他事件的通用頭長(zhǎng)度都是可變的。 Format_description事件 的通用頭長(zhǎng)度是不變的,是因?yàn)槿魏伟姹镜姆?wù)器都需要讀取這個(gè)事件。 Rotate事件 的通用頭長(zhǎng)度也是不變的,是因?yàn)镾lave連接Master時(shí)首先要用到該事件。
- 提交頭的長(zhǎng)度:binlog文件中所有事件的提交頭長(zhǎng)度是不變的,該字段存儲(chǔ)了各個(gè)事件的提交頭長(zhǎng)度構(gòu)成的數(shù)組。由于不同服務(wù)器間的事件數(shù)目不同,所以這個(gè)字段前面還存儲(chǔ)了服務(wù)器的事件數(shù)目。
通過(guò)事件來(lái)記錄數(shù)據(jù)庫(kù)變更
首先,由于二進(jìn)制日志是公共資源,所有線(xiàn)程都向它寫(xiě)入語(yǔ)句,為了避免兩個(gè)線(xiàn)程同時(shí)更新二進(jìn)制日志,在寫(xiě)之前需要獲得一個(gè)互斥鎖Lock_log,寫(xiě)完之后再釋放。
所有涉及到數(shù)據(jù)庫(kù)更新的語(yǔ)句都會(huì)以Query事件的形式寫(xiě)入二進(jìn)制日志中,除了實(shí)際執(zhí)行的語(yǔ)句外,Query事件還包含執(zhí)行語(yǔ)句必需的上下文附加信息。下面給出了如何記錄這些上下文信息
- 當(dāng)前數(shù)據(jù)庫(kù):在 Query事件 添加一個(gè)特殊字段記錄當(dāng)前數(shù)據(jù)庫(kù)。
- 用戶(hù)自定義變量的值: User_var事件 記錄單個(gè)用戶(hù)自定義的變量的變量名及其值。
- RAND函數(shù)的種子: Rand事件 記錄Rand函數(shù)所用的隨機(jī)數(shù)種子。
- 當(dāng)前時(shí)間:NOW,CURDATE,CURTIME,UNIX_TIMESTAMP和SYSDATE這五個(gè)函數(shù)會(huì)用到當(dāng)前時(shí)間,針對(duì)這個(gè)事件會(huì)存儲(chǔ)一個(gè)時(shí)間戳,表示事件何時(shí)開(kāi)始執(zhí)行。
- AUTO_INCREMENT字段的插入值: Intvar事件 記錄在語(yǔ)句開(kāi)始前,表內(nèi)部的自動(dòng)增量計(jì)數(shù)器的值。
- 調(diào)用LAST_INSERTED_ID的返回值: Intvar事件 記錄這個(gè)函數(shù)在語(yǔ)句的返回值。
- 線(xiàn)程ID:主要是涉及到臨時(shí)表的處理。線(xiàn)程ID也是作為一個(gè)獨(dú)立的字段存儲(chǔ)在Query事件中。
* 對(duì)于SYSDATE函數(shù),它返回的是函數(shù)執(zhí)行時(shí)的時(shí)間,這一點(diǎn)不同于NOW函數(shù),NOW返回的是語(yǔ)句執(zhí)行的時(shí)間。所以SYSDATE對(duì)于復(fù)制來(lái)說(shuō)是不安全的,盡量少用。
LOAD DATA INFILE語(yǔ)句
LOAD DATA INFILE比較特殊,它的上下文是文件系統(tǒng)的文件。要正確地傳遞和執(zhí)行LOAD DATA INFILE語(yǔ)句,需要引入新的事件類(lèi)型:
- Begin_load_query :這個(gè)事件開(kāi)始傳輸文件中的數(shù)據(jù)
- Append_block :如果這個(gè)文件超過(guò)了連接的數(shù)據(jù)包大小所允許的最大值,那么跟隨在Begin_load_query事件后面的一個(gè)或多個(gè)Append_block事件的系列包含著這個(gè)文件的剩余部分
- Execute_load_query :Query事件的特殊變種,它包含了在Master上執(zhí)行的LOAD DATA INFILE語(yǔ)句
對(duì)Master上執(zhí)行的每個(gè)LOAD DATA INFILE語(yǔ)句而言,被讀取的文件被映射到一個(gè)支持內(nèi)部文件的緩沖區(qū),并在接下來(lái)的處理流程中使用。此外,一個(gè)唯一的文件ID被分配給該執(zhí)行語(yǔ)句,并用于指向該語(yǔ)句讀取的文件。
當(dāng)語(yǔ)句在執(zhí)行的時(shí),該文件的內(nèi)容被寫(xiě)入二進(jìn)制日志,作為以Begin_load_query事件開(kāi)頭的事件序列,Begin_load_query事件表示新文件的開(kāi)始,且這個(gè)事件序列后面緊跟著零個(gè)或多個(gè)Append_block事件。每個(gè)寫(xiě)入二進(jìn)制的事件都不會(huì)超過(guò)包大小所允許的最大值,這個(gè)最大值由max-allowed_packet選項(xiàng)指定。
當(dāng)整個(gè)文件讀取到表中后,通過(guò)寫(xiě)Execute_load_query事件到二進(jìn)制日志來(lái)終止語(yǔ)句的執(zhí)行。這個(gè)事件包含了執(zhí)行語(yǔ)句和分配給該執(zhí)行語(yǔ)句的文件ID。請(qǐng)注意,這并非是用戶(hù)寫(xiě)的原始語(yǔ)句,而是重新創(chuàng)建的。
* Mysql 5.0.3之前的版本使用的事件名有點(diǎn)不一樣,依次為 Load_log_event,Execute_log_event,Create_file_log_event
二進(jìn)制日志過(guò)濾器
my.cnf中有兩個(gè)選項(xiàng)可用于過(guò)濾日志:binlog-do-db和binlog-ignore-db。這兩個(gè)選項(xiàng)可以使用多次。
MySQL過(guò)濾事件的方式對(duì)于不熟悉的人來(lái)說(shuō)可能有點(diǎn)奇怪。Mysql過(guò)濾是在語(yǔ)句級(jí)完成的,binlog-*-db使用 當(dāng)前數(shù)據(jù)庫(kù) 來(lái)決定是否應(yīng)該過(guò)濾該語(yǔ)句,而不是由語(yǔ)句所影響的表所在的數(shù)據(jù)庫(kù)決定的。對(duì)于下面的例子,使用binlog-ignore-db=bad篩選bad數(shù)據(jù)庫(kù),下例中一個(gè)都不會(huì)寫(xiě)入日志。
USE
bad; INSERT INTO t1
VALUES
(
1
),(
2
);
USE
bad; INSERT INTO good.t2
VALUES
(
1
),(
2
);
USE
bad; UPDATE good.t1, ugly.t2 SET a = b;
至于為什么不是以語(yǔ)句所影響的表所在的數(shù)據(jù)庫(kù)來(lái)決定,可以嘗試分析一下。如果以這種邏輯,使用binlog-ignore-db=ugly篩選時(shí),第三條語(yǔ)句到底要不要寫(xiě)入日志呢?
為了避免在執(zhí)行可能被過(guò)濾的語(yǔ)句時(shí)發(fā)生錯(cuò)誤,請(qǐng)不要編寫(xiě)那種表名,函數(shù)名或存儲(chǔ)過(guò)程名前面加數(shù)據(jù)庫(kù)名的語(yǔ)句,而是通過(guò)使用use來(lái)改變當(dāng)前數(shù)據(jù)庫(kù)。
還有一個(gè)需要說(shuō)明的是,只要設(shè)置了binlog-do-db,過(guò)濾器會(huì)無(wú)視binlog-ignore-db的設(shè)置。
當(dāng)然對(duì)于MySQL復(fù)制來(lái)說(shuō),本身不建議使用過(guò)濾器,因?yàn)槿罩臼遣煌暾摹?
?
二進(jìn)制日志和安全
一般來(lái)說(shuō),一個(gè)有REPLICATION SLAVE權(quán)限的用戶(hù)擁有讀取Master上發(fā)生的所有事件的權(quán)限,因此為了安全應(yīng)該保護(hù)該賬戶(hù)不被損害。具體預(yù)防的措施有:
- 盡可能使從防火墻外無(wú)法登錄該賬戶(hù)
- 記錄所有試圖登錄到該賬戶(hù)的日志,并將日志放置在一個(gè)單獨(dú)的安全服務(wù)器上
- 加密Master和Slave間所用的連接,例如MySQL的built-in SLL
- 敏感信息不要放入日志文件中,比如說(shuō)密碼
#
第二種做法不會(huì)把明文密碼寫(xiě)入到日志中,更安全些
UPDATE employee SET pass = PASSWORD(
'
foobar
'
)
SET
@pass
= PASSWORD(
'
foobar
'
);
UPDATE employee SET pass =
@pass
觸發(fā)器
為了在服務(wù)器上重放二進(jìn)制日志,毫無(wú)問(wèn)題的處理各種表的權(quán)限,有必要用SUPER權(quán)限的用戶(hù)執(zhí)行所有語(yǔ)句。但觸發(fā)器沒(méi)有被定義使用SUPER權(quán)限,所以重要的是以正確的用戶(hù)作為觸發(fā)器的定義者去重新創(chuàng)建觸發(fā)器。CREATE TRIGGER提供了一個(gè)DEFINER子句,如果沒(méi)有給語(yǔ)句指定DEFINER,該語(yǔ)句添加DEFINER子句后被寫(xiě)到二進(jìn)制日志中,且使用當(dāng)前用戶(hù)作為其定義者。
master>SHOW BINLOG EVENTS FROM
92236
LIMIT
1
\G
********************
1
. row ********************
Log_name: master-bin.
000038
Pos
:
92236
Event_type: Query
Server_id:
1
End_log_pos:
92491
Info:
use
`test`; CREATE DEFINER=`root`@`localhost` TRIGGER ...
調(diào)用觸發(fā)器的語(yǔ)句被記錄到二進(jìn)制日志,但它沒(méi)有連接到特定的觸發(fā)器。相反,當(dāng)Slave執(zhí)行該語(yǔ)句時(shí),它會(huì)自動(dòng)執(zhí)行受該語(yǔ)句影響的表相關(guān)聯(lián)的所有觸發(fā)器,這意味著可以在Master和Slave上有不同的觸發(fā)器。
存儲(chǔ)過(guò)程
存儲(chǔ)過(guò)程的定義語(yǔ)句的處理和觸發(fā)器是類(lèi)似的,CREATE PROCETURE語(yǔ)句也有可選的子語(yǔ)句DEFINER,寫(xiě)入二進(jìn)制日志的時(shí)候,會(huì)強(qiáng)制加上該子句的。調(diào)用過(guò)程和觸發(fā)器不一樣。
#
定義存儲(chǔ)過(guò)程
delimiter $$
CREATE PROCEDURE employee_add(p_name CHAR(
64
), p_email CHAR(
64
), p_password CHAR(
64
))
MODIFIES SQL DATA
BEGIN
DECLARE pass CHAR(
64
);
set pass = PASSWORD(p_pass)
INSERT INTO employee(name, email, password)
VALUES
(p_name, p_email, pass);
END $$
delimiter ;
#
調(diào)用存儲(chǔ)過(guò)程
master> CALL employee_add(
'
chunk
'
,
'
chuck@example.com
'
,
'
abrakadabra
'
);
master> SHOW BINLOG EVENTS FROM
104033
\G
********************
1
. row ********************
Log_name: master-bin.
000038
Pos
:
104033
Event_type: Intvar
Server_id:
1
End_log_pos:
104061
Info: INSERT_ID=
1
********************
2
. row ********************
Log_name: master-bin.
000038
Pos
:
104061
Event_type: Query
Server_id:
1
End_log_pos:
104416
Info:
use
`test`; INSERT INTO employee(name, email, password)
VALUES
(
NAME_CONST(
'
p_name
'
,_latin1
'
chuck
'
COLLATE
'
latin1_swedish_ci
'
),
NAME_CONST(
'
p_email
'
,_latin1
'
chuck@example.com
'
COLLATE
'
latin1_swedish_ci
'
),
NAME_CONST(
'
pass
'
,_latin1
'
*FEB778934FDSFQOPL7...
'
COLLATE
'
latin1_swedish_ci
'
))
有四點(diǎn)需要注意:
- CALL語(yǔ)句沒(méi)有被寫(xiě)入二進(jìn)制日志。取而代之的是,執(zhí)行語(yǔ)句作為調(diào)用的結(jié)果被寫(xiě)入二進(jìn)制日志。
- 該語(yǔ)句改寫(xiě)為不包含任何對(duì)存儲(chǔ)過(guò)程的參數(shù)的引用。取而代之的是,使用NAME_CONST函數(shù)為每個(gè)參數(shù)創(chuàng)建一個(gè)單值的結(jié)果集
- 局部聲明的變量pass也被換成了NAME_CONST表達(dá)式
- 調(diào)用語(yǔ)句寫(xiě)入二進(jìn)制日志之前,上下文信息已經(jīng)寫(xiě)入日志,這里指Intvar事件
存儲(chǔ)函數(shù)
存儲(chǔ)過(guò)程的定義語(yǔ)句的處理和觸發(fā)器是類(lèi)似的,CREATE FUNCTION語(yǔ)句也有可選的子語(yǔ)句DEFINER,寫(xiě)入二進(jìn)制日志的時(shí)候,會(huì)強(qiáng)制加上該子句的。調(diào)用的時(shí)候,存儲(chǔ)函數(shù)以與觸發(fā)器相同的方式被復(fù)制。有一點(diǎn)需要注意的就是,SELECT語(yǔ)句不會(huì)被寫(xiě)入二進(jìn)制日志,但是一個(gè)含有存儲(chǔ)函數(shù)的SELECT語(yǔ)句是個(gè)例外。
對(duì)于存儲(chǔ)函數(shù)還有一個(gè)需要提到的是權(quán)限問(wèn)題。CREATE ROUTINE權(quán)限是定義一個(gè)存儲(chǔ)過(guò)程或存儲(chǔ)函數(shù)所必需的。嚴(yán)格說(shuō)創(chuàng)建一個(gè)存儲(chǔ)程序不需要其他權(quán)限,但它通常根據(jù)定義者的權(quán)限執(zhí)行。在Slave上的復(fù)制線(xiàn)程在不進(jìn)行權(quán)限檢查的情況下執(zhí)行,這留下了嚴(yán)重的安全漏洞。MySQL 5.0之前的版本沒(méi)有存儲(chǔ)程序,這樣不會(huì)有問(wèn)題,因?yàn)樵贛aster上違規(guī)的語(yǔ)句不會(huì)寫(xiě)到二進(jìn)制日志中。由于存儲(chǔ)過(guò)程被展開(kāi)了,只有在Master上成功執(zhí)行的語(yǔ)句才會(huì)寫(xiě)進(jìn)二進(jìn)制日志,所以也不會(huì)有問(wèn)題。而存儲(chǔ)函數(shù)有點(diǎn)不同,它并沒(méi)有被展開(kāi),也就是說(shuō)有可能在Master和Slave上執(zhí)行不同的程序分支,帶來(lái)潛在安全漏洞。在存儲(chǔ)函數(shù)定義時(shí)使用SQL SECURITY DEFINER而不是SQL SECURITY INVOKER可以防止這一點(diǎn)。因?yàn)檫@一點(diǎn)的考慮,MySQL默認(rèn)要求SUPER權(quán)限來(lái)定義存儲(chǔ)函數(shù)。
Events
定義跟其他存儲(chǔ)程序一樣,也會(huì)有DEFINER子句。由于事件由事件調(diào)度器調(diào)用,因此它們總是以定義者執(zhí)行從而不會(huì)存在存儲(chǔ)函數(shù)的安全漏洞。當(dāng)事件被執(zhí)行時(shí),該語(yǔ)句被直接寫(xiě)入二進(jìn)制日志。由于事件是在Master上執(zhí)行的,他們?cè)赟lave上是自動(dòng)禁止的。但有時(shí)候如果需要升級(jí)Slave,就需要允許在Slave上執(zhí)行這些事件。
UPDATE mysql.events SET status = ENABLED WHERE status = SLAVESIDE_DISABLED;
特殊結(jié)構(gòu)
盡管基于語(yǔ)句的復(fù)制通常是簡(jiǎn)單的,但一些特殊結(jié)構(gòu)必須小心處理,才能很好的來(lái)保證Slave執(zhí)行語(yǔ)句時(shí)的上下文跟Master上執(zhí)行時(shí)是一樣的。
?
LOAD_FILE函數(shù)
LOAD_FILE函數(shù)讓你可以獲取一個(gè)文件,由于在復(fù)制過(guò)程中,它不會(huì)被傳輸,所以需要改寫(xiě)。
INSERT INTO document(author, body)
VALUES
(
'
Fox
'
, LOAD_FILR(
'
index.html
'
));
#
可以用LOAD DATA FILE改寫(xiě)
LOAD DATA INFILE
'
index.html
'
INTO TABLE document FIELDS TERMINATED BY
'
@*@
'
LINES TERMINATED BY
'
&%&
'
(author, body) SET author =
'
FOX
'
;
#
還可以用用戶(hù)定義變量改寫(xiě)
SET
@document
= LOAD_FILE(
'
index.html
'
);
INSERT INTO document(author, body)
VALUES
(
'
Fox
'
,
@document
);
?
非事務(wù)性的變化和錯(cuò)誤處理
如果有一個(gè)employee表是支持事務(wù)的InnoDB存儲(chǔ)引擎(主鍵是mail),而跟蹤employee修改的log表是不支持事務(wù)的MyISAM存儲(chǔ)引擎。在其上定義兩個(gè)觸發(fā)器,一個(gè)在INSERT之前觸發(fā)tr_insert_before,插入一條記錄到log表,插入紀(jì)錄的狀態(tài)為FAIL;一個(gè)在INSERT之后觸發(fā)tr_insert_after,更改剛才插入紀(jì)錄的狀態(tài)為OK。連續(xù)插入兩條完全相同記錄時(shí),tr_insert_before被觸發(fā),tr_insert_after則不會(huì)被觸發(fā)。雖然employee失敗回滾了,但是log里面插入的數(shù)據(jù)卻沒(méi)辦法回滾,這是個(gè)問(wèn)題。執(zhí)行后二進(jìn)制日志文件內(nèi)容如下。
master> SET
@pass
= PASSWORD(
'
xyz
'
);
master> INSERT INTO employee (name, mail, password)
VALUES
(
'
hu
'
,
'
hu@fox.com
'
,
@pass
);
master> INSERT INTO employee (name, mail, password)
VALUES
(
'
hu
'
,
'
hu@fox.com
'
,
@pass
);
master> SHOW BINLOG EVENTS IN
'
local-bin.000023
'
********************
1
. row ********************
Log_name: master-bin.
000023
Pos
:
1252
Event_type: Query
Server_id:
1
End_log_pos:
1320
Info:
use
'
test
'
; BEGIN
********************
2
. row ********************
Log_name: master-bin.
000023
Pos
:
1320
Event_type: Intvar
Server_id:
1
End_log_pos:
1348
Info: LAST_INSERT_ID=
1
********************
3
. row ********************
Log_name: master-bin.
000023
Pos
:
1348
Event_type: User var
Server_id:
1
End_log_pos:
1426
Info: @
'
pass
'
=_utf 0x432423jklfslagklr... COLLATE utf8_general_ci
********************
4
. row ********************
Log_name: master-bin.
000023
Pos
:
1426
Event_type: Query
Server_id:
1
End_log_pos:
1567
Info:
use
'
test
'
; INSERT INTO employee ...
********************
5
. row ********************
Log_name: master-bin.
000023
Pos
:
1567
Event_type: Xid
Server_id:
1
End_log_pos:
1594
Info: COMMIT /* xid=
60
*/
********************
6
. row ********************
Log_name: master-bin.
000023
Pos
:
1594
Event_type: Query
Server_id:
1
End_log_pos:
1662
Info:
use
'
test
'
; BEGIN
********************
7
. row ********************
Log_name: master-bin.
000023
Pos
:
1662
Event_type: Intvar
Server_id:
1
End_log_pos:
1690
Info: LAST_INSERT_ID=
1
********************
8
. row ********************
Log_name: master-bin.
000023
Pos
:
1690
Event_type: User var
Server_id:
1
End_log_pos:
1768
Info: @
'
pass
'
=_utf 0x432423jklfslagklr... COLLATE utf8_general_ci
********************
9
. row ********************
Log_name: master-bin.
000023
Pos
:
1768
Event_type: Query
Server_id:
1
End_log_pos:
1909
Info:
use
'
test
'
; INSERT INTO employee ...
********************
10
. row ********************
Log_name: master-bin.
000023
Pos
:
1909
Event_type: Query
Server_id:
1
End_log_pos:
1980
Info:
use
'
test
'
; ROLLBACK
?
事務(wù)
由上面的二進(jìn)制日志內(nèi)容可以看到,執(zhí)行事務(wù)的時(shí)候需要額外的處理。對(duì)于事務(wù)來(lái)說(shuō),為了使得每個(gè)事務(wù)的所有語(yǔ)句在一起,不是按照事務(wù)的開(kāi)始順序而是提交順序記入二進(jìn)制日志。為了確保每個(gè)事務(wù)都作為一個(gè)單元被寫(xiě)入二進(jìn)制日志,服務(wù)器需要將在不同線(xiàn)程中執(zhí)行的語(yǔ)句分開(kāi),保存在一個(gè)事務(wù)緩存中,在事務(wù)提交的時(shí)候緩存被清空,同時(shí)事務(wù)緩存的內(nèi)容被復(fù)制到二進(jìn)制日志中。
那如何記錄非事務(wù)性的語(yǔ)句呢?有這么三條規(guī)則可以使用:
- 如果語(yǔ)句被標(biāo)記成事務(wù)的,它將被寫(xiě)入事務(wù)緩存
- 如果語(yǔ)句沒(méi)有被標(biāo)記成事務(wù)性的,而且事務(wù)緩存中沒(méi)有語(yǔ)句,該語(yǔ)句將被直接寫(xiě)入二進(jìn)制日志
- 如果語(yǔ)句沒(méi)有被標(biāo)記成事務(wù)性的,但是事務(wù)緩存中已有語(yǔ)句,該語(yǔ)句被寫(xiě)入事務(wù)緩存
使用XA進(jìn)行分布式事務(wù)處理
- 第一階段,每個(gè)存儲(chǔ)引擎被要求為提交做準(zhǔn)備。在準(zhǔn)備時(shí),存儲(chǔ)引擎將它需要正確提交的一切信息寫(xiě)入到安全的存儲(chǔ)器,然后返回一個(gè)OK消息。如果有一個(gè)存儲(chǔ)引擎的回答是否定的,則意味著它不能提交這個(gè)事務(wù),提交被終止,而且所有的引擎都被通知回滾事務(wù)。
- 在所有的存儲(chǔ)引擎都返回OK的時(shí)候, 在第二階段開(kāi)始之前,事務(wù)緩存被寫(xiě)入二進(jìn)制日志 。普通事務(wù)以帶有COMMIT的普通查詢(xún)事件結(jié)束,與此同時(shí),XA事務(wù)則以一個(gè)包含XID的Xid事件結(jié)束。
二進(jìn)制日志管理
到目前為止,所提到的事件都是Master上的數(shù)據(jù)的改動(dòng)。有一些事件雖然不是代表在Master上修改數(shù)據(jù),但它們卻會(huì)影響復(fù)制。比如在服務(wù)器停止的期間修改了數(shù)據(jù)文件之類(lèi),為了應(yīng)對(duì)這些問(wèn)題,也需要額外類(lèi)型的事件。
二進(jìn)制日志和系統(tǒng)崩潰安全
在數(shù)據(jù)庫(kù)崩潰的時(shí)候,保持?jǐn)?shù)據(jù)庫(kù)和二進(jìn)制日志相互一致性非常重要。換句話(huà)說(shuō),如果沒(méi)有寫(xiě)入二進(jìn)制日志,那么就應(yīng)該沒(méi)有更改被提交到存儲(chǔ)引擎,反之亦然。
但對(duì)于非事務(wù)性引擎則有問(wèn)題。例如,不可能保證二進(jìn)制日志和MyISAM表之間的一致性,因?yàn)镸yISAM是非事務(wù)性的,且MyISAM在試圖記錄語(yǔ)句之前就完成了修改。對(duì)于事務(wù)性存儲(chǔ)引擎則不一樣。正如前面所講,事件被寫(xiě)入二進(jìn)制日志是在釋放所有表鎖之前,所有改變傳輸?shù)礁鱾€(gè)存儲(chǔ)引擎之后的。如果在存儲(chǔ)引擎釋放鎖之前系統(tǒng)宕機(jī)了,服務(wù)器在允許事務(wù)提交之前一定要確認(rèn)寫(xiě)進(jìn)二進(jìn)制日志的改變已經(jīng)寫(xiě)進(jìn)實(shí)際表中,而這是需要和標(biāo)準(zhǔn)文件系統(tǒng)同步進(jìn)行協(xié)調(diào)。
回憶一下XA,為了能安全應(yīng)對(duì)宕機(jī),當(dāng)?shù)谝浑A段完成的時(shí)候,所有的數(shù)據(jù)都應(yīng)該已經(jīng)寫(xiě)到了磁盤(pán)。這就意味著每次一個(gè)事務(wù)完成,系統(tǒng)頁(yè)緩存(page cache)就必須寫(xiě)到磁盤(pán),這種想法的代價(jià)很高,而且很多應(yīng)用并不必須這樣。可以通過(guò)sync-binlog選項(xiàng)來(lái)控制數(shù)據(jù)寫(xiě)磁盤(pán)的頻率,默認(rèn)為0,也就是不寫(xiě)磁盤(pán)的調(diào)度完全交給操作系統(tǒng);設(shè)置n,表示每n次事務(wù)提交就寫(xiě)一次磁盤(pán)。
?
binlog文件輪換(binlog file rotate)?
MySQL隔一段時(shí)間就會(huì)啟用一個(gè)新文件來(lái)保存二進(jìn)制日志事件。把文件切換稱(chēng)之為binlog file rotate。
主要有四種操作會(huì)導(dǎo)致文件輪換:
- 服務(wù)器停止:每次服務(wù)啟動(dòng)都會(huì)啟用一個(gè)新的二進(jìn)制日志文件。
- binlog文件大小達(dá)到最大值:這個(gè)值可以通過(guò)binlog-cache-size參數(shù)控制。
- 顯式刷新:FLUSH LOGS
- 服務(wù)器發(fā)生事故:有些事故需要特殊的人工干預(yù),這都會(huì)在復(fù)制流程上形成一個(gè)"缺口"
- binlog-in-use標(biāo)志位:服務(wù)器在寫(xiě)二進(jìn)制日志時(shí)有可能發(fā)生宕機(jī),因此需要知道一個(gè)文件是否被正確的關(guān)閉。而且,如果一個(gè)文件本身?yè)p壞了,用它進(jìn)行恢復(fù)會(huì)產(chǎn)生更多的問(wèn)題。binlog-in-use就是用于標(biāo)識(shí)一個(gè)文件的完整性,它在文件創(chuàng)建的時(shí)候被設(shè)置,在Rotate事件被寫(xiě)入文件后被清除。
- 二進(jìn)制日志文件格式版本號(hào):
- 服務(wù)器版本:
Incidents
?所謂incident事件是指那些在服務(wù)器上沒(méi)有產(chǎn)生數(shù)據(jù)改變但卻必須要寫(xiě)進(jìn)二進(jìn)制日志的事件,因?yàn)樗鼈冇锌赡苡绊懙綇?fù)制。大多數(shù)這種事件并不需要DBA干預(yù),比如數(shù)據(jù)庫(kù)的重啟等。
- ?stop:這是一種表示服務(wù)器正常關(guān)機(jī)的事件。如果服務(wù)器宕機(jī),就不會(huì)有stop事件。這個(gè)事件會(huì)在舊的二進(jìn)制日志文件里,因?yàn)橹貑?huì)啟用新文件。該事件僅僅包含一個(gè)通用頭。當(dāng)二進(jìn)制日志在Slave上重放的時(shí)候,所有Stop事件都會(huì)被忽略。那這種事件有什么用呢,因?yàn)橹貑?fù)制前可能手動(dòng)恢復(fù)一個(gè)備份或者修改了文件,這時(shí)候DBA在重放該日志文件的時(shí)候,可以找到該事件從而知道在哪里開(kāi)始或者停止重放。
- Incident:該事件類(lèi)型是在MySQL 5.1版本引入的。和Stop事件相比,該事件包含一個(gè)標(biāo)志符來(lái)指定發(fā)生了哪種類(lèi)型事故。它一般用來(lái)表示服務(wù)器被強(qiáng)制執(zhí)行某個(gè)不被記入二進(jìn)制日志的變更。比如,數(shù)據(jù)庫(kù)重新加載,某個(gè)非事務(wù)性事件太大而無(wú)法寫(xiě)入二進(jìn)制日志。MySQL Cluster在其中一個(gè)節(jié)點(diǎn)重新加載數(shù)據(jù)庫(kù)而因此不同步時(shí)也會(huì)產(chǎn)生該事件。當(dāng)二進(jìn)制日志在Slave上重放的時(shí)候,碰到Incident事件的時(shí)候?qū)?huì)停止復(fù)制。
刪除二進(jìn)制文件
?有幾種方式可以刪除二進(jìn)制文件:
1
:設(shè)置my.cnf的expire-logs-days參數(shù)
2
:PURGE BINARY LOGS BEFORE datetime;
3
:PURGE BINARY LOGS TO
'
filename
'
;
刪除二進(jìn)制文件的機(jī)制:
開(kāi)始刪除文件之前,服務(wù)器會(huì)把要?jiǎng)h除的文件列表寫(xiě)到一個(gè)臨時(shí)文件(purge index file),然后才開(kāi)始刪除文件,最后刪除該臨時(shí)文件。這樣即使在刪除日志文件過(guò)程中系統(tǒng)宕機(jī)也能在服務(wù)器再啟動(dòng)時(shí),繼續(xù)刪除未刪除的文件。在前面講到,purge index file也用于文件rotate的時(shí)候。
mysqlbinlog是一個(gè)可以查看binlog日志文件和relay日志文件內(nèi)容的小程序。用mysqlbinlog工具來(lái)查看二進(jìn)制日志內(nèi)容的輸出是可以直接在服務(wù)器上執(zhí)行的。該命令是分析日志的一個(gè)利器,可以查看所有日志的語(yǔ)句內(nèi)容和事件內(nèi)容,因此經(jīng)常用于查錯(cuò)。該命令的具體使用方法 參照官方文檔 。注意可以用使用--hexdump選項(xiàng)來(lái)查看二進(jìn)制日志,不過(guò)需要了解一下 日志的數(shù)據(jù)格式 。比如二進(jìn)制日志的整數(shù)字段是以little-Endian順序打印出來(lái)的,所以你必須從右往左讀。32位的block 03 01 00 00表示16進(jìn)制的103。
?
?
?
---待續(xù)
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

