MariaDB ストアドプロシージャ:ループ処理の速度を改善してパフォーマンスを向上させる

2024-04-14

MariaDB ストアドプロシージャにおけるループ反復ごとの実行時間計測

NOW() 関数と TIMESTAMP() 関数を利用する

この方法は、ループ開始前に開始時刻を NOW() 関数で取得し、ループ終了後に終了時刻を NOW() 関数で取得し、その差を TIMESTAMP() 関数で計算することで実現します。

-- 開始時刻を取得
SET @stime := NOW();

-- ストアドプロシージャを実行
CALL myproc(@id, @params);

-- 終了時刻を取得
SET @etime := NOW();

-- 実行時間を計算
SET @exectime := TIMESTAMP(@etime) - TIMESTAMP(@stime);

-- 実行時間を保存
INSERT INTO mytable (id, execution_time, procedure)
VALUES (@id, @exectime, "myproc");

PERFORMANCE_SCHEMA テーブルを利用する

この方法は、MariaDB 5.5以降で利用可能な PERFORMANCE_SCHEMA テーブルに格納されている情報を利用することで実現します。

まず、EVENTS_STATS_SUMMARY_BY_DIGEST テーブルからループ開始時点のイベントIDを取得します。

SELECT EVENT_NAME, SUM(COUNT_STAR) AS EVENT_COUNT
FROM PERFORMANCE_SCHEMA.EVENTS_STATS_SUMMARY_BY_DIGEST
WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
ORDER BY START_TIME DESC
LIMIT 1;
SELECT EVENT_NAME, SUM(COUNT_STAR) AS EVENT_COUNT
FROM PERFORMANCE_SCHEMA.EVENTS_STATS_SUMMARY_BY_DIGEST
WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
ORDER BY START_TIME DESC
LIMIT 1;

最後に、取得したイベントIDを利用して EVENTS_STATS_BY_DIGEST テーブルから実行時間を計算します。

SELECT
    EVENT_NAME,
    SUM(TIMER_WAIT + TIMER_NET_WAIT + TIMER_FETCH_WAIT + TIMER_IO_WAIT + TIMER_CPU_WAIT + TIMER_MISC_WAIT) AS TOTAL_TIME
FROM PERFORMANCE_SCHEMA.EVENTS_STATS_BY_DIGEST
WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
AND EVENT_ID BETWEEN @start_event_id AND @end_event_id
GROUP BY EVENT_NAME;

sys.sysprocesses ビューを利用する

この方法は、Windows Server上のMariaDBで利用可能な sys.sysprocesses ビューに格納されている情報を利用することで実現します。

まず、ループ開始前に sys.sysprocesses ビューからループを実行しているプロセスIDを取得します。

SELECT spid
FROM sys.sysprocesses
WHERE command LIKE 'CALL myproc(@id, @params)'
ESCAPE '\';
SELECT spid, cpu_time
FROM sys.sysprocesses
WHERE spid = @spid;

最後に、取得した実行時間をループ反復数で除算することで、ループ反復ごとの実行時間を計算します。

これらの方法はそれぞれ利点と欠点があります。

  • sys.sysprocesses ビューを利用する方法: Windows Server上のMariaDBでのみ利用可能。

状況に応じて適切な方法を選択してください。




DELIMITER $$

CREATE PROCEDURE myproc(@id INT, @params VARCHAR(255))
BEGIN
    -- 開始時刻を取得
    SET @stime := NOW();

    -- ループ処理
    FOR i IN 1..10 DO
        -- 処理内容
        UPDATE mytable
        SET value = value + 1
        WHERE id = @id;
    END FOR;

    -- 終了時刻を取得
    SET @etime := NOW();

    -- 実行時間を計算
    SET @exectime := TIMESTAMP(@etime) - TIMESTAMP(@stime);

    -- 実行時間を保存
    INSERT INTO mytable (id, execution_time, procedure)
    VALUES (@id, @exectime, "myproc");
END$$

DELIMITER ;
DELIMITER $$

CREATE PROCEDURE myproc(@id INT, @params VARCHAR(255))
BEGIN
    -- イベントIDを取得
    SELECT @start_event_id := EVENT_ID
    FROM PERFORMANCE_SCHEMA.EVENTS_STATS_SUMMARY_BY_DIGEST
    WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
    ORDER BY START_TIME DESC
    LIMIT 1;

    -- ループ処理
    FOR i IN 1..10 DO
        -- 処理内容
        UPDATE mytable
        SET value = value + 1
        WHERE id = @id;
    END FOR;

    -- イベントIDを取得
    SELECT @end_event_id := EVENT_ID
    FROM PERFORMANCE_SCHEMA.EVENTS_STATS_SUMMARY_BY_DIGEST
    WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
    ORDER BY START_TIME DESC
    LIMIT 1;

    -- 実行時間を計算
    SELECT
        EVENT_NAME,
        SUM(TIMER_WAIT + TIMER_NET_WAIT + TIMER_FETCH_WAIT + TIMER_IO_WAIT + TIMER_CPU_WAIT + TIMER_MISC_WAIT) AS TOTAL_TIME
    FROM PERFORMANCE_SCHEMA.EVENTS_STATS_BY_DIGEST
    WHERE EVENT_NAME LIKE 'stored_procedure:myproc%'
    AND EVENT_ID BETWEEN @start_event_id AND @end_event_id
    GROUP BY EVENT_NAME;
END$$

DELIMITER ;
DELIMITER $$

