盤點(diǎn)分庫分表中件間Mycat中的坑
一、介紹
公司最近在搞服務(wù)分離,數(shù)據(jù)切分的工作,因?yàn)橛唵魏陀唵雾?xiàng)表的數(shù)據(jù)量實(shí)在過大,而且每天都是以50萬的數(shù)據(jù)量在增長,基于現(xiàn)狀,項(xiàng)目組決定采用分庫的方式來解決當(dāng)前遇到的問題。
那具體怎么切分呢?
分庫的策略其實(shí)還比較簡單,主要是要確定分片的字段和策略。
最開始是想通過主鍵ID的奇、偶數(shù)來分兩個(gè)庫,order_1庫主要用于存儲(chǔ)奇數(shù)的ID,order_2庫主要用于存儲(chǔ)偶數(shù)的ID。
但是這種切分,局限性非常大,因?yàn)樽疃嘀荒芊謨蓚€(gè)庫,如果隨著數(shù)據(jù)量的增大,后面就沒很難在分了。
之后又想到了另一個(gè)分片字段:城市ID,因?yàn)橛唵伪砩嫌谐鞘蠭D的屬性,我們可以基于此進(jìn)行分庫,但是全國有幾百個(gè)城市,不可能分幾百個(gè)庫或者表,最后的討論結(jié)果是:
- 城市ID的生成固定大小,默認(rèn)三位數(shù),100~999
- 將訂單表分成三個(gè)庫,order_1、order_2、order_3
- 當(dāng)城市ID 在100~399區(qū)間,就存儲(chǔ)到order_1庫
- 當(dāng)城市ID 在400~699區(qū)間,就存儲(chǔ)到order_2庫
- 當(dāng)城市ID 在700~999區(qū)間,就存儲(chǔ)到order_3庫
通過城市ID進(jìn)行分片,如果后期訂單數(shù)據(jù)量進(jìn)一步過大,也可以進(jìn)一步的分庫!
基于Mysql數(shù)據(jù)庫,使用最廣、最成熟的分布式中間件當(dāng)屬于Mycat。
但是,自從采用Mycat中間件進(jìn)行分庫之后,發(fā)現(xiàn)了非常多的坑,下面我們就一起來看看這些坑點(diǎn)!
二、細(xì)數(shù)Mycat中的坑點(diǎn)
2.1、分頁查詢會(huì)出現(xiàn)全表掃描
當(dāng)我們把功能上線之后,測試人員在頁面上從末尾頁不停的往前分頁查詢訂單數(shù)據(jù)的時(shí)候,運(yùn)維平臺(tái)突然報(bào)監(jiān)控到很多慢 SQL 報(bào)警。
以下是運(yùn)維平臺(tái)監(jiān)控到的慢sql語句。
- SELECT id FROM order
- WHERE OrderCreateTime BETWEEN '2021-05-01 00:00:00' AND '2021-06-01 00:00:00'
- ORDER BY id DESC
- LIMIT 0, 151400
于是,運(yùn)維同學(xué)開始找到我們,說我們程序有問題,并在群里開始吐槽我們開發(fā)寫的啥玩意,但是我們開發(fā)堅(jiān)信程序沒有問題,通過查詢?nèi)罩?,我們排查到代碼的查詢語句是長這樣的。
- SELECT id FROM order
- WHERE OrderCreateTime BETWEEN '2021-05-01 00:00:00' AND '2021-06-01 00:00:00'
- ORDER BY id DESC
- LIMIT 151300, 100
與實(shí)際運(yùn)維給的慢sql語句中的LIMIT 0, 151400完全不符合。
包括我們自己也 review 了代碼,把 sql日志也截了圖,找技術(shù)總監(jiān)說理去。
之后,當(dāng)測試人員再次點(diǎn)擊分頁查詢的時(shí)候,運(yùn)維又監(jiān)控到了LIMIT 0, 151400這種怪異的SQL,我們花了好幾個(gè)小時(shí)排查,在本地跑測試,還是沒發(fā)現(xiàn)什么問題,真的感覺到了要懷疑人生了!
當(dāng)多次測試的時(shí)候,這個(gè)問題每次都能復(fù)現(xiàn),讓我想起了一個(gè)問題,是不是 Mycat 分頁的時(shí)候,對(duì)全表掃描了。
后來經(jīng)過查閱資料,才發(fā)現(xiàn)真有這個(gè)坑!
在分庫分表的情況下,宕 limit 的開始位置特別大的時(shí)候,例如大于某表的總行數(shù)時(shí),mycat 將查詢各個(gè)分表的結(jié)果集返,然后在mycat中進(jìn)行合并和排序,再返回結(jié)果。
例如,當(dāng)你原始的 sql 語句是這樣的:
- SELECT * FROM table_name WHERE type='xxx' ORDER BY create_time LIMIT 10000,1000
通過 mycat 執(zhí)行的結(jié)果,會(huì)是這樣的:
- SELECT * FROM table_name WHERE type='xxx' ORDER BY create_time LIMIT 0,11000
結(jié)果集特別大的情況會(huì)導(dǎo)致查詢很慢,嚴(yán)重的情況會(huì)直接導(dǎo)致 mycat OOM!
因此,在分庫分表的情況,不要用 mycat 進(jìn)行大批量的數(shù)據(jù)分頁查詢,通過條件過濾,減小分頁的數(shù)據(jù)量大小!
2.2、子查詢結(jié)果偶爾不完整
當(dāng)通過某些條件,篩選訂單項(xiàng)數(shù)據(jù)時(shí),測試人員反饋某些數(shù)據(jù)偶爾出現(xiàn)不完整。
具體SQL操作如下:
- select id,productName
- from orderItem
- where orderId in (
- select id from order where userName = '張三'
- )
預(yù)期的查詢結(jié)果時(shí):
- 1,"巧克力"
- 2,"可樂"
- 3,"果凍"
- 4,"蘋果手機(jī)"
但是實(shí)際查詢的時(shí)候,有時(shí)候的結(jié)果如下:
- 1,"巧克力"
- 2,"可樂"
- 4,"蘋果手機(jī)"
在網(wǎng)上查詢了相關(guān)的問題,在分庫分表的情況下,子查詢出了偶爾查詢不到完整數(shù)據(jù)外,還會(huì)出現(xiàn) mycat 內(nèi)部死鎖,因此盡量在代碼中不要使用子查詢,而是采用主鍵ID或是索引字段進(jìn)行單表查詢,這樣效率會(huì)大大提升!
2.3、跨分片join問題
由于歷史代碼的緣故,訂單服務(wù)里面存在很多各種連表操作,例如:
- select a.*,b.accountName,c.address
- from order a
- left join account b on a.accountId = b.id
- left join account_address c on b.id = c.accountId
- where a.orderId = 11110011
但是在走 mycat 查詢之后,直接報(bào)錯(cuò)!
原因是:mycat 目前只支持兩張分片表的 Join,如果要支持多張表需要自己改造程序代碼或者改造Mycat的源代碼。
2.4、部分SQL語法不支持
在實(shí)際使用的時(shí)候,發(fā)現(xiàn)還有部分sql語句是不支持的。
復(fù)制插入(不支持)
- insert into......select.....
復(fù)雜更新(不支持)
- update a, b set a.remark='備注' where a.id=b.id;
復(fù)雜刪除(不支持)
- delete a from a join b on a.id=b.id;
還有就是不支持跨庫連表操作!
2.5、不支持存儲(chǔ)過程創(chuàng)建和調(diào)用
有一點(diǎn),需要大家注意的,在走 mycat 中間件的方式與數(shù)據(jù)庫連接的時(shí)候,如果代碼中寫了存儲(chǔ)過程等語句,是 mycat 是不支持調(diào)用的,因此盡量不要使用!
三、小結(jié)
雖然上面介紹了 mycat 有一些坑,但是這些坑,通過一些優(yōu)化手段還是可以避免的。
實(shí)際上,mycat 作為分庫分表的中間件,也有許多的優(yōu)勢,例如下面官網(wǎng)的介紹。
據(jù)了解,mycat 是目前最成熟、使用最廣的中間件,因此大家在使用的時(shí)候,不需要帶有啥顧慮,對(duì)于以上的坑點(diǎn),盡可能的避免。