如何支持热点更新场景

本文介绍了优化热点更新场景的方法。

背景

数据库中数据更新的顺序为LOCK -> UPDATE -> UNLOCK,当对数据库中的同一条记录有大量修改请求时,会造成大量的锁争抢与锁等待。请求量增加会导致TPS下降,延迟飙升。例如秒杀场景中对于商品库存的扣减。

为解决以上问题PolarDB-X推荐您使用在数据库内核中进行批处理的方案,即对该条记录进行的更新操作使用组提交,其数据更新的顺序变为LOCK-> GROUP UPDATE -> UNLOCK,从而减少锁争抢。结合流水线处理等优化,可以大大提高该场景的TPS,详细信息请参见测试结果

版本限制

仅支持PolarDB-X企业版MySQL 5.7实例。

注意事项

热点更新功能(INVENTORY HINT)只支持单分片事务场景,不支持跨数据库场景。

前置参数配置

重要

使用SET GLOBAL设置参数后,对当前会话是不生效的,需要开启新会话才能生效。

  1. 开启HOTSPOT相关功能,使用高权限账号执行。

    SET GLOBAL HOTSPOT=ON;
    SET GLOBAL HOTSPOT_LOCK_TYPE=ON
  2. 关闭共享ReadView。

    若已开启共享ReadView,则应当先将共享ReadView进行关闭,而后使用热点更新的能力。

    -- 查看共享ReadView是否打开,false是关闭状态,true是开启状态
    SHOW VARIABLES LIKE '%SHARE_READ_VIEW%';

    如果共享ReadView是开启状态,则可通过如下SQL全局关闭:

    SET GLOBAL SHARE_READ_VIEW = FALSE;
    重要

    全局关闭共享ReadView可能会导致一部分查询变慢。

  3. 修改事务类型。

    -- 全局级别设置
    SET GLOBAL TRANSACTION_POLICY = 'XA'; 
    SET GLOBAL ENABLE_XA_TSO = FALSE;
    -- 或设置为session级别
    SET SESSION TRANSACTION_POLICY = 'XA'; 
    SET SESSION ENABLE_XA_TSO = FALSE;

    检查事务类型:

    SHOW VARIABLES LIKE 'TRANSACTION_POLICY';
    说明
    • 如果事务类型不是XA,则需要将事务类型切换为XA,然后才能使用热点更新能力,命令如下(或者在每次使用前设置Session级别)。

    • 该修改会导致事务类型变更为XA,请自行判断该变化是否会产生非预期结果。

使用语法

在业务的UPDATE语句中添加INVENTORY HINT,并将该语句置为最后的一条。

BEGIN;
UPDATE /*+ commit_on_success rollback_on_fail target_affect_row(number)*/ table_reference 
    SET assignment_list
    [WHERE where_condition];
COMMIT | ROLLBACK; -- 取决于更新成功还是失败,选择其一即可
重要

WHERE条件应为主键或唯一键的等值条件,且不支持带有全局索引的表(可包含本地索引)。

参数说明:

名称

是否必选

说明

commit_on_success

必选

如果该语句成功,则进行提交,连同该语句之前的未提交语句一起提交。

rollback_on_fail

可选

如果该语句失败,则进行回滚,连同该语句之前的未提交语句一并回滚。

target_affect_row(number)

可选

校验更新的行数是否符合预期,若不符合则更新失败。

