熱點行是指在資料庫中那些會被頻繁執行修改操作的資料行。高並發情境下對熱點行的更新會造成嚴重的行鎖競爭與等待,影響系統效能。因此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.6與MySQL 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語句將不會使用熱點行效能最佳化。
使用說明
開啟熱點行效能最佳化功能。
您可以在PolarDB控制台上修改以下參數,以開啟或關閉熱點行效能最佳化功能。
參數
說明
hotspot
熱點行效能最佳化功能總開關。取值範圍如下:
ON:開啟。
OFF(預設):關閉。
說明叢集參數在PolarDB控制台上都已加上MySQL設定檔的相容性首碼loose_。如果您需要在PolarDB控制台修改hotspot參數,請選擇帶loose_首碼的參數(即loose_hotspot)進行修改。
使用Hint文法來使用熱點行效能最佳化功能。
Hint
是否必選
說明
必選
更新成功時提交。
可選
更新失敗時復原。
可選
顯式指定該請求只會更新一行,若不符合則更新失敗。
說明由於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 |
|
hotspot_update_max_wait_time | 行資料分組批次更新(Group Update)過程中Leader等待Follower加入該分組的等待時間。
|
hotspot_lock_type | 行資料分組批次更新(Group Update)過程中是否使用新類型的行鎖。取值範圍如下:
說明
|
查看參數配置
您可以使用如下命令查看熱點行效能最佳化功能的參數配置。
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)

並發數 | 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

並發數 | 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分位延遲)

並發數 | 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

並發數 | 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分位延遲)

並發數 | 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 |
附錄:效能測試步驟
準備一台ECS執行個體,並安裝Sysbench。
將以下
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執行Sysbench測試。
準備資料。
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運行測試。
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
並發線程數
顯示當前配置的線程數量。
線程狀態
支援即時查看線程運行狀態。