全部產品
Search
文件中心

PolarDB:熱點行效能最佳化

更新時間:Dec 11, 2025

熱點行是指在資料庫中那些會被頻繁執行修改操作的資料行。高並發情境下對熱點行的更新會造成嚴重的行鎖競爭與等待,影響系統效能。因此PolarDB針對此情境在資料庫核心層進行了創新性的最佳化,極大地提升了系統效能。

背景資訊

熱點行面臨以下問題:

  • 當一個事務對一行資料進行更新時,會對目標資料行加鎖,直到事務提交或復原時才釋放。在同一時段內,針對同一資料行,只有一個事務能夠進行更新,而其他事務則需要等待。由此可見,對於單一熱點行的更新要求實際上是串列執行的,傳統的分庫分表策略在效能提升方面並不會有太大協助。

  • 在電商平台業務中,限購、秒殺是常用的促銷手段。在這些情境下,大量對熱點行的更新要求在極短時間間隔內到達後台資料庫系統,必然造成嚴重的行鎖競爭和等待,影響系統效能。如果一個更新要求等待執行的時間變長,將會對業務層面產生顯著負面影響。

針對上述問題,單純提高電腦硬體設定已經無法滿足這樣的低延遲需求。因此PolarDB在資料庫核心層進行了創新性的最佳化,不但能夠自動識別熱點行更新要求,而且將一定時間間隔內對同一資料行的更新操作進行分組,不同分組採用流水線的方式平行處理,通過這些最佳化,極大地提升了系統的效能。

技術方案

  • 串列處理變流水線處理

    為了提升資料庫系統的效能,最直接的方法是使用平行處理,但是對同一熱點行的更新操作很難做到完全並行。PolarDB創新性地使用了流水線處理方式,最大限度地將熱點行更新操作並行化。

    熱點行更新操作所使用的SQL語句會用autocommit或者COMMIT_ON_SUCCESS進行標記。最佳化後的MySQL核心層會自動識別帶此類標記的更新操作,在一定的時間間隔內,將收集到的更新操作按照主鍵或者唯一鍵進行Hash,對於Hash到同一個桶中的更新操作,會將它們按照到達的先後順序分組分批地進行處理和提交。

    為了使用流水線方式處理這些更新操作,需要使用兩個執行單元對它們進行分組。當第一個分組收集完畢準備提交時,第二個分組立即開始收集更新操作。當第二個分組收集完畢準備提交時,第一個分組已經提交完畢並開始收集新一批的更新操作,兩個分組不斷切換,並存執行。

    現如今多核CPU的使用已經非常普遍。這樣的流水線式的處理方式能夠充分利用硬體資源,提升CPU的使用率和資料庫系統的平行處理能力,從而最大限度地提升資料庫系統的輸送量。

  • 消除申請行鎖時的等待

    為了保證資料的邏輯一致性,對一個資料行進行更新時,首先會對該資料行加鎖。如果加鎖請求無法立刻滿足,則進入等待狀態。這樣一來,不但增加了處理延遲,還會觸發死結檢測,導致額外的資源消耗。

    前面提到,我們會按照時間順序將對同一資料行的更新操作進行分組。組內第一個更新操作為Leader,其讀取目標資料行並且加鎖。後續更新操作為Follower,其對目標資料行加鎖時,如果發現Leader已經持有行鎖,無需等待,直接獲得行鎖。

    通過這個最佳化,能夠減少行鎖的加鎖次數和時間開銷,整個資料庫系統的效能顯著提升。

  • 減少B-tree索引的遍曆

    MySQL是以B-tree索引的方式管理資料的。每次執行查詢時,都需要遍曆索引才能定位到目標資料行,資料表越大,索引層級越多,遍曆時間就越長。

    在前面提到的對更新操作進行分組的機制中,只有每組的Leader遍曆索引定位元據行,之後把更新後的資料行緩衝(Row Cache)在記憶體中。同組的Follower加鎖成功後,直接從記憶體中讀取目標資料行,不需要再次遍曆索引。

    這樣一來,從整體上減少了索引遍曆的次數和時間開銷。

