X-Engine是阿里巴巴自研的高性能低成本存儲(chǔ)引擎,經(jīng)過(guò)多年的努力,我們?cè)诩瘓F(tuán)內(nèi)部以AliSQL(X-Engine)的形式(AliSQL是阿里的MySQL分支)支持了許多業(yè)務(wù),為用戶帶來(lái)了顯著的成本和性能收益。
成都創(chuàng)新互聯(lián)公司擁有一支富有激情的企業(yè)網(wǎng)站制作團(tuán)隊(duì),在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)深耕十余年,專業(yè)且經(jīng)驗(yàn)豐富。十余年網(wǎng)站優(yōu)化營(yíng)銷經(jīng)驗(yàn),我們已為超過(guò)千家中小企業(yè)提供了成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站解決方案,按需定制開發(fā),設(shè)計(jì)滿意,售后服務(wù)無(wú)憂。所有客戶皆提供一年免費(fèi)網(wǎng)站維護(hù)!
時(shí)至今日,阿里巴巴數(shù)據(jù)庫(kù)團(tuán)隊(duì)已經(jīng)向MySQL官方提交了許多有價(jià)值的bug及修復(fù)方案。我們繼承了這一優(yōu)良傳統(tǒng),在生產(chǎn)、測(cè)試中遇到MySQL相關(guān)的問(wèn)題,總是積極地思考解決方案,并迅速與官方交流溝通,為開源社區(qū)的發(fā)展貢獻(xiàn)自己的力量。
下文將介紹我們剛發(fā)現(xiàn)的一個(gè)MySQL問(wèn)題及修復(fù)方案。遇到相同情況的朋友需要注意了,也許不符合規(guī)范的數(shù)據(jù)已經(jīng)寫入你們的數(shù)據(jù)庫(kù)了。
背景知識(shí)
如果MySQL參數(shù)sql_mode包含以下3項(xiàng):
NO_ZERO_DATE
NO_ZERO_IN_DATE
STRICT_TRANS_TABLES
向DATE類型列,插入'0000-00-00'或者年/月/日3部分任意一部分為0,都將失敗。
異常:‘0000-00-00’竟然插入成功了
在MySQL 8.0.16上依次執(zhí)行以下語(yǔ)句:
set sql_mode='';
create table test (mydate DATE NOT NULL DEFAULT '0000-00-00');
set sql_mode=default;
show variables like "sql_mode";
insert into test values();
select * from test;
這里先將sql_mode設(shè)為空的目的是:建表時(shí)將mydate的default value設(shè)為'0000-00-00',否則會(huì)因default value不符合NO_ZERO_DATE而建表失敗。
建表成功后將sql_mode設(shè)回default,包含:
ONLY_FULL_GROUP_BY
NO_ZERO_DATE
NO_ZERO_IN_DATE
STRICT_TRANS_TABLES
ERROR_FOR_DIVISION_BY_ZERO
NO_ENGINE_SUBSTITUTION
然而,這時(shí)竟然成功向test庫(kù)里插入了一條'0000-00-00'的DATE。顯然,NO_ZERO_DATE的語(yǔ)義被打破了。
抽絲剝繭,原來(lái)問(wèn)題出在這
首先,我們定位到MySQL插入路徑,檢查default value是否合法的函數(shù)。
這個(gè)函數(shù)比較簡(jiǎn)單,找出用戶insert lists不包含的、且有default value的列,檢查它們的default value是否合法。write_set是一個(gè)bitmap,標(biāo)識(shí)了用戶insert lists里包含哪些列。
我用gdb在該函數(shù)處加了斷點(diǎn),執(zhí)行上述case,竟然發(fā)現(xiàn)write_set里全部bit被設(shè)為了1。這顯然不正常的現(xiàn)象,我的插入SQL語(yǔ)句insert into test values();insert list明明為空,write_set全為0才合理??磥?lái)有函數(shù)錯(cuò)誤地修改了它。
于是乎,我用gdb給write_set的地址加了一個(gè)watchpoint,重新執(zhí)行insert語(yǔ)句。這次定位到了修改write_set的地方:
該函數(shù)在檢查default value是否合法前執(zhí)行,其作用是當(dāng)binlog_format為ROW且binlog_row_image為FULL時(shí),write_set會(huì)被全部設(shè)置為1。
參數(shù)binlog_format指定了binlog格式,有三個(gè)備選項(xiàng):
ROW代表主備之間通過(guò)log_event同步;
STATEMENT代表主備之間通過(guò)SQL語(yǔ)句同步;
MIXED則是混合格式,默認(rèn)用STATEMENT方式,一些特殊情況下用ROW方式。
由于主備通過(guò)STATEMENT同步(雖然它產(chǎn)生的binlog數(shù)量小),可能因上下文信息、環(huán)境不同等因素,導(dǎo)致結(jié)果不一致,因此安全起見(jiàn),binlog_format默認(rèn)為ROW。
參數(shù)binlog_row_image指定了ROW格式binlog要記錄哪些信息。它也有三個(gè)備選項(xiàng):
FULL表示binlog記錄變更前后的所有列;
MINIMAL表示binlog只記錄唯一標(biāo)識(shí)列和修改列;
NOBLOB表示BLOB是修改列或唯一標(biāo)識(shí)列,才記錄,其它列與FULL相同全部記錄。
binlog_row_image默認(rèn)為FULL。
當(dāng)binlog_format為ROW且binlog_row_image為FULL 時(shí),為了保證所有列都寫到binlog里,write_set竟然被全部設(shè)置為1。
write_set變量本是用來(lái)標(biāo)識(shí)用戶插入列,又被賦予了控制寫binlog的重任。多重語(yǔ)義交織,很容易出bug。這也給我們編碼帶來(lái)啟示:每個(gè)變量應(yīng)當(dāng)有確切的語(yǔ)義。
修復(fù)建議
導(dǎo)致這個(gè)bug的原因是write_set用處太多。因此可以創(chuàng)建一個(gè)新的bitmap:binlog_write_set,專門用于控制寫binlog。