使用示例

  1. 示例表。

    CREATE TABLE table_test  (
        id INT AUTO_INCREMENT PRIMARY KEY,
        c INT NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    CREATE TABLE table_test_2  (
        id INT AUTO_INCREMENT PRIMARY KEY,
        c INT NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  2. 添加commit_on_success以使用组提交等针对热点更新场景的优化(id为主键,使用如下语句对id=1的记录进行更新时,若更新成功则自动提交)。

    BEGIN;
    UPDATE /*+ commit_on_success*/ table_test SET c = c - 1 WHERE id = 1;
    COMMIT;
    说明

    COMMIT | ROLLBACK按需选择其一即可。

  3. 使用rollback_on_fail,可使得更新失败时自动进行回滚。

    BEGIN;
    UPDATE /*+ commit_on_success rollback_on_fail*/ table_test SET c = c - 1 WHERE id = 1;
    COMMIT;
  4. 使用target_affect_row(number),使得该UPDATE语句的预期更新行数为number,若不等于number,则更新失败。

    BEGIN;
    UPDATE /*+ commit_on_success rollback_on_fail target_affect_row(1)*/ table_test SET c = c - 1 WHERE id = 1;
    COMMIT;
  5. 在带有INVENTORY HINTUPDATE语句前,可对同一个物理库中的表进行DML操作。

    BEGIN;
    INSERT INTO table_test_2 VALUES (1,1);
    UPDATE /*+ commit_on_success rollback_on_fail target_affect_row(1)*/ table_test SET c = c - 1 WHERE id = 1;
    COMMIT;

查看热点组提交是否生效

  • 使用如下SQL查看组提交状态,Group_update_leader_count增加则说明触发了热点组提交的优化逻辑。

    SHOW GLOBAL STATUS LIKE "%Group_update%";
    +---------------------------------------+--------+
    | Variable_name                         | Value  |
    +---------------------------------------+--------+
    | Group_update_fail_count               | 54     |
    | Group_update_follower_count           | 962869 |
    | Group_update_free_count               | 2      |
    | Group_update_group_same_count         | 0      |
    | Group_update_gu_leak_count            | 0      |
    | Group_update_ignore_count             | 0      |
    | Group_update_insert_dup               | 0      |
    | Group_update_leader_count             | 168292 |
    | Group_update_lock_fail_count          | 0      |
    | Group_update_mgr_recycle_queue_length | 0      |
    | Group_update_recycle_queue_length     | 0      |
    | Group_update_reuse_count              | 23329  |
    | Group_update_total_count              | 2      |
    +---------------------------------------+--------+
  • 使用show physical full processlist查看UPDATE的状态,是否出现HOTSPOT字样。

    SHOW physical FULL processlist WHERE command != 'Sleep';
    +-------+------+-----+---------+-------------+---------+------+-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Group | Atom | Id  | User    | db          | Command | Time | State                   | Info                                                                                                                                                                                         |
    +-------+------+-----+---------+-------------+---------+------+-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    |     0 |    0 |  56 | diamond | test_000001 | Query   |    0 | hotspot wait for commit | /*DRDS /127.0.0.1/12e774cab8800000-128/0// */UPDATE  /*+COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ `test_hotline_lZTr` AS `test_hotline` SET `b` = (`b` + 1) WHERE (`a` = 1) |
    |     0 |    0 | 822 | diamond | test_000001 | Query   |    0 | query end               | /*DRDS /127.0.0.1/12e774c4e9400000-563/0// */UPDATE  /*+COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ `test_hotline_lZTr` AS `test_hotline` SET `b` = (`b` + 1) WHERE (`a` = 1) |
    |     0 |    0 | 831 | diamond | test_000001 | Query   |    0 | hotspot wait for commit | /*DRDS /127.0.0.1/12e774c551000000-509/0// */UPDATE  /*+COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ `test_hotline_lZTr` AS `test_hotline` SET `b` = (`b` + 1) WHERE (`a` = 1) |
    |     0 |    0 | 838 | diamond | test_000000 | Query   |    0 | starting                | show full processlist                                                                                                                                                                        |
    +-------+------+-----+---------+-------------+---------+------+-------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

热点更新性能测试

测试准备

  • 测试表

    CREATE TABLE sbtest(id INT UNSIGNED NOT NULL PRIMARY KEY, c BIGINT UNSIGNED NOT NULL);
  • 测试SQL

    UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c=c+1 WHERE id = 1;
  • 测试工具安装以及使用,请参见Sysbench测试

  • PolarDB-X企业版实例计算节点和存储节点的规格均为4C8 GB*2。

测试结果

场景

1线程

4线程

8线程

16线程

32线程

64线程

128线程

256线程

512线程

热点更新

298

986

1872

3472

6315

10138

13714

15803

23262

普通更新

318

423

409

409

412

428

448

497

615

说明
  • 以上测试结果的测试样本均为双1模式。

  • 以上结果的单位为TPS,即每秒处理的交易数(Transaction per second)。

  • 热点更新的TPS与机器规格、并发请求数和更新语句有关,测试结果仅供参考。