決済のたびにSQLiteへ1行ずつ記録する方法を、以前の記事で解説しました。
ただ、この方法には向かない場面があります。
これまでCSVに溜め続けてきた過去の決済ログを、まとめてSQLiteに移したい場合です。
1件ずつINSERTを実行すると、
SQLiteは何もしなければ1回ごとに書き込みを確定させるため、
件数が多いほど、想像以上に時間がかかります。
この記事では、
DatabaseTransactionBegin/Commit/Rollbackを使い、
既刊のCSVログを安全かつ高速にSQLiteへ一括インポートする方法を解説します。
・1件ずつINSERTする方法が遅い理由
・DatabaseTransactionBegin/Commit/Rollbackの書式
・CSVログを安全に一括インポートする設計
・実際に動くサンプルコード全体
なぜ1件ずつINSERTする既刊の方法では不十分な場面があるのか
まず、既刊記事とこの記事がどう場面が違うのかを整理します。
既刊のSQLite記事はリアルタイムの1行追記が前提だった
既刊のSQLiteで集計する記事は、決済が発生した瞬間に1行だけINSERTする設計でした。
1件ずつなら、この方法で十分です。
大量データを一括で取り込む場面での遅さ
・既刊CSVログに数千行が溜まっている場合
・1行ずつINSERTを実行すると
・SQLiteは既定では1回ごとに書き込みを確定(自動コミット)するため
・件数が多いほど処理時間が大きく伸びる
既刊記事のFAQでも「CSVからSQLiteへの移行」には触れていましたが、
一括インポートを速く安全に行う方法までは扱っていませんでした。
DatabaseTransactionBegin/Commit/Rollbackの書式
この記事の中心になる3つの関数の仕様を確認します。
関数シグネチャ
bool DatabaseTransactionBegin(int database); bool DatabaseTransactionCommit(int database); bool DatabaseTransactionRollback(int database);
3つとも引数はDatabaseOpenが返したデータベースハンドルのみで、
戻り値は成功時true・失敗時falseです。
Beginで開始しCommitで確定、失敗時はRollback
公式リファレンス(DatabaseTransactionBegin)によると、
Beginで開始したトランザクションは、Commitの呼び出しで確定する必要があります。
・BeginからCommitまでの間に実行したINSERTは
・Commitを呼ぶまでは1つのまとまりとして保留される
・途中で失敗したらRollbackでまとめて取り消せる
なぜトランザクションでまとめると速くなるのか
仕組みの違いを確認します。
公式リファレンスが示す速度差
・公式リファレンスの例では2,737件のディールを挿入する際
・トランザクションを使うと約532.8倍速くなったと示されている
・(48.5ミリ秒vs25,818.9ミリ秒という具体的な数値が掲載されている)
SQLiteの自動コミットという仕組み
トランザクションを明示的に開始しない場合、SQLiteはINSERT文1件ごとに、
ディスクへの書き込みを確定する処理を行います。
Beginでまとめてしまえば、この確定処理が最後の1回だけで済みます。
CSVログをSQLiteへ一括インポートする設計
既刊のCSVログを例に、実際の取り込み処理を組み立てます。
・このスクリプトは実行するたびにCSVを無条件で取り込む
・同じファイルを2回実行すると記録が2重になる(詳しくは後述)
・移行が目的の『一度だけ実行するツール』として使うことを想定している
既刊fileopen-filewrite-mql5.mdのCSVを読み込む
既刊記事のCSVは「決済時刻,銘柄,方向,ロット,決済損益」
という、5列・区切り文字はカンマのヘッダー付きファイルでした。
・FILE_CSVモードのFileReadStringは1行ではなく1フィールドずつ返る
・5列のヘッダーを読み飛ばすには5回FileReadStringを呼ぶ必要がある
・既刊fileseek-filetell-mql5.mdで指摘した仕様と同じ
1行でも失敗したらRollbackで全体を取り消す
if(!DatabaseExecute(db, sql))
{
DatabaseTransactionRollback(db);
return(false);
}
途中の1行でINSERTが失敗した場合、それまで保留していた分も含めて、
まとめて取り消します。
中途半端な件数だけが取り込まれた状態を防ぐためです。
実際に動くコード全体
CSVの読み込みからトランザクションでの一括INSERTまでを含む、
実際にMetaEditorでコンパイルできるスクリプトです。
//+------------------------------------------------------------------+
//| ImportCsvLogToDatabase.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property script_show_inputs
input string InpCsvFileName = "deal_log.csv"; //既刊記事で作成したCSVログのファイル名
bool ImportCsvToDatabase(string csvFileName, int db)
{
int csvHandle = FileOpen(csvFileName, FILE_READ | FILE_CSV | FILE_ANSI, ',');
if(csvHandle == INVALID_HANDLE)
{
Print("CSVのFileOpenに失敗しました。error=", GetLastError());
return(false);
}
//ヘッダー行(5列)を読み飛ばす
for(int h = 0; h < 5; h++)
FileReadString(csvHandle);
if(!DatabaseTransactionBegin(db))
{
Print("DatabaseTransactionBeginに失敗しました。error=", GetLastError());
FileClose(csvHandle);
return(false);
}
int importedCount = 0;
while(!FileIsEnding(csvHandle))
{
string closeTimeStr = FileReadString(csvHandle);
string symbol = FileReadString(csvHandle);
string direction = FileReadString(csvHandle);
string lotsStr = FileReadString(csvHandle);
string profitStr = FileReadString(csvHandle);
if(symbol == "")
break; //末尾の空行対策
datetime closeTime = StringToTime(closeTimeStr);
double profit = StringToDouble(profitStr);
string sql = StringFormat(
"INSERT INTO trade_log(symbol, profit, close_time) VALUES('%s', %.2f, %d)",
symbol, profit, (long)closeTime);
if(!DatabaseExecute(db, sql))
{
Print("INSERTに失敗しました。Rollbackします。error=", GetLastError());
DatabaseTransactionRollback(db);
FileClose(csvHandle);
return(false);
}
importedCount++;
}
DatabaseTransactionCommit(db);
FileClose(csvHandle);
Print(importedCount, "件のCSV行をSQLiteへインポートしました");
return(true);
}
void OnStart()
{
int db = DatabaseOpen("TradeLog.sqlite", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
if(db == INVALID_HANDLE)
{
Print("DatabaseOpenに失敗しました。error=", GetLastError());
return;
}
DatabaseExecute(db,
"CREATE TABLE IF NOT EXISTS trade_log (symbol TEXT, profit REAL, close_time INTEGER)");
ImportCsvToDatabase(InpCsvFileName, db);
DatabaseClose(db);
}
MetaEditorでのコンパイル・実行手順
実際にMT5で動かすまでの手順です。
- MetaEditorで新規スクリプト「ImportCsvLogToDatabase」を作成し、上のコードを貼り付けてコンパイルする
- エラー・警告が0件であることを確認する
- 既刊記事で作成した`deal_log.csv`と同じ形式のファイルをMQL5/Filesフォルダに用意する
- デモ口座のチャートにスクリプトをドラッグし、インポートするCSVファイル名を確認して実行する
- 「エキスパート」タブに「○件のCSV行をSQLiteへインポートしました」と出ることを確認する
よくあるエラーと注意点
実装でつまずきやすいポイントをまとめます。
Beginを呼ばずにCommitだけ呼んでもエラーにならない場合がある
・Beginを呼び忘れても即座にエラーにならないことがある
・その場合、トランザクションの高速化の効果が得られないまま
・1件ずつの自動コミットに戻ってしまう
・必ずBeginの戻り値(bool)を確認する習慣をつける
同じCSVを2回インポートすると重複する
このスクリプトはCSVの内容を無条件でINSERTするため、
同じファイルを2回実行すると、同じ記録が2重に入ります。
重複を避けたい場合は、取り込み済みのファイル名を別途記録しておくか、
決済チケット番号などの一意な列を追加して重複チェックを行ってください。
既刊のDatabaseOpen記事との使い分け
似た目的の記事が複数あるため、使い分けを整理します。
| 記事 | 用途 | 1回の処理件数 | トランザクション |
|---|---|---|---|
| 本記事 | CSVの一括インポート | 数百〜数千件 | 使う(Begin/Commit/Rollback) |
| SQLite記事(既刊) | 決済のたびのリアルタイム記録 | 1件 | 使わない(自動コミット) |
よくある質問
DatabaseTransactionについて、よくある疑問に回答します。
決済ごとのリアルタイム記録にもトランザクションを使うべきですか?
1件ずつしかINSERTしない場面では、
トランザクションを使っても速度上のメリットはほとんどありません。
既刊記事のように、1件ごとにそのままDatabaseExecuteする方法で十分です。
トランザクションの中でSELECT文も使えますか?
使えます。
ただし、この記事のテーマはINSERTの高速化のため、
SELECT文を含めたい場合は、
既刊のDatabasePrepare/DatabaseReadの記事を参考にしてください。
インポート中にEAやMT5自体が落ちたらどうなりますか?
Commitが呼ばれる前に異常終了した場合、
そのトランザクション内のINSERTはSQLite側で確定されず、
データベースには反映されないと考えられます
(完全な保証については公式リファレンスに明記がないため、
重要な移行作業の前には必ずCSVの元ファイルを別途バックアップしてください)。
【MQL5入門】DatabaseTransactionBegin/Commit/Rollbackで決済ログCSVをSQLiteへ安全・高速に一括インポートする方法:まとめ
1件ずつINSERTする方法は、
決済のたびのリアルタイム記録には向いていますが、
数千件のCSVログを一括で取り込む場面では遅くなります。
DatabaseTransactionBegin/Commitで、
複数のINSERTを1つのまとまりとして扱えば、
書き込みの確定処理が1回だけになり、大幅に高速化できます。
途中で失敗した場合もRollbackでまとめて取り消せるため、
中途半端な件数だけが取り込まれた状態を防げます。
コメント