前提條件

  • 您的PolarDB叢集需為以下版本之一:

    • MySQL 5.6,且核心小版本需為20200601及以上版本。

    • MySQL 5.7,且核心小版本需為5.7.1.0.17及以上版本。

    • MySQL 8.0,且核心小版本需為8.0.1.1.10及以上版本。

  • 開啟Binlog

  • 叢集參數rds_ic_reduce_hint_enable處於關閉狀態。

    • MySQL 5.6MySQL 8.0:叢集參數預設處於關閉狀態。

    • MySQL 5.7:叢集預設處於開啟狀態,開啟熱點行最佳化功能前,需修改參數值為OFF(關閉)。

    說明

    叢集參數在PolarDB控制台上都已加上MySQL設定檔的相容性首碼loose_。如果您需要在PolarDB控制台修改rds_ic_reduce_hint_enable參數,請選擇帶loose_首碼的參數(即loose_rds_ic_reduce_hint_enable)進行修改。

使用限制

在以下情境中,熱點行效能最佳化將不會被使用:

  • 熱點行所屬的資料表是分區表。

  • 熱點行所屬的資料表上定義了觸發器(Trigger)。

  • 熱點行使用了Statement Queue機制。

  • 在全域Binlog開啟的情況下,若會話層級的Binlog關閉,執行UPDATE語句將不會使用熱點行效能最佳化。

使用說明

  1. 開啟熱點行效能最佳化功能。

    您可以在PolarDB控制台修改以下參數,以開啟或關閉熱點行效能最佳化功能。

    參數

    說明

    hotspot

    熱點行效能最佳化功能總開關。取值範圍如下:

    1. ON:開啟。

    2. OFF(預設):關閉。

    說明

    叢集參數在PolarDB控制台上都已加上MySQL設定檔的相容性首碼loose_。如果您需要在PolarDB控制台修改hotspot參數,請選擇帶loose_首碼的參數(即loose_hotspot)進行修改。

  2. 使用Hint文法來使用熱點行效能最佳化功能。

    Hint

    是否必選

    說明

    COMMIT_ON_SUCCESS

    必選

    更新成功時提交。

    ROLLBACK_ON_FAIL

    可選

    更新失敗時復原。

    TARGET_AFFECT_ROW(1)

    可選

    顯式指定該請求只會更新一行,若不符合則更新失敗。

    說明

    由於Hint文法生效會自動認可事務,因此Hint需要位於事務的最後一條SQL語句。

    樣本:更新sbtest表中c列的數值。

    UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;

相關操作

自訂參數配置

PolarDB控制台不支援對以下參數進行修改。如果您需要進行修改,請前往配額中心,在配額名稱為PolarDB熱點行參數調整操作列,單擊申請

參數

說明

hotspot_for_autocommit

autocommit模式下的UPDATE語句是否使用熱點行效能最佳化功能。取值範圍如下:

  • ON:開啟。

  • OFF(預設):關閉。

hotspot_update_max_wait_time

行資料分組批次更新(Group Update)過程中Leader等待Follower加入該分組的等待時間。

  • 單位:微秒(us)。

  • 預設值:100us。

hotspot_lock_type

行資料分組批次更新(Group Update)過程中是否使用新類型的行鎖。取值範圍如下:

  • ON:開啟。

  • OFF(預設):關閉。

說明
  • 在該參數開啟的情況下,對於相同熱點行的更新操作進行申請行鎖時,無需等待,直接獲得行鎖,從而可以提升效能。

  • 新類型的行鎖:指的是上述說明中無需等待,直接獲得的行鎖。

查看參數配置

您可以使用如下命令查看熱點行效能最佳化功能的參數配置。