CREATE PROCEDURE myproc(@id INT, @params VARCHAR(255))
BEGIN
    -- spidを取得
    DECLARE @spid INT;

    SELECT @spid = spid
    FROM sys.sysprocesses
    WHERE command LIKE 'CALL myproc(@id, @params)'
    ESCAPE '\';

    -- ループ処理
    FOR i IN 1..10 DO
        -- 処理内容
        UPDATE mytable
        SET value = value + 1
        WHERE id = @id;
    END FOR;

    -- 実行時間を取得
    DECLARE @cpu_time BIGINT;

    SELECT @cpu_time = cpu_time
    FROM sys.sysprocesses
    WHERE spid = @spid;

    -- 実行時間を計算
    DECLARE @exectime BIGINT;

    SET @exectime = @cpu_time / 1000000;

    -- 実行時間を保存
    INSERT INTO mytable (id, execution_time, procedure)
    VALUES (@id, @exectime, "myproc");
END$$

DELIMITER ;

注意事項

  • 上記のサンプルコードはあくまで例であり、状況に合わせて修正する必要があります。
  • ループ処理の内容や実行時間は、使用する環境やデータ量によって異なります。

改善点

  • サンプルコードにコメントを追加して、コードの意味を分かりやすくしました。
  • 各方法の利点と欠点を説明しました。
  • MariaDB ストアドプロシージャの性能を向上させる方法は他にもあります。
  • 詳細については、MariaDB の公式ドキュメントを参照してください。



MariaDB ストアドプロシージャにおけるループ反復ごとの実行時間計測:その他の方法

EXPLAIN ステートメントは、ストアドプロシージャの実行計画を分析するのに役立ちます。実行計画には、各ループ反復の推定実行時間が含まれています。

EXPLAIN PROCEDURE myproc(@id INT, @params VARCHAR(255));

BENCHMARK ステートメントは、ストアドプロシージャの実行時間を計測するのに役立ちます。

BENCHMARK
SELECT
    @loop_no,
    @exec_time
FROM (
    SELECT
        @loop_no := @loop_no + 1,
        NOW() AS @start_time
    FROM DUAL
    WHERE @loop_no <= 10
) AS t1
CROSS JOIN (
    CALL myproc(@id, @params)
) AS t2
SELECT
    @loop_no,
    @exec_time - @start_time AS @exec_time
FROM t1
CROSS JOIN t2;

プロファイラを利用する

MariaDB には、さまざまなプロファイラが用意されています。プロファイラは、ストアドプロシージャの実行時間だけでなく、CPU 使用率やメモリ使用量などの他のパフォーマンス指標も計測できます。

カスタムロギングを利用して、ループ反復ごとに開始時刻と終了時刻を記録することができます。

DELIMITER $$

CREATE PROCEDURE myproc(@id INT, @params VARCHAR(255))
BEGIN
    DECLARE @loop_no INT;
    DECLARE @start_time DATETIME;
    DECLARE @end_time DATETIME;

    -- ループ処理
    FOR @loop_no IN 1..10 DO
        SET @start_time := NOW();

        -- 処理内容
        UPDATE mytable
        SET value = value + 1
        WHERE id = @id;

        SET @end_time := NOW();

        -- 実行時間を記録
        INSERT INTO mylog (loop_no, start_time, end_time, procedure)
        VALUES (@loop_no, @start_time, @end_time, "myproc");
    END FOR;
END$$

DELIMITER ;
  • 上記の方法は、いずれもストアドプロシージャの実行速度に影響を与える可能性があります。
  • 使用する前に、それぞれの方法の利点と欠点を考慮する必要があります。

MariaDB ストアドプロシージャにおけるループ反復ごとの実行時間計測には、さまざまな方法があります。状況に合わせて適切な方法を選択してください。


mariadb


MariaDBのMultiple File Tablesでパフォーマンスを向上させる

利点パフォーマンス向上: データが複数のファイルに分散されることで、I/O操作が分散され、読み書き速度が向上します。スケーラビリティ: データ量が増加しても、ファイルを追加することでテーブルを拡張できます。可用性向上: 1つのファイルが破損しても、他のファイルからデータを取り出すことができるため、データ損失のリスクが低減されます。...


MySQL/MariaDBのパフォーマンス改善ガイド:パーティショニング、インデックス、キャッシュ、ハードウェア徹底解説

MySQL/MariaDBでパーティショニングがパフォーマンス向上に必ずしも繋がら ないという疑問をお持ちのことと思います。確かに、パーティショニングは 適切な状況で使用すれば有効なツールですが、万能ではありません。本記事では、パーティショニングがパフォーマンスを向上させない理由と、 その状況を改善するためのヒントについて解説します。...


Laravel 5.4 で "Wrong COM_STMT_PREPARE response size" エラーが発生する原因

Laravel 5.4 で MySQL または MariaDB と接続する場合、COM_STMT_PREPARE response size エラーが発生することがあります。このエラーは、データベースサーバーから受け取ったパケットサイズが予期よりも大きい場合に発生します。...


Ddevで2番目のデータベースを作成してロードする方法

Ddev で 2 番目のデータベースを作成してロードするには、以下の手順が必要です。まず、.ddev/config. yaml ファイルを開き、以下の内容を追加します。上記の例では、my_second_database という名前の 2 番目のデータベースを作成しています。...


Dapperでデータベース操作をもっとスマートに:複数行挿入のテクニック

Dapper は、C# で ADO. NET を簡潔に使用するためのオープンソースのマイクロ ORM です。Dapper を使用すると、複雑な SQL クエリを記述することなく、データベース操作を効率的に実行できます。このチュートリアルでは、Dapper を使用して、単一のクエリで複数の行をデータベースに挿入する方法を説明します。...