雙十一大促是怎么做MySQL熱點(diǎn)數(shù)據(jù)高效更新的?
MySQL的熱點(diǎn)數(shù)據(jù)更新問(wèn)題,一直都是行業(yè)內(nèi)的一個(gè)難題,對(duì)于秒殺場(chǎng)景至關(guān)重要。一旦處理不好,就可能會(huì)導(dǎo)致數(shù)據(jù)庫(kù)被打垮。
通常來(lái)說(shuō),對(duì)于熱點(diǎn)問(wèn)題,都是選擇使用Redis來(lái)抗,比如秒殺場(chǎng)景借助他的單線程高并發(fā)能力來(lái)做預(yù)扣減。
常規(guī)方案
但是,引入Redis又會(huì)帶來(lái)數(shù)據(jù)不一致的問(wèn)題,進(jìn)而會(huì)導(dǎo)致超賣(mài)和少賣(mài),如果一定要在MySQL這個(gè)層面上,抗住高并發(fā)的熱點(diǎn)數(shù)據(jù)并發(fā)更新,有什么方案呢?拿庫(kù)存扣減舉例
1、庫(kù)存拆分,把一個(gè)大的庫(kù)存拆分成多個(gè)小庫(kù)存,拆分后,一次扣減動(dòng)作就可以分散到不同的庫(kù)、表中進(jìn)行,降低鎖粒度提升并發(fā)。
優(yōu)點(diǎn):實(shí)現(xiàn)較簡(jiǎn)單
缺點(diǎn):存在碎片問(wèn)題、庫(kù)存調(diào)控不方便
2、請(qǐng)求合并,把多個(gè)庫(kù)存扣減請(qǐng)求,合并成一個(gè),進(jìn)行批量更新。
優(yōu)點(diǎn):簡(jiǎn)單
缺點(diǎn):適用于異步場(chǎng)景,或者經(jīng)過(guò)分析后認(rèn)為可以合并的場(chǎng)景
3、把update轉(zhuǎn)換成insert,直接插入一次占用記錄,然后異步統(tǒng)計(jì)剩余庫(kù)存,或者通過(guò)SQL統(tǒng)計(jì)流水方式計(jì)算剩余庫(kù)存。
優(yōu)點(diǎn):沒(méi)有update,無(wú)鎖沖突
缺點(diǎn):insert時(shí)控制不好容易超賣(mài)、insert后剩余庫(kù)存不好統(tǒng)計(jì)
企業(yè)級(jí)方案
除了上面這三個(gè)方案外,重點(diǎn)介紹一個(gè)很多大公司在用的,扛了618/雙11等大促的高并發(fā)的秒殺的方案。
那就是改造MySQL
主要思路就是,針對(duì)于頻繁更新或秒殺類(lèi)業(yè)務(wù)場(chǎng)景,大幅度優(yōu)化對(duì)于熱點(diǎn)行數(shù)據(jù)的update操作的性能。當(dāng)開(kāi)啟熱點(diǎn)更新自動(dòng)探測(cè)時(shí),系統(tǒng)會(huì)自動(dòng)探測(cè)是否有單行的熱點(diǎn)更新,如果有,則會(huì)讓大量的并發(fā) update 排隊(duì)執(zhí)行,以減少大量行鎖造成的并發(fā)性能下降。(另外我出了一份Java面試寶典,類(lèi)似的方案有很多)
也就是說(shuō),他們改造了MySQL數(shù)據(jù)庫(kù),讓同一個(gè)熱點(diǎn)行的更新語(yǔ)句,在執(zhí)行層進(jìn)行排隊(duì)。這樣的排隊(duì)相比update的排隊(duì),要輕量級(jí)很多,因?yàn)樗恍枰孕恍枰獡屾i。
這個(gè)方案的好處就是開(kāi)發(fā)不需要做額外的事情,只需要開(kāi)啟熱點(diǎn)檢測(cè)就行了。缺點(diǎn)就是改造MySQL數(shù)據(jù)庫(kù)有成本。不過(guò)現(xiàn)在很多云上數(shù)據(jù)庫(kù)都支持了。
效果如何?
比如阿里云的數(shù)據(jù)庫(kù)在做過(guò)改造之后,就做過(guò)單行熱點(diǎn)數(shù)據(jù)更新測(cè)試。
本示例中,分別使用兩個(gè)實(shí)例進(jìn)行測(cè)試(高可用版和三節(jié)點(diǎn)企業(yè)版),規(guī)格碼為rds.mysql.st.v52和mysql.st.12xlarge.25。
- 實(shí)例版本:MySQL 5.7
- 實(shí)例規(guī)格:90核720GB(獨(dú)占物理機(jī)型)
- 實(shí)例系列:高可用版和三節(jié)點(diǎn)企業(yè)版
- 實(shí)例存儲(chǔ)類(lèi)型:本地盤(pán)
- 實(shí)例模板:高性能參數(shù)模板
測(cè)試數(shù)據(jù)為單表,表內(nèi)100行記錄。表結(jié)構(gòu)如下:
CREATE TABLE `sbtest1`
(
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT
,`k` INT(10) UNSIGNED NOT NULL DEFAULT '0'
,`c` CHAR(120) NOT NULL DEFAULT ''
,`pad` CHAR(60) NOT NULL DEFAULT ''
,PRIMARY KEY (`id`)
,KEY `k_1` (`k`)
)
ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT
CHARSET=utf8 MAX_ROWS=1000000
對(duì)id=100的記錄進(jìn)行并發(fā)更新,SQL如下:
UPDATE sbtest1 SET k=k+1 WHERE id=100
測(cè)試的Lua腳本如下:
pathtest = string.match(test,"(.*/)")
if pathtest then
dofile(pathtest .."common.lua")
else
require("common")
end
function thread_init(thread_id)
set_vars()
end
function event(thread_id)
local table_name
table_name ="sbtest".. sb_rand_uniform(1, oltp_tables_count)
rs = db_query("begin")
rs = db_query("update /*+commit_on_success rollback_on_fail target_affect_row(1) */ sbtest1 SET k=k+1 WHERE id=100")
rs =db_query("commit")
end
測(cè)試結(jié)果
實(shí)例類(lèi)型 | 單行記錄更新峰值(TPS) |
RDS高可用版 | 1.2萬(wàn) |
RDS三節(jié)點(diǎn)企業(yè)版 | 3.1萬(wàn) |
參考資料:
騰訊云數(shù)據(jù)庫(kù)MySQL熱點(diǎn)更新:
https://cloud.tencent.com/document/product/236/63239
阿里云數(shù)據(jù)庫(kù)Inventory Hint:
https://www.alibabacloud.com/help/zh/apsaradb-for-rds/latest/inventory-hint