ホット行とは、頻繁に変更されるデータベースの行のことです。高い同時実行性が求められるシナリオでは、ホット行の更新によって深刻な行ロック競合と長い待機時間が発生し、システムパフォーマンスが低下します。PolarDB はこの問題に対処するため、データベースカーネル層で最適化を実装し、システムパフォーマンスを大幅に向上させます。
背景情報
ホット行は、以下のような課題をもたらします。
トランザクションがデータ行を更新する際、対象の行にロックを取得し、トランザクションがコミットまたはロールバックされるまでそのロックを解放しません。この間、1 つのトランザクションしか行を更新できず、他のトランザクションは待機する必要があります。これは、単一のホット行に対する更新リクエストが直列に実行されることを意味します。従来のデータベースやテーブルシャーディング戦略では、このシナリオにおけるパフォーマンス向上は限定的です。
このようなシナリオでは、ホット行に対する大量の更新リクエストが短時間でバックエンドデータベースシステムに到達する可能性があります。これにより、深刻な行ロック競合と長い待機時間が発生し、システムパフォーマンスが低下します。更新リクエストの待機時間が長くなると、ビジネス運用に大きな影響を与える可能性があります。
ハードウェアをスケールアップするだけでは、このような低レイテンシーの要件を満たすことはできません。この問題に対処するため、PolarDB はデータベースカーネル層で革新的な最適化を提供します。システムはホット行の更新リクエストを自動的に識別し、特定の時間間隔内に発生した同じデータ行への更新をグループ化します。その後、異なるグループがパイプラインを使用して並列処理されます。これらの最適化により、システムパフォーマンスが大幅に向上します。
技術的ソリューション
シーケンシャル処理からパイプライン処理へ
並列処理はデータベースのパフォーマンスを向上させる最も直接的な方法ですが、同じホット行に対する更新操作を完全に並列化することは困難です。PolarDB は、革新的なパイプライン処理アプローチを使用して、ホット行の更新操作の並列性を最大限に高めます。
ホット行の更新操作に使用される SQL ステートメントは、
autocommitまたは COMMIT_ON_SUCCESS でマークされます。最適化された MySQL カーネル層は、これらのマークが付いた更新操作を自動的に識別します。一定の時間間隔内で、収集された更新操作に対してプライマリキーまたは一意キーに基づいてHashを実行します。Hashによって同じバケットにマッピングされた更新操作については、カーネルは到着順に従ってそれらをグループ化し、バッチでコミットします。これらの更新操作をパイプラインで処理するために、2 つの実行ユニットがグループ化に使用されます。最初のグループが収集されコミットの準備ができた時点で、2 番目のグループはすぐに更新操作の収集を開始します。2 番目のグループが収集されコミットの準備ができたときには、最初のグループはすでにコミットされており、新しいバッチの更新操作の収集を開始します。2 つのグループは交互に並列実行されます。
このパイプライン処理モデルは、ハードウェアリソースを最大限に活用し、CPU 使用率を高め、システムの並列処理能力を向上させることで、スループットを最大化します。
行ロック要求時の待機を排除
論理的なデータ整合性を確保するため、更新はまず対象のデータ行のロックを取得します。ロック要求がすぐに許可されない場合、リクエストは待機状態になります。これは処理レイテンシーを増加させるだけでなく、デッドロック検出をトリガーし、余分なオーバーヘッドを追加します。
前述のように、同じデータ行に対する更新操作は時系列でグループ化されます。グループ内の最初の更新操作がリーダーです。リーダーは対象のデータ行を読み取り、ロックします。後続の更新操作はフォロワーです。フォロワーが対象データ行のロックを要求し、リーダーがすでに行ロックを保持していることを見つけると、待機することなくすぐにロックを取得できます。
この最適化により、行ロックの取得回数とそれに関連する時間的オーバーヘッドが削減されます。結果として、システム全体のパフォーマンスが大幅に向上します。
B-tree インデックスの走査を削減
MySQL は B-tree インデックスを使用してデータを管理します。各クエリは、対象のデータ行を見つけるためにインデックスを走査する必要があります。データテーブルが大きく、インデックスが深いほど、走査にかかる時間は長くなります。
前述のグループ化メカニズムでは、各グループのリーダーのみがインデックスを走査してデータ行を特定し、更新された行をメモリ (行キャッシュ) にキャッシュします。同じグループ内のフォロワーは、ロックを正常に取得した後、インデックスを再度走査することなく、メモリから直接対象のデータ行を読み取ります。
これにより、インデックスの総走査回数とそれに関連するオーバーヘッドが削減されます。
前提条件
ご利用の 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 以降。
binlog が有効になっていること。
クラスターパラメーター
rds_ic_reduce_hint_enableが無効になっている必要があります。PolarDB for MySQL 5.6 および PolarDB for MySQL 8.0 では、このパラメーターはデフォルトで無効になっています。
PolarDB for MySQL 5.7 では、このパラメーターはデフォルトで有効になっています。ホット行の最適化を有効にする前に、パラメーターの値を OFF に変更する必要があります。
説明MySQL の設定ファイルとの互換性を確保するため、PolarDBコンソールでは、すべてのクラスターパラメーターにプレフィックス loose_ が付きます。PolarDBコンソールで
rds_ic_reduce_hint_enableパラメーターを変更するには、loose_ プレフィックスが付いたパラメーター、つまり loose_rds_ic_reduce_hint_enable を選択する必要があります。
制限事項
ホット行の最適化は、以下のシナリオでは適用されません。
ホット行を含むテーブルがパーティションテーブルである場合。
ホット行を含むテーブルにトリガーが定義されている場合。
ホット行がステートメントキューメカニズムを使用している場合。
グローバル binlog が有効で、セッションレベルの binlog が無効な場合、ホット行の最適化は
UPDATE文には適用されません。ホット行の最適化を有効にすると、
ON UPDATE CURRENT_TIMESTAMP属性のみに依存して自動更新されるカラムは、自動更新されなくなります。UPDATE文でSET column_name = CURRENT_TIMESTAMP(n)を使用して、これらのカラムに明示的に値を割り当てる必要があります。
使用方法
ホット行の最適化を有効にします。
PolarDBコンソールで、以下のパラメーターを変更して、ホット行の最適化を有効または無効にできます。
パラメーター
説明
hotspot
ホット行の最適化機能を有効にするかどうかを指定します。有効な値:
ON:有効。
OFF (デフォルト):無効。
説明MySQL の設定ファイルとの互換性を確保するため、PolarDBコンソールでは、すべてのクラスターパラメーターにプレフィックス loose_ が付きます。PolarDBコンソールで hotspot パラメーターを変更するには、loose_ プレフィックスが付いたパラメーター、つまり loose_hotspot を選択します。
ヒント構文を使用してホット行の最適化を適用します。
ヒント
必須
説明
必須
更新が成功した場合にトランザクションをコミットします。
オプション
更新が失敗した場合にトランザクションをロールバックします。
オプション
リクエストが 1 行のみを更新することを期待していることを指定します。この条件が満たされない場合、更新は失敗します。
説明このヒントはトランザクションを自動的にコミットするため、トランザクションの最後の SQL ステートメントに含める必要があります。
例:
sbtestテーブルのcカラムの値を更新します。UPDATE /*+ COMMIT_ON_SUCCESS ROLLBACK_ON_FAIL TARGET_AFFECT_ROW(1) */ sbtest SET c = c + 1 WHERE id = 1;
関連操作
カスタムパラメーター
PolarDBコンソールでは、以下のパラメーターを変更できません。変更するには、クォータセンターに移動し、クォータ ID polardb_mysql_hotspot を見つけて、操作列の 申請をクリックします。
パラメーター | 説明 |
hotspot_for_autocommit |
|
hotspot_update_max_wait_time | グループ更新中にリーダーがフォロワーの参加を待つ最大時間。
|
hotspot_lock_type | グループ更新のために新しい行ロックタイプを有効にするかどうかを指定します。有効な値:
説明
|
パラメーター設定
次のコマンドを実行して、ホット行の最適化に関するパラメーター設定を表示します。
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、メモリ、I/O、スレッドなどのコンポーネントのシステム性能テストに使用されます。マルチスレッドテストをサポートし、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 for MySQL 5.6
テストシナリオ
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 for MySQL 5.7
テストシナリオ
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 |
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 for MySQL 8.0
テストシナリオ
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 |
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 -- 在庫ホットスポットのパフォーマンスをテストするためのものです sysbench.cmdline.options= { inventory_hotspot = {"ali 在庫ホットスポットを有効にする", 'off'}, tables = {"テーブル数", 1}, table_size = {"テーブルサイズ", 1}, oltp_skip_trx = {'trx をスキップ', true}, hotspot_rows = {'ホットスポット行数', 1} } function cleanup() drv = sysbench.sql.driver() con = drv:connect() for i = 1, sysbench.opt.tables do print(string.format("テーブル 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("テーブル 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() endSysbench テストを実行します。
データを準備します。
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
同時実行スレッド数
設定されたスレッド数。
スレッドの状態
実行中のスレッドのリアルタイムの状態。