作者:杨涛涛
资深数据库专家,专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课程培训等工作。
本文来源:原创投稿
* 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
长期以来,在 MySQL 的开发规范里一般都会这么写:禁止大事务!话题转到 TiDB ,依然应该是:禁止大事务!
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 语句。
只需要在配置文件里加上如下选项就可涵盖大部分事务:
所以还是得禁止大事务,拆分为小事务批量处理。
有主键,并且主键连续 有主键,主键不连续
表无主键(类似第一种)
第一种最容易拆分,根据主键来划分不同的块即可。
举个例子:
表t1有100W条记录,除主键外有6个索引,对表t1进行 update :
update ytt.t1 set log_date = current_date() - interval ceil(rand()*1000) day where 1;
在默认自动提交下,这条语句其实就是隐式大事务语句,在内部转换为 :
beginupdate ytt.t1 set log_date = current_date() - interval ceil(rand()*1000) day where 1;commit;
假设表t1主键为自增且连续,那很简单,把这个事务分为10个小事务,每次更新10W条记录,而不是一次性更新100W条。脚本大致如下:
root@ytt-ubuntu:~/scripts# cat update_table_batch#!/bin/sh# TiDB 拆分更新for i in `seq 1 10`;do min_id=$(((i-1)*100000+1)) max_id=$((i*100000)) queries="update t1 set log_date = date_sub(current_date(), interval ceil(rand()*1000) day) \ where id >=$min_id and id<=$max_id;" mysql --login-path=TiDB_login -D ytt -e "$queries" &done
第二种,针对不连续的自增主键场景。
第一种最为常见,在 TiDB 里强烈不推荐使用连续自增字段来做主键,这会导致潜在的单 region 写热点问题。所以自增主键推荐使用 auto_random 特性来随机写入,避免连续性。
上面脚本里列出的方法就变得不太适合。那该怎么拆呢?可以稍加变通,用窗口函数 row_number() 来补模拟主键,更新表改为t2,改写后的脚本大致如下:
root@ytt-ubuntu:~/scripts# cat update_table_batch#!/bin/sh# TiDB 拆分更新for i in `seq 1 10`;do min_id=$(((i-1)*100000+1)) max_id=$((i*100000)) queries="update t2 a, (select *,row_number() over(order by id) rn from t2) b set a.log_date = \ date_sub(current_date(), interval ceil(rand()*1000) day) \ where a.id = b.id and (b.rn>=$min_id and b.rn<=$max_id);" mysql --login-path=TiDB_login -D ytt -e "$queries" &done
其实以上两种思路已经包含了绝大多数拆分场景。MySQL 或者 TiDB 对于没有主键的表都默认包含一个隐式自增 ID 来区分行之间关系,所以为了避免在 DML 层来增加复杂的拆分策略,依然强烈建议使用显式主键!
结语
技术分享 | MySQL 和 TiDB 互相快速导入全量数据
新特性解读 | MySQL 8.0 通用表达式(WITH)深入用法
社区近期动态

点一下“阅读原文”了解更多资讯




