TiDB 对大事务的简单拆分
【是否原创】是
【首发渠道】爱可生开源社区
【首发渠道链接】https://mp.weixin.qq.com/s/p197w5Lt7BZPft7YeIT8IQ
【正文】
作者:杨涛涛
资深数据库专家,专研 MySQL 十余年。擅长 MySQL、TiDB、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课程培训等工作。
本文来源:原创投稿
引言
长期以来,在 MySQL 的开发规范里一般都会这么写:禁止大事务!话题转到 TiDB ,依然应该是:禁止大事务!
TiDB 由于事务本身分布式特性,加之后台 RAFT 复制导致的写放大,非常不推荐使用大事务。
TiDB 在 4.0 之前的版本对事务要求有些过于细致,比如:
单个事务包含的 SQL 语句不超过 5000 条
单条 KV entry 不超过 6MB
KV entry 的总条数不超过 30w
KV entry 的总大小不超过 100MB
上面的几点限制会导致一些 DML 语句写入受阻,比如下面这三类经典无过滤条件语句:
insert … select … where 1
update … where 1
delete from … where 1
非常容易出现事务过大的错误:*ERROR 8004 (HY000): transaction too large, len:300001*。一般有如下方法来规避这个问题:
针对 Insert、delete 语句开启无安全保证的 dml batch 特性:TiDB_batch_insert、TiDB_batch_delete。
分块拆分整条 update 语句。
在 4.0 之后的版本里,除了单个 kv entry 大小依然限制为最大 6MB,其他几个限制全部被取消。
只需要在配置文件里加上如下选项就可涵盖大部分事务:
performance.txn-total-size-limit: 10737418240(范围 1G 到 10G)
那是不是 4.0 版本后就可以随意写事务了?当然不是!由于 TiDB 的写放大,也会连带导致内存占用成倍增长,对其他业务会有很大影响,所以 TiDB 对最大事务支持硬性限制其为 10G。比如用 DM 来同步 MySQL 数据到 TiDB ,大事务会导致内存加大,写入延迟剧增,进而影响其他的写性能。
所以还是得禁止大事务,拆分为小事务批量处理。
那如何对大事务进行拆分呢?单从业务方面讲,业务类型不同,对应的拆分方法不同,可能一本书都写不完。这里我仅仅从数据库角度,细分为从表角度,再进一步到 DML 语句角度如何拆分。
上面列的这三条经典语句看起来很简单,但是没有过滤条件,如果表索引数非常多,即使表记录数不大,也会是一个大事务,只不过这个事务只包含一条 DML 语句。但是这类语句的拆分实际上要看表结构怎么定义,分为三种:
有主键,并且主键连续
有主键,主键不连续
表无主键(类似第一种)
第一种最容易拆分,根据主键来划分不同的块即可。
举个例子:
表 t1 有 100W 条记录,除主键外有 6 个索引,对表 t1 进行 update :
在默认自动提交下,这条语句其实就是隐式大事务语句,在内部转换为 :
假设表 t1 主键为自增且连续,那很简单,把这个事务分为 10 个小事务,每次更新 10W 条记录,而不是一次性更新 100W 条。脚本大致如下:
第二种,针对不连续的自增主键场景。
第一种最为常见,在 TiDB 里强烈不推荐使用连续自增字段来做主键,这会导致潜在的单 region 写热点问题。所以自增主键推荐使用 auto_random 特性来随机写入,避免连续性。
上面脚本里列出的方法就变得不太适合。那该怎么拆呢?可以稍加变通,用窗口函数 row_number() 来补模拟主键,更新表改为 t2,改写后的脚本大致如下:
其实以上两种思路已经包含了绝大多数拆分场景。MySQL 或者 TiDB 对于没有主键的表都默认包含一个隐式自增 ID 来区分行之间关系,所以为了避免在 DML 层来增加复杂的拆分策略,依然强烈建议使用显式主键!
结语
虽然 TiDB 4.0 版本后,对大事务支持已经非常好,但这不是可以随便用大事务的理由,还是要做好表设计提前拆、检索表数据提前拆等拆分策略,才能更好的让数据库服务好业务。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/1e57d25126d11d3735ed773b4】。文章转载请联系作者。
评论