ホット行とは、多数の同時実行トランザクションによって頻繁に更新されるデータベース行です。各更新処理は、トランザクションがコミットまたはロールバックされるまで当該行をロックするため、リクエストは逐次的にキューイングされます。テーブルシャーディングやハードウェアのスペックアップでは、この問題に対してほとんど効果が得られません。タイムセールや在庫差引などの高い同時実行性を要するワークロードでは、このようなロック競合によりレイテンシが急増し、スループットが頭打ちになります。
PolarDB は、カーネルレベルでこの課題に対処します。同一行に対する同時更新を自動的にバッチ化し、パイプライン方式で処理することで、冗長なロック待ちおよび B ツリー走査を排除します。これにより、高い同時実行性下で最大 50 倍のスループット向上が実現可能です。
ホット行の最適化を利用するタイミング
以下のすべての条件が満たされる場合に、ホット行の最適化は最も効果的です:
複数の同時実行トランザクションが 同一行 を繰り返し更新する — 例:タイムセール中の在庫差引
ワークロードが
autocommitモードを使用している、またはCOMMIT_ON_SUCCESSヒントを適用できるように調整可能であるデータベースが、パイプライン並列処理の恩恵を受けるマルチコア CPU 上で実行されている
以下のシナリオでは、本機能は 適用されません:
ホット行を含むテーブルが パーティションテーブル である
テーブルに トリガー が定義されている
ホット行に対して ステートメントキュー が使用されている
グローバルバイナリロギングが有効だが、セッションレベルのバイナリロギングが無効 である — この構成では、
UPDATEステートメントがホット行の最適化をバイパスします
仕組み
PolarDB は、UPDATE ステートメントが autocommit または COMMIT_ON_SUCCESS ヒントでマークされているかを識別します。短いタイムウィンドウ内に一致する更新を収集し、プライマリキーまたは一意キーに基づいてハッシュ値を計算してバケットに分類します。同一バケットにハッシュされた更新は、グループ更新 (Group Update) を形成します。
パイプライン処理 — 逐次処理から並列処理へ
2 つの実行ユニットが交互に動作します。ユニット A がグループコミットを行っている間、ユニット B は次の更新バッチを収集します。これによりコミット間のアイドル時間が解消され、マルチコア CPU の能力を最大限に活用できます。
例:100 µs のタイムウィンドウ内で、inventory WHERE id = 1 に対する 200 件の同時更新が到着した場合、従来の 200 回の「ロック取得 → 更新 → コミット」の逐次サイクルではなく、PolarDB はこれらを 100 件ずつの 2 グループにバッチ化し、パイプライン処理します。結果として、バッチ全体のレイテンシは O(n) から O(1) に低減します。
フォロワーによる即時ロック取得
各グループ内で、最初の更新操作が リーダー (Leader) です — リーダーは対象行を読み取り、行ロックを取得します。その後のグループ内のすべての操作は フォロワー (Follower) です。フォロワーがロックを要求し、リーダーが既にそのロックを保持していることを検出すると、待機することなく即座にロックを取得できます。これにより、グループ全体のロック待ちオーバーヘッドおよびデッドロック検出が不要になります。
行キャッシュによる B ツリー走査の削減
B ツリーインデックスを走査して対象行を特定するのはリーダーのみです。取得した行はメモリ上の 行キャッシュ (Row Cache) に格納されます。同一グループ内のフォロワーは、行キャッシュから直接行を取得し、インデックス走査を完全にスキップします。
前提条件
ホット行の最適化を有効化する前に、以下の条件を満たしていることを確認してください。
ご利用の PolarDB クラスターが、以下のいずれかのバージョンを実行していること:
PolarDB for MySQL 5.6(マイナーエンジンバージョン 20200601 以降)
PolarDB for MySQL 5.7(マイナーエンジンバージョン 5.7.1.0.17 以降)
PolarDB for MySQL 8.0(マイナーエンジンバージョン 8.0.1.1.10 以降)
バイナリロギング がクラスター上で有効化されていること
クラスターパラメーター rds_ic_reduce_hint_enable が OFF に設定されていること:
PolarDB for MySQL 5.6 および 8.0:デフォルトで OFF — 特に操作は不要です
PolarDB for MySQL 5.7:デフォルトで ON — 続行前にパラメーター値を変更し、OFF に設定してください
「PolarDB コンソール」で表示されるすべてのクラスターパラメーターは、MySQL 構成との互換性のため、loose_ プレフィックスが付与されています。rds_ic_reduce_hint_enable を変更するには、コンソールで loose_rds_ic_reduce_hint_enable を選択してください。
ホット行の最適化を有効化する
ステップ 1:機能パラメーターを有効化する
PolarDB コンソールで、クラスターパラメーターを設定し、hotspot(コンソール上での名称:loose_hotspot)を構成します。
| パラメーター | 説明 |
|---|---|
| hotspot | ホット行の最適化を有効化または無効化します。有効値: ON(有効)、OFF(無効、デフォルト) |
hotspot をコンソールで変更するには、loose_ プレフィックス付きのパラメーター loose_hotspot を選択してください。
ステップ 2:UPDATE ステートメントにヒント構文を追加する
ホット行を対象とする UPDATE ステートメントに、以下のヒントを追加します。ヒントはトランザクションの 最後の SQL ステートメント に配置する必要があります。なぜなら、COMMIT_ON_SUCCESS は成功時に自動的にコミットを行うためです。
| ヒント | 必須 | 説明 |
|---|---|---|
| COMMIT_ON_SUCCESS | 必須 | 更新が成功した場合、トランザクションをコミットします |
| ROLLBACK_ON_FAIL | 任意 | 更新が失敗した場合、トランザクションをロールバックします |
| TARGET_AFFECT_ROW(1) | 任意 | 更新対象行が 1 行を超える場合、更新を失敗させます |
例 — 特定行のカウンター列をインクリメントする:
UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;ステップ 3:機能が有効化されているかを確認する
ホットロウ最適化がグループ更新を処理していることを確認するには、次のクエリを実行します。
SHOW GLOBAL STATUS LIKE '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 |
+------------------------------+-------+以下のパラメーターは、PolarDB コンソールでは変更できません。変更するには、「クォータセンター」にアクセスし、「PolarDB ホット行パラメーター調整」のクォータを検索して、アクション 列の 申請 をクリックしてください。
| パラメーター | 説明 | デフォルト値 |
|---|---|---|
| hotspot_for_autocommit | UPDATE ステートメントが autocommit モードで実行される場合に、ホット行の最適化を適用します。有効値:ON/OFF | OFF |
| hotspot_update_max_wait_time | リーダーがフォロワーのグループ参加を待機する最大時間。単位:マイクロ秒 (µs) | 100 µs |
| hotspot_lock_type | グループ更新中に新しい行ロックタイプを有効化します。ON の場合、同一ホット行のロックを要求するフォロワーは、待機せずに即座にロックを取得できます。有効値:ON/OFF | OFF |
パフォーマンステスト
テスト環境
ツール: Sysbench — Lua ベースのテストスクリプトをサポートするオープンソースのマルチスレッドベンチマークツール
テストシナリオ: 1 行のホット行、8 コア CPU
テーブル定義:
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 for MySQL 5.6 | QPS が約 50 倍向上 |
| PolarDB for MySQL 5.7 | QPS が約 35 倍向上 |
| PolarDB for MySQL 8.0 | QPS が約 26 倍向上 |
PolarDB for MySQL 5.6
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 for MySQL 5.7
QPS
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 パーセンタイル・レイテンシ)
95 パーセンタイル・レイテンシ(ms)

| 同時実行数 | 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 for MySQL 8.0
QPS
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 パーセンタイル・レイテンシ)
95 パーセンタイル・レイテンシ(ms)

| 同時実行数 | 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 をインストールします。
Sysbench のソースコードの
src/luaフォルダに、oltp_inventory.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 --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_read_only run
入力パラメーター
| パラメーター | 説明 |
|---|---|
mysql-host | クラスターエンドポイント |
mysql-port | クラスターエンドポイントのポート |
mysql-user | クラスターのユーザー名 |
mysql-password | クラスターユーザーのパスワード |
mysql-db | データベース名 |
出力パラメーター
| パラメーター | 表示内容 | 説明 |
|---|---|---|
tables | データテーブル数 | テストで使用されるテーブルの総数 |
table_size | テーブルあたりの行数 | 各テーブル内のレコード数 |
Data size | テーブルデータサイズ(MB または GB) | テーブル内のデータのサイズ |
threads | 同時実行スレッド数 | 設定されたスレッド数 |
Thread status | リアルタイムスレッドステータス | テスト実行中のスレッドの実行状況 |