🚄
PostgreSQLで複数のSET LOCAL効率よく実行する
SET
コマンドは実行時設定パラメータを変更するためのコマンドです[1]。影響範囲をトランザクション内に限定するときはLOCAL
を指定し、何も指定しないかSESSION
を指定すると、影響範囲はセッション全体になります。
ただし、使用するクライアントライブラリやプロトコルの制約で複数のSQL文をまとめて一度に送信できない場合は、それぞれのSET
ごとにネットワーク上でのやり取りが発生し、ネットワークの往復時間(RTT: Round Trip Time)が増えてしまいます。例えば以下のように個別にクエリを実行すると、SET LOCAL
だけで3回のRTTが発生します。
tx, err := db.BeginTx(ctx, nil)
if err != nil {
// ...
}
// 個別にSET LOCALを実行
if _, err := tx.ExecContext(ctx, `SET LOCAL statement_timeout = '30s'`); err != nil {
// ...
}
if _, err := tx.ExecContext(ctx, `SET LOCAL lock_timeout = '5s'`); err != nil {
// ...
}
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`SET LOCAL app.tenant_id = '%s'`, validatedTenantID)); err != nil {
// ...
}
// ...
PostgreSQLでは、SET
コマンドと等価な機能をset_config
関数として提供しています[2]。この関数を使えば、1つのSELECTクエリ内で複数の設定をまとめて実行できます。第3引数の真偽値で影響範囲をトランザクション内かセッション内か指定します。
つまり、SET LOCAL
とSET SESSION
はそれぞれ以下のように書き換えられます。
-- SET LOCAL some_setting = 'value'; と等価
set_config('some_setting', 'value', true);
-- SET SESSION some_setting = 'value'; と等価
set_config('some_setting', 'value', false);
これを利用すると、先ほどの例は以下のように書けます。
tx, err := db.BeginTx(ctx, nil)
if err != nil {
// ...
}
// set_configを使い、1回のクエリ内で設定をまとめて実行
_, err := tx.ExecContext(ctx, fmt.Sprintf(
`SELECT
set_config('statement_timeout', '5s', true),
set_config('lock_timeout', '2s', true),
set_config('app.tenant_id', '%s', true)`,
validatedTenantID,
))
if err != nil {
// ...
}
Discussion