SHOW variables LIKE "hotspot%";

返回結果樣本:

+------------------------------+-------+
|Variable_name                 | Value |
+------------------------------+-------+
|hotspot                       | OFF   |
|hotspot_for_autocommit        | OFF   |
|hotspot_lock_type             | OFF   |
|hotspot_update_max_wait_time  | 100   |
+------------------------------+-------+

查看使用方式

您可以使用如下命令查看熱點行效能最佳化功能的使用方式。

SHOW GLOBAL status LIKE 'Group_update%';

效能測試

測試載入器

Sysbench是一個開源的、跨平台的效能測試工具,主要用於資料庫基準測試(如MySQL)、 系統效能測試(如CPU、記憶體、IO、線程)。它支援多線程測試,並通過Lua指令碼靈活控制測試邏輯,適合資料庫效能評估、壓力測試等情境。

測試資料表與測試語句

  • 資料表定義

    CREATE TABLE sbtest (id INT UNSIGNED NOT NULL, c BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id));
  • 測試語句

    UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;

測試結果

PolarDB MySQL版5.6

測試情境

單個熱點行加8核CPU。

測試結果

在單熱點行加8核CPU的情境下,引入熱點行效能最佳化後,庫存熱點效能在高並發時提升近50倍。

測試資料(QPS)

image.png

並發數

1

8

16

32

64

128

256

512

1024

關閉熱點行最佳化

1365.31

1863.94

1866.6

1862.64

1867.32

1832.51

1838.31

1819.52

1833.2

開啟熱點行最佳化

1114.79

7000.19

12717.32

22029.48

43096.06

61349.7

83098.69

90860.94

87689

PolarDB MySQL版5.7

測試情境

單個熱點行加8核CPU。

測試結果

在單熱點行加8核CPU的情境下,引入熱點行效能最佳化後,庫存熱點效能在高並發時提升近35倍。

測試資料

QPS

image.png

並發數

1

8

16

32

64

128

256

512

1024

關閉熱點行最佳化

1348.49

1892.29

1889.77

1895.86

1875.2

1850.26

1843.62

1849.92

1835.68

開啟熱點行最佳化

1104.9

6886.89

12485.17

16003.23

16460.31

16548.86

27920.89

47893.96

66500.92

lat95th (95分位延遲)

image

並發數

1

8

16

32

64

128

256

512

1024

關閉熱點行最佳化

0.9

5.47

9.91

18.95

36.89

73.13

164.45

297.92

590.56

開啟熱點行最佳化

1.08

1.44

1.58

3.25

5.28

9.56

12.08

13.22

18.28

PolarDB MySQL版8.0

測試情境

單個熱點行加8核CPU。

測試結果

在單熱點行加8核CPU的情境下,引入熱點行效能最佳化後,庫存熱點效能在高並發時提升近26倍。

測試資料

QPS

image

並發數

1

8

16

32

64

128

256

512

1024

關閉熱點行最佳化

1559.14

2103.82

2116.4

2082.1

2079.74

2031.64

1993.09

1977.6

1983.61

開啟熱點行最佳化

1237.28

7443.04

12244.19

15529.52

23041.15

33931.18

53924.24

54598.6

50988.22

lat95th (95分位延遲)

image

並發數

1

8

16

32

64

128

256

512

1024

關閉熱點行最佳化

0.8

5

8.9

17.32

33.12

66.84

153.02

287.38

549.52

開啟熱點行最佳化

0.97

1.34

1.89

3.19

4.82

5.88

7.17

13.46

28.16

