TiDB 事務(wù)限制詳解

在2.1及之前的 TiDB 版本中,對(duì)于事務(wù)的限制是和其他關(guān)系型數(shù)據(jù)庫(kù)而言比較特殊的地方,很多用戶(hù)在使用過(guò)程中總是會(huì)感覺(jué)比較困惑,本文針對(duì)事務(wù)限制做一些詳細(xì)的說(shuō)明,希望能夠幫助大家理解。

官方定義

由于 TiDB 分布式兩階段提交的要求,修改數(shù)據(jù)的大事務(wù)可能會(huì)出現(xiàn)一些問(wèn)題。因此,TiDB 特意對(duì)事務(wù)大小設(shè)置了一些限制以減少這種影響:

每個(gè)鍵值對(duì)不超過(guò) 6MB

鍵值對(duì)的總數(shù)不超過(guò) 300,000

鍵值對(duì)的總大小不超過(guò) 100MB

詳見(jiàn)PingCAP 官方文檔,https://pingcap.com/docs-cn/sql/mysql-compatibility/


相信鍵值對(duì)應(yīng)該比較容易理解,畢竟 TiDB 底層存儲(chǔ)選用的是 rocksdb 引擎,一種基于 key-value 的存儲(chǔ)結(jié)構(gòu)。而每個(gè)鍵值對(duì)的大小和總大小限制分別是6MB 和100MB,這個(gè)應(yīng)該也比較容易理解。關(guān)鍵在于每個(gè)事物包含鍵值對(duì)的總數(shù)不超過(guò)30W 這個(gè)經(jīng)常會(huì)引起一些誤解,下面做一些詳細(xì)說(shuō)明。

如何理解30W

很多人第一眼看上去,以為是一個(gè)事務(wù)涉及的行數(shù)不能超過(guò)30W,但其實(shí)不是這樣的,首先需要了解 TiKV 對(duì)于結(jié)構(gòu)化數(shù)據(jù)是如何轉(zhuǎn)化為 key-value 結(jié)構(gòu)存儲(chǔ)的。

對(duì)于 key-value 結(jié)構(gòu)的數(shù)據(jù),可以認(rèn)為結(jié)構(gòu)如下

當(dāng)插入一條數(shù)據(jù)時(shí),tikv 是如何記錄這條數(shù)據(jù)呢,包含以下幾個(gè)步驟:

1、插入數(shù)據(jù)本身

2、插入唯一索引

3、插入普通索引

綜上,當(dāng)執(zhí)行 insert 事務(wù)時(shí),30W 限制需要除以所有索引的數(shù)量(包含主鍵和唯一索引)。


下面考慮當(dāng)刪除一條數(shù)據(jù)時(shí),tikv 是如何處理的。首先需要明確,rocksdb 引擎所有的操作都是新增,所以刪除也是插入,只是插入了一條 flag = del 的記錄,具體情況如下:

1、插入數(shù)據(jù)本身的刪除標(biāo)記

2、插入唯一索引的刪除標(biāo)記

3、插入普通索引的刪除標(biāo)記

綜上,當(dāng)執(zhí)行delete 事務(wù)時(shí),30W 限制需要除以所有索引的數(shù)量(包含主鍵和唯一索引)。


更新比較復(fù)雜,放到最后來(lái)說(shuō)明。首先來(lái)看,更新的是非主鍵且無(wú)索引字段的情況。

這種情況,只需要修改記錄本身的內(nèi)容即可,也就是下面一步:

1、插入數(shù)據(jù)本身即可

綜上,非主鍵且無(wú)索引字段更新,30W 限制就是30W。


其次,來(lái)看更新的是非主鍵,但包含索引的字段情況。

1、數(shù)據(jù)本身

2、如果更新字段上有唯一索引

3、如果更新字段上有普通索引

綜上,非主鍵但索引相關(guān)字段的更新,30W 限制需要除以(1 + 字段涉及索引數(shù)量 * 2)。


最后來(lái)看當(dāng)更新的是主鍵字段的情況。從上面插入的描述中可以看出,無(wú)論是數(shù)據(jù)本身,還是索引,都包含了 pk,所以主鍵更新會(huì)觸發(fā)所有的key 更新,具體如下:

1、數(shù)據(jù)本身

2、所有的唯一索引

3、所有的普通索引

綜上,主鍵字段的更新,30W 限制需要除以(所有索引的數(shù)量 * 2)。

30W 鍵值對(duì)的轉(zhuǎn)換

總結(jié)如下:

具體案例:

CREATE TABLE `t1` (

? `id` int(11) NOT NULL AUTO_INCREMENT,

? `name` char(10) CHARSET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,

? `age` int(11) DEFAULT NULL,

? PRIMARY KEY (`id`),

? KEY `idx_name` (`name`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

以上面的簡(jiǎn)單表結(jié)構(gòu)為例,該表有自增主鍵,外加1個(gè)普通索引,那么上面的事務(wù)限制對(duì)應(yīng)的記錄數(shù)為


事務(wù)的其他限制

除了上面 rocksdb 層的限制意外,tidb 中對(duì)于事務(wù)還有另外一個(gè)限制

1、參數(shù) stmt-count-limit,默認(rèn)值是5000。

StmtCountLimit limits the max count of statement inside a transaction.

也就是一個(gè)事務(wù)里面,默認(rèn)最多包含5000條 SQL statement,在不超過(guò)上面 rocksdb 層的幾個(gè)限制的前提下,這個(gè)參數(shù)可以修改 tidb 的配置文件進(jìn)行調(diào)整。


2、另外在某些場(chǎng)景下,例如執(zhí)行insert into? select 的時(shí)候,可能會(huì)遇到下面的報(bào)錯(cuò)

ERROR 1105 (HY000): BatchInsert failed with error: [try again later]: con:3877 txn takes too much time, start: 405023027269206017, commit: 405023312534306817????

這個(gè)主要是有一個(gè)隱藏參數(shù),max-txn-time-use,默認(rèn)值是 gc_life_time - 10s,也就是590

具體參考 PingCAP GitHub 上的文檔:https://github.com/pingcap/tidb/blob/master/config/config.toml.example#L240

# The max time a Txn may use (in seconds) from its startTS to commitTS.# We use it to guarantee GC worker will not influence any active txn. Please make sure that this# value is less than gc_life_time - 10s.

所以我們要盡量保證一個(gè)事務(wù)在這個(gè)gc_life_time - 10s 的時(shí)間內(nèi)完成,也可以通過(guò)調(diào)整 gc 時(shí)間 + 修改這個(gè)參數(shù)來(lái)避免這個(gè)問(wèn)題,可能 tidb 的配置文件中沒(méi)有放出這個(gè)參數(shù),可以手動(dòng)編輯,加入這個(gè)值。當(dāng)然了,更好的辦法應(yīng)該是開(kāi)啟 tidb_batch_insert 參數(shù)來(lái)規(guī)避單個(gè)事務(wù)過(guò)大的問(wèn)題。

如何繞開(kāi)大事務(wù)的限制

官方提供內(nèi)部 batch 的方法,來(lái)繞過(guò)大事務(wù)的限制,分別由三個(gè)參數(shù)來(lái)控制:

tidb_batch_insert

作用域: SESSION默認(rèn)值: 0這個(gè)變量用來(lái)設(shè)置是否自動(dòng)切分插入數(shù)據(jù)。僅在 autocommit 開(kāi)啟時(shí)有效。 當(dāng)插入大量數(shù)據(jù)時(shí),可以將其設(shè)置為 true,這樣插入數(shù)據(jù)會(huì)被自動(dòng)切分為多個(gè) batch,每個(gè) batch 使用一個(gè)單獨(dú)的事務(wù)進(jìn)行插入。

tidb_batch_delete

作用域: SESSION默認(rèn)值: 0這個(gè)變量用來(lái)設(shè)置是否自動(dòng)切分待刪除的數(shù)據(jù)。僅在 autocommit 開(kāi)啟時(shí)有效。 當(dāng)刪除大量數(shù)據(jù)時(shí),可以將其設(shè)置為 true,這樣待刪除數(shù)據(jù)會(huì)被自動(dòng)切分為多個(gè) batch,每個(gè) batch 使用一個(gè)單獨(dú)的事務(wù)進(jìn)行刪除。

tidb_dml_batch_size

作用域: SESSION默認(rèn)值: 20000這個(gè)變量用來(lái)設(shè)置自動(dòng)切分插入/待刪除數(shù)據(jù)的的 batch 大小。僅在 tidb_batch_insert 或 tidb_batch_delete 開(kāi)啟時(shí)有效。 需要注意的是,當(dāng)單行總數(shù)據(jù)大小很大時(shí),20k 行總數(shù)據(jù)量數(shù)據(jù)會(huì)超過(guò)單個(gè)事務(wù)大小限制。因此在這種情況下,用戶(hù)應(yīng)當(dāng)將其設(shè)置為一個(gè)較小的值。

針對(duì) update 場(chǎng)景,官方還是建議通過(guò) limit 的方式來(lái)循環(huán)操作,目前并未提供內(nèi)部 batch update 的參數(shù)開(kāi)關(guān)。

需要注意的是,開(kāi)啟了 batch 功能之后,大事務(wù)的完整性就沒(méi)法保證了,只能保證每個(gè)批次的事務(wù)完整性。當(dāng)然,數(shù)據(jù)庫(kù)的最佳實(shí)踐依然是由程序或 DBA 來(lái)控制事務(wù)的大小,尤其是針對(duì)分布式數(shù)據(jù)庫(kù),建議每個(gè)batch 控制在100條左右,高并發(fā)的寫(xiě)入,同時(shí)避免熱點(diǎn)現(xiàn)象,才能發(fā)揮TiDB? 分布式的優(yōu)勢(shì)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容