随着在线交易、电商行业的发展,业务系统的热点并发压力逐渐成为一种挑战。热点账户短时间内余额大量更新,或者热门商品在营销活动中限时秒杀,都是这种场景的直接体现。热点更新的本质是短时间内对数据库中的同一行数据的某些字段值进行高并发的修改(余额,库存等),这其中的瓶颈主要在于关系型数据库为了保持事务一致性,对数据行的更新都需要经过“加锁->更新->写日志提交->释放锁”的过程,而这个过程实质上是串行的。所以,提高热点行更新能力的关键在于如何尽可能缩短持有锁的时间。
虽然学术界很早就提出了“提前解行锁(Early Lock Release)”的方案(即 ELR),但是因为 ELR 的异常处理场景非常复杂,业界很少有成熟的工业实现。OceanBase 数据库在这个问题上通过持续的探索,提出了一种基于分布式架构的实现方式,提升类似业务场景中单行并发更新的能力,作为 OceanBase 数据库“可扩展的 OLTP ”中的关键能力之一。
本篇文章中,我们将通过构造一个多并发单行更新的场景,介绍 OceanBase 数据库 ELR 特性的使用方法和效果对比。因为是在多并发压力场景下验证,我们建议至少使用和本例中相同的节点规格进行体验和验证,达到更好的效果。关于 OceanBase 数据库 ELR 的设计和原理实现,因其较为复杂,本文暂不做详细讨论。
本例中我们使用一台 16C-128GB 配置的节点,下面我们来分步骤体验 OceanBase 数据库 ELR 特性。
1.创建测试表,插入测试数据
首先我们在测试库中创建一张表,并插入测试数据。
CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ); INSERT INTO sbtest1 VALUES(1,0,'aa','aa');
本例中我们通过类似
UPDATE sbtest1 SET k=k+1 WHERE id=1
的语句,以主键查询方式针对
k
列进行并发更新,您也可以插入更多数据进行测试,但因为是针对单行并发的压测,对整体结果基本没有影响。
2.构造并发更新场景
本例中,我们使用 Python 多线程的方式来模拟并发更新,同时启动 50 个 Thread,每个 Thread 并发的对 id=1 的行数据将 k 字段值 +1。您在自己搭建的环境中也可以直接使用下面的脚本
ob_elr.py
进行测试。只需要将脚本中的数据库连接信息修改一下即可使用。
#!/usr/bin/env python3 from concurrent.futures import ThreadPoolExecutor import pymysql import time import threading # database connection info config = { 'user': 'root@test', 'password': 'root', 'host': '172.19.81.183', 'port': 2881, 'database': 'test' } # parallel thread and updates in each thread parallel = 50 batch_num = 2000 # update query def update_elr(): update_hot_row = ("update sbtest1 set k=k+1 where id=1") cnx = pymysql.connect(**config) cursor = cnx.cursor() for i in range(0,batch_num): cursor.execute(update_hot_row) cursor.close() cnx.close() start=time.time() with ThreadPoolExecutor(max_workers=parallel) as pool: for i in range(parallel): pool.submit(update_elr) end = time.time() elapse_time = round((end-start),2) print('Parallel Degree:',parallel) print('Total Updates:',parallel*batch_num) print('Elapse Time:',elapse_time,'s') print('TPS on Hot Row:' ,round(parallel*batch_num/elapse_time,2),'/s')
3.默认配置下执行测试
作为对比参照,我们先在默认不开启 ELR 的情况下测试,在测试机器上直接执行
ob_elr.py
脚本。
本例中我们采用 50 并发数,总计更新 100000 次。
./ob_elr.py
执行完成,测试脚本输出执行时间和 TPS:
[root@obce00 ~]# ./ob_elr.py Parallel Degree: 50 Total Updates: 100000 Elapse Time: 54.5 s TPS on Hot Row: 1834.86 /s
测试结果
在不开启 ELR 的默认配置下,本例测试环境中,单行并发更新 TPS 为1834.86/s
4.打开 OceanBase 数据库 ELR 配置
接下来我们开启 OceanBase 数据库的热点行功能,首先需要登录到
sys
管理员租户。
[root@obce00 ~]# obclient -h127.0.0.1 -P2881 -uroot@sys -Doceanbase -A -p -c
然后进行如下两个参数的设置。其中
enable_early_lock_release
参数的生效范围,既可以指定具体租户,也可以指定全部租户,即
tenant=all
。
ALTER SYSTEM SET _max_elr_dependent_trx_count = 1000; ALTER SYSTEM SET enable_early_lock_release=true tenant= test;
5.开启 OceanBase 数据库 ELR 进行测试
开启热点行功能后,我们再次执行测试。在执行前我们先查看表
sbtest
中
id=1
的记录,
k
字段值为 100000,这是因为刚刚执行了一轮默认配置下的 100000 次的更新。
SELECT * FROM sbtest1 WHERE id=1; +----+--------+----+-----+ | id | k | c | pad | +----+--------+----+-----+ | 1 | 100000 | aa | aa | +----+--------+----+-----+ 1 row in set
接下来我们在测试机器上再次执行
ob_elr.py
。仍然采用 50 并发数,总计更新 100000 次。
./ob_elr.py
执行完成,测试脚本会输出执行时间和 TPS:
[root@obce00 ~]# ./ob_elr.py Parallel Degree: 50 Total Updates: 100000 Elapse Time: 12.16 s TPS on Hot Row: 8223.68 /s
测试结果
OceanBase 数据库在开启 ELR 提前解行锁能力后,单行更新的 TPS 达到 8223.68/s,相比默认配置下提升 4.5 倍左右。 同时我们可以看到表
sbtest1
的
k
值为 200000,即本次也更新了 100000 次。
SELECT * FROM sbtest1 WHERE id=1; +----+--------+----+-----+ | id | k | c | pad | +----+--------+----+-----+ | 1 | 200000 | aa | aa | +----+--------+----+-----+ 1 row in set
本例中仅介绍了单行并发更新的场景,OceanBase 数据库的 ELR 能力还支持多语句事务的并发更新,根据语句数量和场景差异,同样可以获得显著的性能提升。
此外 OceanBase 数据库的 ELR 还可以应用在多地部署高网络延迟的场景中,例如单个事务在默认场景下 30 ms,并发下开启 ELR 可以获得进百倍的 TPS 吞吐量提升。
由于 OceanBase 数据库的日志协议基于 Multi-Paxos 构建,并且优化了 2PC 提交过程,OceanBase 数据库在开启 ELR 后,如果发生节点宕机重启、Leader 切换,仍然可以保证事务的一致性。您可以尝试构造这些实验进行体验。