すべてのプロダクト
Search
ドキュメントセンター

PolarDB:ホット行の最適化

最終更新日:Mar 29, 2026

ホット行とは、多数の同時実行トランザクションによって頻繁に更新されるデータベース行です。各更新処理は、トランザクションがコミットまたはロールバックされるまで当該行をロックするため、リクエストは逐次的にキューイングされます。テーブルシャーディングやハードウェアのスペックアップでは、この問題に対してほとんど効果が得られません。タイムセールや在庫差引などの高い同時実行性を要するワークロードでは、このようなロック競合によりレイテンシが急増し、スループットが頭打ちになります。

PolarDB は、カーネルレベルでこの課題に対処します。同一行に対する同時更新を自動的にバッチ化し、パイプライン方式で処理することで、冗長なロック待ちおよび B ツリー走査を排除します。これにより、高い同時実行性下で最大 50 倍のスループット向上が実現可能です。

ホット行の最適化を利用するタイミング

以下のすべての条件が満たされる場合に、ホット行の最適化は最も効果的です:

  • 複数の同時実行トランザクションが 同一行 を繰り返し更新する — 例:タイムセール中の在庫差引

  • ワークロードが autocommit モードを使用している、または COMMIT_ON_SUCCESS ヒントを適用できるように調整可能である

  • データベースが、パイプライン並列処理の恩恵を受けるマルチコア CPU 上で実行されている

以下のシナリオでは、本機能は 適用されません

  • ホット行を含むテーブルが パーティションテーブル である

  • テーブルに トリガー が定義されている

  • ホット行に対して ステートメントキュー が使用されている

  • グローバルバイナリロギングが有効だが、セッションレベルのバイナリロギングが無効 である — この構成では、UPDATE ステートメントがホット行の最適化をバイパスします

仕組み

PolarDB は、UPDATE ステートメントが autocommit または COMMIT_ON_SUCCESS ヒントでマークされているかを識別します。短いタイムウィンドウ内に一致する更新を収集し、プライマリキーまたは一意キーに基づいてハッシュ値を計算してバケットに分類します。同一バケットにハッシュされた更新は、グループ更新 (Group Update) を形成します。

image

パイプライン処理 — 逐次処理から並列処理へ

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_enableOFF に設定されていること:

    • 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_autocommitUPDATE ステートメントが autocommit モードで実行される場合に、ホット行の最適化を適用します。有効値:ONOFFOFF
hotspot_update_max_wait_timeリーダーがフォロワーのグループ参加を待機する最大時間。単位:マイクロ秒 (µs)100 µs
hotspot_lock_typeグループ更新中に新しい行ロックタイプを有効化します。ON の場合、同一ホット行のロックを要求するフォロワーは、待機せずに即座にロックを取得できます。有効値:ONOFFOFF

パフォーマンステスト

テスト環境

  • ツール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.6QPS が約 50 倍向上
PolarDB for MySQL 5.7QPS が約 35 倍向上
PolarDB for MySQL 8.0QPS が約 26 倍向上

PolarDB for MySQL 5.6

QPS

image.png
同時実行性181632641282565121024
最適化無効1365.311863.941866.61862.641867.321832.511838.311819.521833.2
最適化有効1114.797000.1912717.3222029.4843096.0661349.783098.6990860.9487689

PolarDB for MySQL 5.7

QPS

QPS

image.png
同時実行数181632641282565121024
最適化無効1348.491892.291889.771895.861875.21850.261843.621849.921835.68
最適化有効1104.96886.8912485.1716003.2316460.3116548.8627920.8947893.9666500.92

lat95th(95 パーセンタイル・レイテンシ)

95 パーセンタイル・レイテンシ(ms)

image
同時実行数181632641282565121024
最適化無効0.95.479.9118.9536.8973.13164.45297.92590.56
最適化有効1.081.441.583.255.289.5612.0813.2218.28

PolarDB for MySQL 8.0

QPS

QPS

image
同時実行性181632641282565121024
最適化無効1559.142103.822116.42082.12079.742031.641993.091977.61983.61
最適化有効1237.287443.0412244.1915529.5223041.1533931.1853924.2454598.650988.22

lat95th(95 パーセンタイル・レイテンシ)

95 パーセンタイル・レイテンシ(ms)

image
同時実行数181632641282565121024
最適化無効0.858.917.3233.1266.84153.02287.38549.52
最適化有効0.971.341.893.194.825.887.1713.4628.16

付録:パフォーマンステストの実行手順

  1. ECS インスタンスを準備し、Sysbench をインストールします。

  2. 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
  3. クラスターに接続します。

  4. テストデータを準備します。

    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
  5. テストを実行します。

    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リアルタイムスレッドステータステスト実行中のスレッドの実行状況