SQLite в MQL5: новые функции и тестирование производительности

 

В 2265 билде мы реализовали штатные функции работы с базами данных на основе SQLite 3.30.1:


Базы можно держать как на диске, так и только в памяти с помощью флага DATABASE_OPEN_MEMORY. Обертывание массивных вставок/изменений в транзакции DatabaseTransactionBegin/Commit/Rollback ускоряет операции в сотни раз.

Так как мы максимально ориентированы на производительность, то вот результаты тестов LLVM 9.0.0 vs 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 абсолютно такая же как в нативном С++ с одним из самых лучших компиляторов. Пакет бенчмарков для воспроизведения приложен.


Мы также реализовали уникальную функцию 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
Если да, то как будет вести себя программа .ex5, при увеличении размера файла .sqlite? в уже скомпилированной программе .ex5
 

Спасибо за новый функционал.
Считаю хорошую справку по новому функционалу залогом успеха в его освоении. Очень не хватает примеров по работе в самой справке.
Так же прошу обратить внимание на следующие недостатки, которые удалось обнаружить:


1) Описание функции DatabaseExecute не соответствует действительности, а скопирована с  DatabasePrepare.

2) Неполное описание первого параметра функции DatabaseReadint     database,       // хендл базы данных, полученный в DatabaseOpen;
Так как в  DatabasePrepare предоставлена более полная информация: создает хендл запроса, который затем может быть исполнен с помощью DatabaseRead().

3) Функции DatabaseTransactionXXX действительно генерируют приведенные списки ошибок GetLastError() или они выполняют "followup error from a previous failure"?

4) Для функций DatabaseTransactionХХХ не предоставлена какая-либо информация о работе с nested transactions. 

5) Опечатка в описании параметра функции DatabaseColumnName (должно быть "для получения имени поля")
string&  name         // ссылка на переменную для получения имени таблицы

 
Roman:
Отличное ново-введение Ренат! Возник такой вопрос.
Файл .sqlite можно включать в структуру ME проекта? для последующей упаковки в .ex5
Если да, то как будет вести себя программа .ex5, при увеличении размера файла .sqlite? в уже скомпилированной программе .ex5

Скорее всего разрешим в ресурсы включать и эти файлы будут автоматически извлекаться на диск при первом запуске программы.

То есть, никакого распухания базы внутри ex5 не будет. С файлом можно работать только на диске.

 
Renat Fatkhullin:

Базы можно держать как на диске, так и только в памяти с помощью флага DATABASE_OPEN_MEMORY.

Правильно ли я понимаю, что это официальный механизм обмена данными между Терминалами MT5 (вместо убивающих SSD файлов) и между программами внутри Терминала (вместо ресурсов)?

 
Sergey Dzyublik:

Спасибо за новый функционал.
Считаю хорошую справку по новому функционалу залогом успеха в его освоении. Очень не хватает примеров по работе в самой справке.
Так же прошу обратить внимание на следующие недостатки, которые удалось обнаружить:


1) Описание функции DatabaseExecute не соответствует действительности, а скопирована с  DatabasePrepare.

2) Неполное описание первого параметра функции DatabaseReadint     database,       // хендл базы данных, полученный в DatabaseOpen;
Так как в  DatabasePrepare предоставлена более полная информация: с оздает хендл запроса, который затем может быть исполнен с помощью DatabaseRead().

3) Функции DatabaseTransactionXXX действительно генерируют приведенные списки ошибок GetLastError() или они выполняют "followup error from a previous failure"?

4) Для функций DatabaseTransactionХХХ не предоставлена какая-либо информация о работе с nested transactions. 

5) Опечатка в описании параметра функции  DatabaseColumnName (должно быть "для получения имени поля")
string&  name         // ссылка на переменную для получения имени таблицы

Справка уже частично обновлена, посмотрите еще раз. Это пока первая версия справки и она будет дополняться.

В SQLite нет вложенных транзакций, так что не надо их пытаться делать.

Примеры представлены в топике, там вообще ничего сложного. Позже сделаем статью и класс-обертку в стандартной библиотеке.

 
fxsaber:

Правильно ли я понимаю, что это официальный механизм обмена данными между Терминалами MT5 (вместо убивающих SSD файлов) и между программами внутри Терминала (вместо ресурсов)?

Хватит откровенные глупости про "убивающие SSD" от недалеких пользователей распространять.

Нет, это файловые базы - ими можно обмениваться, но рискованно одновременно обращаться из разных экспертов из-за потенциально монопольного доступа при одновременно открытых базах.

Открытые "только в памяти по флагу DATABASE_OPEN_MEMORY" базы доступны только конкретной программе и ни с кем не разделяются.


Области применения баз данных:

  1. Хранение настроек и состояний MQL5 программ

  2. Хранение массивных данных

  3. Использование подготовленных на стороне данных

    Например, экспортировали данные из Метатрейдера в SQLite, в Питоне просчитали эти данные на готовых математических пакетах и положили результат тоже в SQlite формате.

В следующем релизе будет нативная поддержка просмотра и редактирования SQLite баз данных прямо в редакторе, что приведет к использованию этих баз как штатный механизм обмена данными.
 
Renat Fatkhullin: 

В следующем релизе будет нативная поддержка просмотра и редактирования SQLite баз данных прямо в редакторе, что приведет к использованию этих баз как штатный механизм обмена данными.

Редактор баз данных в МЕ, ну очень удобно будет, спасибо.

 
Renat Fatkhullin:

Нет, это файловые базы - ими можно обмениваться, но рискованно одновременно обращаться из разных экспертов из-за потенциально монопольного доступа при одновременно открытых базах.

В Python есть библиотека Sqlite3Worker, для потокобезопасного ввода/вывода, при работе с базой из нескольких потоков. 
Возможно есть смысл, подумать над портированием реализации в mql, для возможности асинхронной работы с базой из нескольких экспертов.
Ну, или позаимствовать идею, и реализовать свой асинхронный ввод/вывод.

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

В Python есть библиотека Sqlite3Worker, для потокобезопасного ввода/вывода, при работе с базой из нескольких потоков. 
Возможно есть смысл, подумать над портированием реализации в mql, для возможности асинхронной работы с базой из нескольких экспертов.
Ну, или позаимствовать идею, и реализовать свой асинхронный ввод/вывод.

Вы табличку производительности видели выше? Там в MQL5 часто быстрее чем в С++ работает.

У нас все конечно же мультипотоково и все правильно.

Речь же совсем о другом - что произойдет, если разные программы/процессы независимо полезут в один и тот же файл базы данных. Не одна программа(MQL5), а несколько независимых, не знающих друг об друге и не использующие один и тот же database handle.

 
А что, разве существует принципиальная сложность организовать синхронизацию доступа к одной базе из разных программ/терминалов? Опять эрзац получается?
Причина обращения: