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 函数参数描述中有一个错字(必须是 "获得字段名")。
string&name// 对用于获取表名的变量的引用

 
Roman:
好消息,Renat!出现了一个问题。
.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 函数参数描述中有一个错字(必须是 "获得字段名")。
string&name// 对用于获取表名的变量的引用

帮助已经部分更新了,再看一下吧。这仍然是第一个版本的帮助,它将被更新。

在SQLite中没有嵌套事务,所以没有必要尝试做这些事务。

专题中提出了例子,那里根本就没有什么复杂的东西。我们以后会在标准库中做一篇文章和一个封装类。

 
fxsaber:

我是否正确理解,这是MT5终端之间(而不是SSD杀毒文件)和终端内部程序之间(而不是资源)的正式数据交换机制?

不要再散布无能的用户关于 "杀死SSD "的公然胡说八道。

不,这些是文件库--它们可以被交换,但由于基地同时开放时可能会出现垄断性的访问,因此从不同的专家那里同时访问它们是有风险的。

开放的 "只在内存中由DATABASE_OPEN_MEMORY 标志 "的数据库只对特定程序可用,不与任何人共享。


数据库应用。

  1. 存储MQL5程序的设置和状态

  2. 大规模数据存储

  3. 使用外部准备的数据

    。例如,从Metatrader导出数据到SQLite,在Python中使用现成的数学包计算这些数据,并将结果也放在SQlite格式中。

在下一个版本中,将有对直接在编辑器中查看和编辑SQLite数据库 的本地支持,这将导致使用这些数据库作为常规的数据交换机制。
 
Renat Fatkhullin:

在下一个版本中,将有对直接在编辑器中查看和编辑SQLite数据库的原生支持,从而将这些数据库作为一种常规的数据交换机制来使用。

ME中的数据库编辑器将是,嗯,非常方便的,谢谢你。

 
Renat Fatkhullin:

不,这些是文件库--它们可以被交换,但从不同的EA同时访问它们是有风险的,因为当基地同时开放时,有可能被垄断访问。

Python有一个叫做Sqlite3Worker 的库,当从多个线程处理数据库时,可以实现线程安全的I/O。
也许考虑将实现移植到mql是有意义的,以允许与多个专家顾问的数据库异步工作。
嗯,或者借用这个想法,实现你自己的异步I/O。

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

Python有Sqlite3Worker 库,用于线程安全的输入/输出,当从多个线程处理数据库时。
也许有必要考虑将实现移植到mql,以允许与多个专家顾问的数据库异步工作。
嗯,或者借用这个想法,实现你自己的异步I/O。

你看到上面的业绩表了吗?在MQL5中往往比在C++中更快。

当然,我们有多线程,而且一切都很正确。

这个问题是关于其他方面的--如果不同的程序/进程 独立访问同一个数据库文件会发生什么。不是一个单一的程序(MQL5),而是几个独立的程序,它们互不相识,也不使用同一个数据库手柄。

 
从不同的程序/终端同步访问同一个数据库,原则上有困难吗?你是不是又开始搞起了 "阿尔法"?
原因: