MQL5におけるSQLite:新機能とパフォーマンステスト

 

2265 ビルドでは、SQLite 3.30.1 をベースにした通常のデータベース関数を実装しています。


データベースは、 DATABASE_OPEN_MEMORY フラグにより、ディスク上またはメモリ上にのみ保持することができます。 大量の挿入や変更をDatabaseTransactionBegin/Commit/Rollbackトランザクションでラップすることで、操作を数百倍に高速化することができます。

パフォーマンスを最大限に重視するため、LLVM 9.0.0とMQL5の比較テストの結果を紹介します。 時間(ミリ秒)、少ないほど良い。
Windows 10 x64, Intel Xeon  E5-2690 v3 @ 2.60GHz
                                                        LLVM   MQL5
---------------------------------------------------------------------------------
Test  1: 1000 INSERTs:                                 11572   8488
Test  2: 25000 INSERTs in a transaction:                  59     60
Test  3: 25000 INSERTs into an indexed table:            102    105
Test  4: 100 SELECTs without an index:                   142    150
Test  5: 100 SELECTs on a string comparison:             391    390
Test  6: Creating an index:                               43     33
Test  7: 5000 SELECTs with an index:                     385    307
Test  8: 1000 UPDATEs without an index:                   58      54
Test  9: 25000 UPDATEs with an index:                    161    165
Test 10: 25000 text UPDATEs with an index:               124    120
Test 11: INSERTs from a SELECT:                           84     84
Test 12: DELETE without an index:                         25     74
Test 13: DELETE with an index:                            70     72
Test 14: A big INSERT after a big DELETE:                 62     66
Test 15: A big DELETE followed by many small INSERTs:     33     33
Test 16: DROP TABLE: finished.                            42     40

MQL5でのスピードは、最高のコンパイラを使ったネイティブC++と全く同じです。再生用のベンチマーク一式を添付します。


また、構造体に直接レコードを読み込むことができる独自のDatabaseReadBind関数を実装し、一括処理の簡略化と高速化を実現しました。

ここでは、簡単な例をご紹介します。

struct Person
  {
   int               id;
   string            name;
   int               age;
   string            address;
   double            salary;
  };

//+------------------------------------------------------------------+
//| Test                                                             |
//+------------------------------------------------------------------+
bool TestDB(string filename,int flags)
  {
   int db;
//--- open
   db=DatabaseOpen(filename,flags);
   if(db==INVALID_HANDLE)
     {
      Print("DB: ",filename," open failed with code ",GetLastError());
      return(false);
     }
//--- create a table
   if(!DatabaseTableExists(db,"COMPANY"))
      if(!DatabaseExecute(db,"CREATE TABLE COMPANY("
                          "ID INT PRIMARY KEY     NOT NULL,"
                          "NAME           TEXT    NOT NULL,"
                          "AGE            INT     NOT NULL,"
                          "ADDRESS        CHAR(50),"
                          "SALARY         REAL );"))
        {
         Print("DB: ",filename," create table failed with code ",GetLastError());
         DatabaseClose(db);
         return(false);
        }
//--- insert data
   if(!DatabaseExecute(db,"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1, 'Paul', 32, 'California', 20000.00 ); "
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); "
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );"
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );"))
     {
      Print("DB: ",filename," insert failed with code ",GetLastError());
      DatabaseClose(db);
      return(false);
     }
//--- prepare the request
   int request=DatabasePrepare(db,"SELECT * FROM COMPANY WHERE SALARY>15000");

   if(request==INVALID_HANDLE)
     {
      Print("DB: ",filename," request failed with code ",GetLastError());
      DatabaseClose(db);
      return(false);
     }
//--- выводим записи
   Person person;

   for(int i=0; DatabaseReadBind(request,person); i++)
      Print(i,":  ",person.id, " ", person.name, " ",person.age, " ",person.address, " ",person.salary);

   Print("");
//--- close all
   DatabaseFinalize(request);
   DatabaseClose(db);
   return(true);
  }

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   TestDB("test.sqlite",DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);
  }


Output:
0:  1 Paul 32 California 20000.0
1:  3 Teddy 23 Norway 20000.0
2:  4 Mark 25 Rich-Mond  65000.0



ファイル:
SqLiteTest.zip  2709 kb
 
レナトのニュース紹介は最高ですそんな疑問があります。
.sqliteファイルをMEプロジェクト構造に含めることができますか?その後の.ex5でのパッケージングのために
もし可能なら、.sqliteファイルのサイズが 大きくなったときに.ex5プログラムはどのように動作しますか? すでにコンパイルされた.ex5プログラムにおいて
 

新機能をありがとうございます。
新機能のヘルプが充実していることが、使いこなすためのポイントだと考えています。 ヘルプ自体に作例がないのは本当に寂しいです。
また、以下のような不備が見つかりましたので、ご注意ください。


1)DatabaseExecute 関数の記述は、DatabasePrepareから コピーしたものであり、真実ではありません。

2)DatabaseRead 関数の最初のパラメーターの記述が不完全:intdatabase,// DatabaseOpen で取得したデータベースハンドル;
DatabasePrepare の
方がより完全な情報を提供するので。DatabaseRead() で実行可能なクエリハンドルを作成 します

3)DatabaseTransactionXXX 関数は、本当に与えられたGetLastError()エラーリストを生成するのか、それとも「以前の失敗からのフォローアップエラー」を実行するのか?

4)DatabaseTransactionXXX の 関数には、ネストされたトランザクションの処理に関する情報はない。

5)DatabaseColumnName 関数のパラメータ記述に誤りがあります("to get field name "である必要があります)。
string&name//テーブル名を取得するための変数への参照

 
Roman:
レナトさん、素晴らしいニュースです。ひとつ疑問が生じました。
.sqliteファイルは、MEプロジェクト構造に含めることができますか?
その場合、.sqliteのファイルサイズが大きくなると、.ex5プログラムはどのように動作しますか? すでにコンパイル済みの.ex5プログラムにおいて

ほとんどの場合、リソースを許可し、これらのファイルは、プログラムの初回起動時に自動的にディスクに展開されます。

つまり、ex5内部でベースが膨らむことはないのです。このファイルはディスク上でしか扱えません。

 
Renat Fatkhullin:

ベースは、 DATABASE_OPEN_MEMORYフラグを 使用して、ディスク上または メモリ上にのみ 保持することができます。

これは、MT5端末間(SSDファイルを殺す代わりに)と端末内のプログラム間(リソースの代わりに)のデータ交換の正式な仕組みということでよろしいでしょうか?

 
Sergey Dzyublik:

新機能をありがとうございます。
新機能のヘルプが充実していることが、使いこなす成功の鍵だと思います。 ヘルプ自体に作業例がないのは本当に寂しいです。
また、私が発見した以下の不備にもご注目ください。


1)DatabaseExecute 関数の記述は、DatabasePrepareから コピーしたものであり、真実ではありません。

2)DatabaseRead 関数の最初のパラメーターの記述が不完全:intdatabase, // DatabaseOpen で取得したデータベースハンドル;
DatabasePrepare の
方がより完全な情報を提供するので。クエリーハンドルを作成し、DatabaseRead() で実行できるようにします

3)DatabaseTransactionXXX の 関数は、与えられたエラーリスト GetLastError() を本当に生成するのでしょうか、それとも「以前の失敗からのフォローアップエラー」を実行するのでしょうか?

4)DatabaseTransactionXXX の 関数には、ネストされたトランザクションの処理に関する情報はない。

5)DatabaseColumnName 関数のパラメータ記述に誤りがあります("to get field name "でなければなりません)。
string&name//テーブル名を取得するための変数への参照

ヘルプはすでに一部更新されていますので、再度ご覧ください。これはまだヘルプの最初のバージョンであり、更新される予定です。

SQLiteにはネストされたトランザクションは存在しないので、それを行おうとする必要はない。

トピックの中で例が紹介されていますが、そこに複雑なことは全くありません。後日、標準ライブラリの記事とラッパークラスをやる予定です。

 
fxsaber:

これは、MT5端末間(SSDのkillingファイルの代わり)、端末内のプログラム間(リソースの代わり)のデータ交換の正式な仕組みという理解でよろしいでしょうか?

無能なユーザーから「SSDを殺す」なんて露骨なデタラメを流されるのはやめてくれ。

いや、これはファイルベースなので、交換は可能ですが、ベースを同時にオープンするとアクセスが独占される可能性があり、異なる専門家から同時にアクセスするのはリスクが高いです。

オープンな「DATABASE_OPEN_MEMORY フラグによるメモリ内限定」データベースは、特定のプログラムのみが利用でき、誰とも共有されません。


データベースアプリケーション。

  1. MQL5プログラムの設定と状態の保存

  2. 大容量データストレージ

  3. 外部で準備したデータを使う

    . 例えば、MetatraderからSQLiteにデータをエクスポートし、Pythonですぐに使える数学パッケージを使ってこのデータを計算し、その結果をSQliteフォーマットにも格納します。

次のリリースでは、SQLiteデータベースを エディターで直接閲覧・編集できるようにネイティブサポートされ、通常のデータ交換メカニズムとして使えるようになる予定です。
 
Renat Fatkhullin:

次のリリースでは、SQLiteデータベースをエディタで直接表示・編集できるようになり、通常のデータ交換メカニズムとして利用できるようになります。

MEに搭載されたデータベースエディターは、まあ、とても便利でしょう、ありがとうございます。

 
Renat Fatkhullin:

ただし、異なるEAから同時にアクセスすると、アクセス権が独占される可能性があり、危険です。

PythonにはSqlite3Workerという ライブラリがあり、複数のスレッドからデータベースを操作する際に、スレッドセーフなI/Oを実現します。
おそらく、複数のExpert Advisorのデータベースで非同期作業を可能にするために、mqlに実装を移植することを考えるのは理にかなっています。
まあ、あるいはそのアイデアを拝借して、独自の非同期I/Oを実装するのもよいでしょう。

sqlite3worker
sqlite3worker
  • 2017.03.21
  • pypi.org
('Thread safe sqlite3 interface',)
 
Roman:

PythonにはSqlite3Worker ライブラリがあり、複数のスレッドからデータベースを操作する際に、スレッドセーフな入出力を実現します。
おそらく、複数のExpert Advisorのデータベースとの非同期作業を可能にするために、実装をmqlに移植することを検討するポイントがあります。
まあ、あるいはそのアイデアを拝借して、独自の非同期I/Oを実装するのもよいでしょう。

上の性能表はご覧になりましたか?C++よりもMQL5の方が速いことが多いですね。

もちろんマルチスレッドもありますし、すべてが正しいのです。

この質問は、異なるプログラム/プロセスが 独立して同じデータベースファイルにアクセスした場合にどうなるかという、他のことに関するものです。単一のプログラム(MQL5)ではなく、互いに知らない、同じデータベースハンドルを使用しない、複数の独立したプログラムです。

 
異なるプログラム/ターミナルから同じデータベースへのアクセスを同期させることは原理的に難しいのでしょうか?またエラソーになってるのか?
理由: