TiDB 事务限制详解

2019-01-15  本文已影响0人  CocaLi

在2.1及之前的 TiDB 版本中,对于事务的限制是和其他关系型数据库而言比较特殊的地方,很多用户在使用过程中总是会感觉比较困惑,本文针对事务限制做一些详细的说明,希望能够帮助大家理解。

官方定义

由于 TiDB 分布式两阶段提交的要求,修改数据的大事务可能会出现一些问题。因此,TiDB 特意对事务大小设置了一些限制以减少这种影响:

每个键值对不超过 6MB

键值对的总数不超过 300,000

键值对的总大小不超过 100MB

详见PingCAP 官方文档,https://pingcap.com/docs-cn/sql/mysql-compatibility/


相信键值对应该比较容易理解,毕竟 TiDB 底层存储选用的是 rocksdb 引擎,一种基于 key-value 的存储结构。而每个键值对的大小和总大小限制分别是6MB 和100MB,这个应该也比较容易理解。关键在于每个事物包含键值对的总数不超过30W 这个经常会引起一些误解,下面做一些详细说明。

如何理解30W

很多人第一眼看上去,以为是一个事务涉及的行数不能超过30W,但其实不是这样的,首先需要了解 TiKV 对于结构化数据是如何转化为 key-value 结构存储的。

对于 key-value 结构的数据,可以认为结构如下

当插入一条数据时,tikv 是如何记录这条数据呢,包含以下几个步骤:

1、插入数据本身

2、插入唯一索引

3、插入普通索引

综上,当执行 insert 事务时,30W 限制需要除以所有索引的数量(包含主键和唯一索引)。


下面考虑当删除一条数据时,tikv 是如何处理的。首先需要明确,rocksdb 引擎所有的操作都是新增,所以删除也是插入,只是插入了一条 flag = del 的记录,具体情况如下:

1、插入数据本身的删除标记

2、插入唯一索引的删除标记

3、插入普通索引的删除标记

综上,当执行delete 事务时,30W 限制需要除以所有索引的数量(包含主键和唯一索引)。


更新比较复杂,放到最后来说明。首先来看,更新的是非主键且无索引字段的情况。

这种情况,只需要修改记录本身的内容即可,也就是下面一步:

1、插入数据本身即可

综上,非主键且无索引字段更新,30W 限制就是30W。


其次,来看更新的是非主键,但包含索引的字段情况。

1、数据本身

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

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

综上,非主键但索引相关字段的更新,30W 限制需要除以(1 + 字段涉及索引数量 * 2)。


最后来看当更新的是主键字段的情况。从上面插入的描述中可以看出,无论是数据本身,还是索引,都包含了 pk,所以主键更新会触发所有的key 更新,具体如下:

1、数据本身

2、所有的唯一索引

3、所有的普通索引

综上,主键字段的更新,30W 限制需要除以(所有索引的数量 * 2)。

30W 键值对的转换

总结如下:

具体案例:

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

以上面的简单表结构为例,该表有自增主键,外加1个普通索引,那么上面的事务限制对应的记录数为

事务的其他限制

除了上面 rocksdb 层的限制意外,tidb 中对于事务还有另外一个限制

1、参数 stmt-count-limit,默认值是5000。

StmtCountLimit limits the max count of statement inside a transaction.

也就是一个事务里面,默认最多包含5000条 SQL statement,在不超过上面 rocksdb 层的几个限制的前提下,这个参数可以修改 tidb 的配置文件进行调整。

2、另外在某些场景下,例如执行insert into  select 的时候,可能会遇到下面的报错

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

这个主要是有一个隐藏参数,max-txn-time-use,默认值是 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.

所以我们要尽量保证一个事务在这个gc_life_time - 10s 的时间内完成,也可以通过调整 gc 时间 + 修改这个参数来避免这个问题,可能 tidb 的配置文件中没有放出这个参数,可以手动编辑,加入这个值。当然了,更好的办法应该是开启 tidb_batch_insert 参数来规避单个事务过大的问题。

如何绕开大事务的限制

官方提供内部 batch 的方法,来绕过大事务的限制,分别由三个参数来控制:

tidb_batch_insert

作用域: SESSION默认值: 0这个变量用来设置是否自动切分插入数据。仅在 autocommit 开启时有效。 当插入大量数据时,可以将其设置为 true,这样插入数据会被自动切分为多个 batch,每个 batch 使用一个单独的事务进行插入。

tidb_batch_delete

作用域: SESSION默认值: 0这个变量用来设置是否自动切分待删除的数据。仅在 autocommit 开启时有效。 当删除大量数据时,可以将其设置为 true,这样待删除数据会被自动切分为多个 batch,每个 batch 使用一个单独的事务进行删除。

tidb_dml_batch_size

作用域: SESSION默认值: 20000这个变量用来设置自动切分插入/待删除数据的的 batch 大小。仅在 tidb_batch_insert 或 tidb_batch_delete 开启时有效。 需要注意的是,当单行总数据大小很大时,20k 行总数据量数据会超过单个事务大小限制。因此在这种情况下,用户应当将其设置为一个较小的值。

针对 update 场景,官方还是建议通过 limit 的方式来循环操作,目前并未提供内部 batch update 的参数开关。

需要注意的是,开启了 batch 功能之后,大事务的完整性就没法保证了,只能保证每个批次的事务完整性。当然,数据库的最佳实践依然是由程序或 DBA 来控制事务的大小,尤其是针对分布式数据库,建议每个batch 控制在100条左右,高并发的写入,同时避免热点现象,才能发挥TiDB  分布式的优势。

上一篇下一篇

猜你喜欢

热点阅读