多数のデータ操作言語(DML)ステートメントが同時に同一行を対象とする場合、InnoDB の行レベルロックにより、これらのステートメントは直列化されます。同時実行数が増加すると、スレッドがロックの後ろでキューに並び、スループットが低下します。ステートメントキューは、ハッシュアルゴリズムを用いて競合するステートメントを共有バケットにルーティングすることで、ロック獲得の競合を回避し、代わりにキュー内で待機させる仕組みです。これにより、ロックの取得および解放を繰り返すオーバーヘッドが低減されます。
単一行に対して 128 スレッドの同時 UPDATE ステートメントを実行するベンチマークテストにおいて、ステートメントキューを有効化した PolarDB は、標準的な MySQL と比較して約 4 倍のスループットを達成します。
ステートメントキューの適用範囲
ステートメントキューは、大量の DML ステートメントが同一の行を対象とするワークロードで最も効果的です。たとえば、リーダーボードのカウンター、在庫更新、または多数の同時書き込みが同一のプライマリキーに集中するチケット予約システムなどが該当します。
一方、ステートメントが異なる行を対象とするワークロードでは、ロック競合が少ないため、効果は限定的です。
hotspot 変数を ON に設定してホットローオプティマイゼーションを有効化している場合、ステートメントキューは自動的にバイパスされます。両機能を併用することはできませんので、どちらか一方のみをご利用ください。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
以下のマイナーエンジンバージョンのいずれかを実行中の PolarDB for MySQL クラスター:
PolarDB for MySQL 8.0:マイナーエンジンバージョン 8.0.1.1.10 以降
PolarDB for MySQL 5.7:マイナーエンジンバージョン 5.7.1.0.6 以降
PolarDB for MySQL 5.6:マイナーエンジンバージョン 20200601 以降
構文
PolarDB for MySQL 5.6
POLARDB_STATEMENT_CONCURRENT_QUEUE キーワードを、DML キーワード(SELECT、UPDATE、INSERT、または DELETE)の直後に配置します。このキーワードは、4 つの DML 文の種類すべてに適用されます。
値によるキューイング
明示的な整数または文字列の値に基づいて、ステートメントをバケットに割り当てます。同じ値を持つステートメントは同一のバケットに配置されます。
構文:
POLARDB_STATEMENT_CONCURRENT_QUEUE [int | string]例:
INSERT POLARDB_STATEMENT_CONCURRENT_QUEUE 1 INTO t VALUES(1, 1, 'xpchild');
UPDATE POLARDB_STATEMENT_CONCURRENT_QUEUE 1 t SET c=c+1 WHERE id = 1;
UPDATE POLARDB_STATEMENT_CONCURRENT_QUEUE "xpchild" t SET col1 = col1+1 WHERE id = 1;WHERE 句のフィールドによるキューイング
WHERE 句に含まれる列の実行時値に基づいて、ステートメントをバケットに割り当てます。同一のフィールド値を持つステートメントは同一のバケットに配置されます。
構文:
POLARDB_STATEMENT_CONCURRENT_QUEUE [field]例:
SELECT POLARDB_STATEMENT_CONCURRENT_QUEUE id * FROM t WHERE 3 = id;
UPDATE POLARDB_STATEMENT_CONCURRENT_QUEUE id t SET c=c+1 WHERE id = 1 AND name = 'xpchild';WHERE 句では、元の列に対する二項演算のみをサポートします。各二項演算の右辺には、リテラルの数値または文字列を指定する必要があります。元の列に対する関数呼び出しや計算式はサポートされていません。PolarDB for MySQL 5.7 および 8.0
ヒント構文 /*+ ... */ を DML キーワード(SELECT、UPDATE、INSERT、または DELETE)の直後に配置します。このヒントは、4 つの DML 文の種類すべてに適用されます。
値によるキューイング
構文:
/*+ CCL_QUEUE_VALUE([INT | STRING]) */例:
UPDATE /*+ ccl_queue_value(1) */ t SET c=c+1 WHERE id = 1;
UPDATE /*+ ccl_queue_value('xpchild') */ t SET c=c+1 WHERE name = 'xpchild';WHERE 句のフィールドによるキューイング
構文:
/*+ CCL_QUEUE_FIELD(STRING) */例:
UPDATE /*+ ccl_queue_field("id") */ t SET c=c+1 WHERE id = 1 AND name = 'xpchild';WHERE 句では、元の列に対する二項演算のみをサポートします。各二項演算の右辺には、リテラルの数値または文字列を指定する必要があります。元の列に対する関数呼び出しや計算式はサポートされていません。キューのサイズの設定
キューの容量を制御する変数が 2 つあります。ご使用の同時実行数に応じて、適切な値を設定してください。
| 変数 | 説明 | 有効な値 | デフォルト値 |
|---|---|---|---|
ccl_queue_bucket_count | バケット数 | 1~64 | 4 |
ccl_queue_bucket_size | バケットあたりの最大同時実行ステートメント数 | 1~4096 | 64 |
キューの状態の監視
以下のストアドプロシージャを使用して、キューの状態を確認およびリセットできます。
現在の状態の確認
CALL dbms_ccl.show_ccl_queue();出力例:
+------+-------+-------------------+---------+---------+----------+
| ID | TYPE | CONCURRENCY_COUNT | MATCHED | RUNNING | WAITTING |
+------+-------+-------------------+---------+---------+----------+
| 1 | QUEUE | 64 | 1 | 0 | 0 |
| 2 | QUEUE | 64 | 40744 | 65 | 6 |
| 3 | QUEUE | 64 | 0 | 0 | 0 |
| 4 | QUEUE | 64 | 0 | 0 | 0 |
+------+-------+-------------------+---------+---------+----------+出力フィールドの説明:
| フィールド | 説明 |
|---|---|
CONCURRENCY_COUNT | このバケットで許可される最大同時実行ステートメント数 |
MATCHED | このバケットのルールに一致したステートメントの総数 |
RUNNING | このバケットで現在実行中のステートメント |
WAITTING | 現在キューで待機中のステートメント |
キューのデータのリセット
dbms_ccl.flush_ccl_queue() は、メモリ上のキュー統計情報をクリアし、現在の状態を返します。
CALL dbms_ccl.flush_ccl_queue();
CALL dbms_ccl.show_ccl_queue();フラッシュ後の出力例:
+------+-------+-------------------+---------+---------+----------+
| ID | TYPE | CONCURRENCY_COUNT | MATCHED | RUNNING | WAITTING |
+------+-------+-------------------+---------+---------+----------+
| 1 | QUEUE | 64 | 0 | 0 | 0 |
| 2 | QUEUE | 64 | 0 | 0 | 0 |
| 3 | QUEUE | 64 | 0 | 0 | 0 |
| 4 | QUEUE | 64 | 0 | 0 | 0 |
+------+-------+-------------------+---------+---------+----------+アプリケーションコードの変更なしでキューのヒントを適用する
PolarDB for MySQL 5.7 および 8.0 では、ステートメントキューとステートメントアウトラインを組み合わせることで、サーバー側から既存の SQL ステートメントにキューのヒントを注入できます。アプリケーションコードの変更は不要です。
PolarDB for MySQL 5.6 では構文が異なるため、この統合機能は利用できません。
以下の例では、Sysbench の update_non_index.lua スクリプトを用いて、このワークフローを実証します。
テストスキーマ:
CREATE TABLE `sbtest1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 MAX_ROWS=1000000;対象ステートメント:
UPDATE sbtest1 SET c='xpchild' WHERE id=0;手順:
オンラインでキューのヒントを注入するステートメントアウトラインを作成します。
CALL DBMS_OUTLN.add_optimizer_outline('test', '', 1, ' /*+ ccl_queue_field("id") */ ', "UPDATE sbtest1 SET c='xpchild' WHERE id=0");アウトラインが正常に作成されたことを確認します。
CALL dbms_outln.show_outline();期待される出力:
+------+--------+------------------------------------------------------------------+-----------+-------+------+--------------------------------+------+----------+---------------------------------------------+ | ID | SCHEMA | DIGEST | TYPE | SCOPE | POS | HINT | HIT | OVERFLOW | DIGEST_TEXT | +------+--------+------------------------------------------------------------------+-----------+-------+------+--------------------------------+------+----------+---------------------------------------------+ | 1 | test | 7b945614749e541e0600753367884acff5df7e7ee2f5fb0af5ea58897910f023 | OPTIMIZER | | 1 | /*+ ccl_queue_field("id") */ | 0 | 0 | UPDATE `sbtest1` SET `c` = ? WHERE `id` = ? | +------+--------+------------------------------------------------------------------+-----------+-------+------+--------------------------------+------+----------+---------------------------------------------+EXPLAINを実行して、ヒントが有効になっていることを確認します。EXPLAIN UPDATE sbtest1 SET c='xpchild' WHERE id=0; SHOW WARNINGS;SHOW WARNINGSの出力には、注入されたヒントが表示されるはずです。+-------+------+-----------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+-----------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | update /*+ CCL_QUEUE_FIELD('id') */ `test`.`sbtest1` set `test`.`sbtest1`.`c` = 'xpchild' where (`test`.`sbtest1`.`id` = 0) | +-------+------+-----------------------------------------------------------------------------------------------------------------------------+テスト開始前に、キューに活動がないことを確認します。
CALL dbms_ccl.show_ccl_queue();Sysbench テストを実行します。
sysbench \ --mysql-host={$ip} \ --mysql-port={$port} \ --mysql-db=test \ --test=./sysbench/share/sysbench/update_non_index.lua \ --oltp-tables-count=1 \ --oltp_table_size=1 \ --num-threads=128 \ --mysql-user=u0テスト中にキューの状態を確認し、ステートメントが正しくルーティングされていることを検証します。
CALL dbms_ccl.show_ccl_queue();テスト中の期待される出力:
+------+-------+-------------------+---------+---------+----------+ | ID | TYPE | CONCURRENCY_COUNT | MATCHED | RUNNING | WAITTING | +------+-------+-------------------+---------+---------+----------+ | 1 | QUEUE | 64 | 10996 | 63 | 4 | | 2 | QUEUE | 64 | 0 | 0 | 0 | | 3 | QUEUE | 64 | 0 | 0 | 0 | | 4 | QUEUE | 64 | 0 | 0 | 0 | +------+-------+-------------------+---------+---------+----------+アウトラインのヒット数とも照合します。
CALL dbms_outln.show_outline();本テスト実行では、115,795 件のステートメントがアウトラインルールに一致し、そのうち 10,996 件がキューに到達、63 件が同時実行中、4 件が待機中でした。
次のステップ
ステートメントアウトライン — アプリケーションコードを変更せずにオプティマイザヒントを注入する