附錄:效能測試步驟

  1. 準備一台ECS執行個體,並安裝Sysbench

  2. 將以下oltp_inventory.lua放在Sysbench源碼的src/lua目錄下。

    #!/usr/bin/env sysbench
    -- it is to test inventory_hotspot performance
    
    sysbench.cmdline.options= {
        inventory_hotspot = {"enable ali inventory hotspot", 'off'},
        tables = {"table number", 1},
        table_size = {"table size", 1},
        oltp_skip_trx = {'skip trx', true},
        hotspot_rows = {'hotspot row number', 1}
    }
    
    
    function cleanup()
        drv = sysbench.sql.driver()
        con = drv:connect()
        for i = 1, sysbench.opt.tables do
            print(string.format("drop table sbtest%d ...", i))
            drop_table(drv, con, i)
        end
    end
    
    function drop_table(drv, con, table_id)
        local query
        query = string.format("drop table if exists sbtest%d ", table_id)
        con:query(query)
    end
    
    function create_table(drv, con, table_id)
        local query
        query = string.format("CREATE TABLE sbtest%d (id INT UNSIGNED NOT NULL, c BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id))", table_id)
        con:query(query)
        for i=1, sysbench.opt.table_size do
            con:query("INSERT INTO sbtest" .. table_id .. "(id, c) values (" ..i.. ", 1)")
        end
    end
    
    function prepare()
        drv = sysbench.sql.driver()
        con = drv:connect()
        for i = 1, sysbench.opt.tables do
            print(string.format("Creating table sbtest%d ...", i))
            create_table(drv, con, i)
        end
    end
    
    function thread_init()
        drv = sysbench.sql.driver()
        con = drv:connect()
        begin_query = 'BEGIN'
        commit_query = 'COMMIT'
    end
    
    function event()
        local table_name
        table_name = "sbtest" .. sysbench.rand.uniform(1, sysbench.opt.tables)
        local min_line = math.min(sysbench.opt.table_size, sysbench.opt.hotspot_rows)
        local row_id = sysbench.rand.uniform(1, min_line)
    
        if not sysbench.opt.oltp_skip_trx then
            con:query(begin_query)
        end
    
        if (sysbench.opt.inventory_hotspot == "on") then
            con:query("UPDATE COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW 1 " .. table_name .. " SET c=c+1 WHERE id =" .. row_id)
        else
            con:query("UPDATE " .. table_name .. " SET c=c+1 WHERE id = " .. row_id)
        end
    
        if not sysbench.opt.oltp_skip_trx then
            if (sysbench.opt.inventory_hotspot == "on") then
                con:query(commit_query)
            end
        end
    end
    
    function thread_done()
       con:disconnect()
    end
  3. 使用命令列串連叢集

  4. 執行Sysbench測試。

    1. 準備資料。

      sysbench --hotspot_rows=1 --histogram=on --mysql-user=<user> --inventory_hotspot=on --mysql-host=<host> --threads=1 --report-interval=1 --mysql-password=<password> --tables=1 --table-size=1 --oltp_skip_trx=true --db-driver=mysql --percentile=95 --time=300 --mysql-port=<port> --events=0 --mysql-db=<database> oltp_inventory prepare
    2. 運行測試。

      sysbench --db-driver=mysql --mysql-host=<host> --mysql-port=<port> --mysql-user=<user> --mysql-password=<password> --mysql-db=<database> --range-selects=0 --table_size=25000 --tables=250 --events=0 --time=600 --rand-type=uniform --threads=<threads> oltp_inventory run

    入參數說明

    參數類別

    說明

    mysql-host

    叢集串連地址。

    mysql-port

    叢集串連地址對應的連接埠。

    mysql-user

    叢集使用者名稱稱。

    mysql-password

    叢集使用者名稱稱對應的密碼。

    mysql-db

    資料庫名稱

    輸出參數說明

    參數類別

    展示內容

    說明

    tables

    資料表數量

    所有參與測試的資料表數量。

    table_size

    資料錶行數

    展示每個表的記錄數量。

    資料量大小

    以儲存空間(如MB/GB)為單位展示表的資料量。

    threads

    並發線程數

    顯示當前配置的線程數量。

    線程狀態

    支援即時查看線程運行狀態。