一個(gè)MySQL建表需求的討論和引導(dǎo)
昨天收到一個(gè)業(yè)務(wù)同學(xué)的需求郵件,一般有些復(fù)雜的需求業(yè)務(wù)同學(xué)會(huì)發(fā)郵件告知我們,需要我們?cè)u(píng)估之后再做交付,我看了郵件之后,發(fā)現(xiàn)這個(gè)需求好像有點(diǎn)別扭,大體的意思是在中間件的環(huán)境中創(chuàng)建一張表,表結(jié)構(gòu)如下:
- CREATE TABLE `app_loading_info` (
- `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
- `pid` bigint(20) NOT NULL DEFAULT '0' COMMENT ,
- `appid` int(11) NOT NULL DEFAULT '0' COMMENT 'APPID',
- `username` varchar(64) NOT NULL DEFAULT '' COMMENT '姓名',
- `card` varchar(20) NOT NULL DEFAULT '' ,
- `ai` varchar(40) NOT NULL DEFAULT '' ,
- `state` int(11) NOT NULL DEFAULT '0' ,
- `ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
- `mtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
- PRIMARY KEY (`id`),
- KEY `idx_pid` (`pid`),
- KEY `idx_state` (`state`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
按照ID分片,基本邏輯如下:
每天會(huì)去篩選為完成處理的用戶數(shù)據(jù),重新處理,處理完成后會(huì)去修改用戶的一個(gè)標(biāo)志位,主要有幾個(gè)步驟:
1)根據(jù)state狀態(tài)提取state=0的數(shù)據(jù)(未完成處理數(shù)據(jù))
2)程序中按照id為區(qū)間分批提取
3)提取完成后修改state為state=1,根據(jù)pid,state組合
看了這個(gè)初步的設(shè)計(jì)之后,我總是感覺(jué)哪里不對(duì),于是找業(yè)務(wù)同學(xué)面對(duì)面溝通。
首先對(duì)于這個(gè)表的定義上,業(yè)務(wù)同學(xué)說(shuō)是歸屬于狀態(tài)表,也就意味著表中的每一個(gè)用戶都有唯一的狀態(tài)值對(duì)應(yīng),這個(gè)表中存儲(chǔ)的數(shù)據(jù)量會(huì)越來(lái)越大。
其次,按照state狀態(tài)字段去提取未完成處理的數(shù)據(jù),這個(gè)目標(biāo)環(huán)境是一套集群環(huán)境,集群中是按照id進(jìn)行分片,但是查詢條件按照state是有潛在問(wèn)題的。
比如業(yè)務(wù)層對(duì)于自增id的使用,在分片環(huán)境中可能是不唯一的,如上圖所示,可能id=1最多會(huì)存在N條同樣的數(shù)據(jù)(N為分片數(shù)),所以從業(yè)務(wù)需求上是不太能滿足的。
另外根據(jù)state=0去查詢數(shù)據(jù),這個(gè)查詢的復(fù)雜度較高,也就意味著state=0需要遍歷所有的分片,每個(gè)分片中會(huì)通過(guò)state=0的索引條件過(guò)濾數(shù)據(jù)最后匯總起來(lái),從使用上來(lái)說(shuō),這也是分庫(kù)分表的一個(gè)潛在影響,不是很建議這種使用方式。
還有字段id的設(shè)計(jì),按照狀態(tài)表的使用方式,也是不合理的,在一些特殊的場(chǎng)景中我們會(huì)采用id+其他業(yè)務(wù)屬性字段組合主鍵, 在這里這種場(chǎng)景顯然不是。
如果去掉id字段采用主鍵的模式,好像就違背了業(yè)務(wù)初衷根據(jù)id進(jìn)行區(qū)間提取的方式,細(xì)細(xì)品來(lái)這個(gè)需求是矛盾的。
如果按照最勉強(qiáng)的方式,建議是指定時(shí)間范圍內(nèi)處理,比如8點(diǎn)到9點(diǎn)之間處理,這個(gè)之外的時(shí)間范圍就不要做類似心跳或者服務(wù)檢測(cè)的處理了,對(duì)于業(yè)務(wù)側(cè)來(lái)說(shuō),還是能夠基本接受的,但是無(wú)論如何這不是一種最優(yōu)解,而且對(duì)于索引的使用實(shí)在有悖于中間件服務(wù)使用的初衷。
經(jīng)過(guò)進(jìn)一步的溝通,我們?cè)俅瓮诰蛐枨螅瑢?duì)于里面的表數(shù)據(jù)是如何處理的,業(yè)務(wù)同學(xué)說(shuō)其實(shí)表中的數(shù)據(jù)如果時(shí)間長(zhǎng)了之后是需要考慮數(shù)據(jù)清理的,所以按照這種模式,這個(gè)需求的就基本清晰了,和初始需求有比較大的差異。
到了這里需求的方向其實(shí)就有了大的轉(zhuǎn)折,這個(gè)表按照目前的需求其實(shí)使用日志表的模式要更好一些,比如表中的數(shù)據(jù)是按照如下的列表情況存儲(chǔ),以日期表為維度進(jìn)行存儲(chǔ)。
如果需要按照T+1的模式去處理未完成的數(shù)據(jù),整個(gè)復(fù)雜度只針對(duì)某一天的表執(zhí)行索引掃描,不會(huì)對(duì)其他的表產(chǎn)生關(guān)聯(lián)影響,而如果按照日期為單表存儲(chǔ),整個(gè)事情的自由度就更大了,按照state或者是pid的維度進(jìn)行查詢,效果都是可以接受的。
所以最后經(jīng)過(guò)討論和評(píng)估,其實(shí)沒(méi)有必要在中間件環(huán)境中進(jìn)行該類業(yè)務(wù)的處理,相比而言,性價(jià)比也不高。而基于中間件的服務(wù)承接的是偏核心的業(yè)務(wù),對(duì)于性能和負(fù)載的影響較為敏感,如果稀里糊涂就執(zhí)行了,其實(shí)后面會(huì)帶來(lái)一些其他的隱患。
通過(guò)這樣一個(gè)看起來(lái)簡(jiǎn)單的需求的溝通和挖掘,最后產(chǎn)生了不同的解決方案,對(duì)于業(yè)務(wù)側(cè)來(lái)說(shuō)還是比較滿意的,至少能夠超出他們的基本需求期望實(shí)現(xiàn),而且很多細(xì)節(jié)的工作也不需要更多的人工參與和后期討論,大大減少了溝通的邊際成本。
以上僅是一個(gè)需求的討論過(guò)程,不代表方案是最優(yōu)的,僅供參考。
本文轉(zhuǎn)載自微信公眾號(hào)「楊建榮的學(xué)習(xí)筆記」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系楊建榮的學(xué)習(xí)筆記公眾號(hào)。