完整的事务支持是对于分布式数据库最大的挑战之一,在 PolarDB-X 2.0 中,我们基于对 InnoDB 的 CTS 扩展,在 XA 事务的基础上实现了 TSO 事务,并提供了兼具高效和一致性的 SI 隔离级别。我们提供了转账测试来模拟银行业务,测试分布式事务的一致性。
什么是转账测试?
转账是金融业务最核心的场景之一,也是对事务要求最高的能力。转账测试是 PolarDB-X 实现的一套测试框架,通过转账业务的模拟,验证我们分布式事务实现的正确性。转账的核心逻辑就是高并发地模拟两个账户之间的交易,并通过观察者查询数据是否一致。
转账测试的插件模式
虽然仅有两个场景,但事实上,由于数据库代码的复杂性,我们的测试应该尽可能覆盖所有的事务路径,事实上转账测试以插件的形式支持了超过十种场景:
- 交易场景
transfer_transaction: 使用 BEGIN/COMMIT 显式开启事务进行转账。transfer_pre_query: 转账前使用 FOR UPDATE 锁定数据,确保余额充足,这个场景可能会产生死锁,可以测试我们的分布式死锁检测功能。transfer_single_partition:仅发生在一个分片内的转账,测试我们单分片优化策略(一阶段提交)的正确性。transfer_big:在同一个事务内运行多笔转账。
- 查询场景
read_transaction:使用 BEGIN/COMMIT 显式开启事务,并查询所有账户中 balance 的数据,检查总和。read_transaction_readonly:使用START TRANSACTION READ ONLY显式开启只读事务,这种情况下会有针对只读事务的一定优化,需要测试。read_autocommit:同1,使用 autocommit=1 策略进行查询。read_sum:直接使用SELECT SUM(balance) FROM accounts查询总和。read_secondary_index:强制使用(FORCE INDEX)二级索引查询数据。read_global_secondary_index:强制使用(FORCE INDEX)全局二级索引查询数据。read_long:每查询一批账户 sleep 一段时间,测试长事务。read_long_transaction:显式开启事务,并多次查询数据,检查是否能保证 REPEATABLE READ。read_follower:PolarDB-X 支持备库一致性读的 feature,这个插件通过 Follower 查询数据并验证结果。
以上场景有独立开关,并且以混合负载的形式跑在同一个表上。
运行转账测试
话不多说,我们先尝试运行一下转账测试:
首先我们创建一个逻辑库:
CREATE DATABASE tt;
使用转账测试的 binary 连接到数据库上,准备数据:
transfer prepare -config=config_example_polarx.toml -dsn='root@tcp(127.0.0.1:3306)/tt'
这个语句会创建一个拆分表,并插入 10000 条数据(可配置):
CREATE TABLE `accounts` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`balance` INT DEFAULT 1000 COMMENT '账户余额',
`sid` INT,
`gsid` INT,
`version` INT DEFAULT 0 COMMENT '检查 follower_read 是否足够新'
) dbpartition by hash(`id`);
CREATE INDEX `idx_sid` ON `accounts` (`sid`);
CREATE GLOBAL INDEX `idx_gsid` ON `acounts` (`gsid`) dbpartition by hash(`gsid`);
转账测试通过 toml 来配置,每个plugin 有一个单独的 section,一个简单的配置如下:
row_counts = 100000
initial_balance = 1000
[transfer_transaction]
enabled = true
threads = 50
[read_transaction]
enabled = true
threads = 10
然后可以直接运行转账测试,就会按照配置自动运行各个 workload。
transfer run -config=config_example_polarx.toml -dsn='root@tcp(127.0.0.1:3306)/tt'
失败情况测试
分布式事务最重要的是测试异常情况下是否能正常工作,为了模拟事务的失败,我们在我们分布式事务的实现中各个关键点都埋下了 failpoint,可以通过 hint 触发,例如:
/*+TDDL(TRANSACTION_FAIL(AFTER_PREPARE))*/UPDATE `accounts` SET balance = balance + 1 WHERE id = 0;
/*+TDDL(TRANSACTION_FAIL(BEFORE_COMMIT))*/COMMIT;
我们可以在 toml 每个插件的配置中添加如 fail_ratio = 0.05 的参数,这可以在场景中注入一定量的符合预期的失败情况,验证在这种情况下我们是否依然能保证正确的隔离级别和一致性。
failpoint 只能测试一些预期之中的失败,我们还基于 chaos 系统对运行中的 PolarDB-X 任何节点模拟了网络失败、部分节点不可用、部分存储节点数据丢失等场景,转账测试会对各种异常情况的 PolarDB-X 实例进行测试,验证我们的实现是否正确。
总结
转账测试是 PolarDB-X 事务实现中最重要的测试之一,只有通过最完整可靠的测试,我们针对事务的优化才会上线。在实现上,我们对转账测试做了高度插件化的设计,保证我们可以很轻松地添加新的 case 到我们的测试中,我们会进一步完善转账测试,为 PolarDB-X 用户数据的正确性保驾护航。




