SQLite dans MQL5 : nouvelles fonctionnalités et tests de performance

 

Dans la version 2265, nous avons implémenté les fonctions habituelles de base de données basées sur SQLite 3.30.1 :


Les bases de données peuvent être conservées sur disque ou en mémoire uniquement avec l'indicateur DATABASE_OPEN_MEMORY. Envelopper les insertions/modifications massives dans des transactions DatabaseTransactionBegin/Commit/Rollback accélère les opérations des centaines de fois.

Comme nous nous concentrons au maximum sur les performances, voici les résultats des tests LLVM 9.0.0 vs MQL5. Temps en millisecondes, moins il y en a, mieux c'est :
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

La vitesse en MQL5 est absolument la même qu'en C++ natif avec l'un des meilleurs compilateurs. Une suite de benchmarks pour le rejeu est jointe.


Nous avons également mis en œuvre une fonction unique DatabaseReadBind qui vous permet de lire les enregistrements directement dans la structure, ce qui simplifie et accélère les opérations de masse.

Voici un exemple simple :

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



Dossiers :
SqLiteTest.zip  2709 kb
 
Grande présentation de la nouvelle Renat ! Il y a une telle question.
Le fichier .sqlite peut-il être inclus dans la structure du projet ME ? pour un conditionnement ultérieur en .ex5
Si oui, comment le programme .ex5 se comportera-t-il lorsque la taille du fichier.sqlite sera augmentée ? dans un programme .ex5 déjà compilé.
 

Merci pour cette nouvelle fonctionnalité.
Je considère qu'une bonne aide sur la nouvelle fonctionnalité est la clé du succès pour la maîtriser. Les exemples de travail dans l'aide elle-même me manquent vraiment.
Veuillez également prêter attention aux déficiences suivantes qui ont été constatées :


1) La description de la fonction DatabaseExecute n'est pas vraie, mais copiée deDatabasePrepare.

2) Description incomplète du premier paramètre de la fonctionDatabaseRead:intdatabase,// handle de la base de données obtenu dans DatabaseOpen ;
DepuisDatabasePrepare, les informations sont plus complètes :Crée un handle de requête, qui peut ensuite être exécuté avec DatabaseRead().

3) Les fonctionsDatabaseTransactionXXX génèrent-elles réellement les listes d'erreurs GetLastError() données ou effectuent-elles une "erreur de suivi d'un échec précédent" ?

4) Aucune information n'est fournie pour les fonctions DatabaseTransactionXXX concernant la gestion des transactions imbriquées.

5) Il y a une erreur dans la description du paramètre de la fonctionDatabaseColumnName ( ce doit être "pour obtenir le nom du champ").
string&name// référence à la variable permettant de récupérer le nom de latable

 
Roman:
Excellente nouvelle, Renat ! Cette question s'est posée.
Un fichier .sqlite peut-il être inclus dans la structure du projet ME ?
Si c'est le cas, comment le programme .ex5 se comportera-t-il lorsque la taille du fichier .sqlite sera augmentée ? dans un programme .ex5 déjà compilé

Le plus souvent, nous autoriserons l'inclusion de ressources et ces fichiers seront automatiquement extraits sur le disque lors du premier démarrage du programme.

C'est-à-dire qu'il n'y aura pas de gonflement de la base à l'intérieur de ex5. Le fichier ne peut être traité que sur le disque.

 
Renat Fatkhullin:

Les bases peuvent être conservées soit sur le disque, soit en mémoire uniquement, en utilisant l'indicateur DATABASE_OPEN_MEMORY.

Ai-je raison de comprendre qu'il s'agit d'un mécanisme officiel d'échange de données entre les terminaux MT5 (au lieu de tuer les fichiers SSD) et entre les programmes à l'intérieur du terminal (au lieu des ressources) ?

 
Sergey Dzyublik:

Merci pour cette nouvelle fonctionnalité.
Je pense qu'une bonne aide pour la nouvelle fonctionnalité est la clé du succès pour la maîtriser. Les exemples de la façon de travailler dans l'aide elle-même me manquent vraiment.
Veuillez également prêter attention aux déficiences suivantes que j'ai constatées :


1) La description de la fonction DatabaseExecute n'est pas vraie, mais copiée de DatabasePrepare.

2) Description incomplète du premier paramètre de la fonctionDatabaseRead:intdatabase, // handle de la base de données obtenu dans DatabaseOpen ;
Depuis DatabasePrepare, les informations sont plus complètes : crée un handle de requête, qui peut ensuite être exécuté avec DatabaseRead().

3) Les fonctionsDatabaseTransactionXXX génèrent-elles réellement les listes d'erreurs données GetLastError() ou effectuent-elles un "suivi d'erreur d'un échec précédent" ?

4) Aucune information n'est fournie pour les fonctions DatabaseTransactionXXX concernant la gestion des transactions imbriquées.

5) Il y a une erreur dans la description du paramètre de la fonction DatabaseColumnName ( ce doit être "pour obtenir le nom du champ").
string&name// référence à la variable permettant de récupérer le nom de latable

L'aide a déjà été partiellement mise à jour, jetez-y un coup d'œil. Il s'agit encore de la première version de l'aide et elle sera mise à jour.

Il n'y a pas de transactions imbriquées dans SQLite, il est donc inutile d'essayer d'en faire.

Des exemples sont présentés dans le sujet, il n'y a rien de compliqué du tout. Nous ferons un article et une classe wrapper dans la bibliothèque standard plus tard.

 
fxsaber:

Ai-je bien compris qu'il s'agit d'un mécanisme officiel d'échange de données entre les terminaux MT5 (au lieu des fichiers SSD killing) et entre les programmes à l'intérieur du terminal (au lieu des ressources) ?

Arrêtez de répandre des absurdités flagrantes sur la "mort des SSD" de la part d'utilisateurs incompétents.

Non, ce sont des bases de fichiers - elles peuvent être échangées, mais il est risqué d'y accéder simultanément à partir de différents experts en raison d'un accès potentiellement monopolistique lorsque les bases sont ouvertes en même temps.

Les bases de données ouvertes "in-memory-only by DATABASE_OPEN_MEMORY flag" ne sont disponibles que pour un programme spécifique et ne sont partagées avec personne.


Applications de bases de données :

  1. Stockage des paramètres et des états des programmes MQL5

  2. Stockage de données en masse

  3. Utilisation de données préparées en externe

    . Par exemple, j'ai exporté des données de Metatrader vers SQLite, j'ai calculé ces données dans Python à l'aide de paquets mathématiques prêts à l'emploi et j'ai mis le résultat également au format SQlite.

Dans la prochaine version, il y aura un support natif pour visualiser et éditer les bases de données SQLite directement dans l'éditeur, ce qui conduira à l'utilisation de ces bases de données comme mécanisme régulier d'échange de données.
 
Renat Fatkhullin:

Dans la prochaine version, il y aura un support natif pour visualiser et modifier les bases de données SQLite directement dans l'éditeur, ce qui conduira à l'utilisation de ces bases de données comme mécanisme régulier d'échange de données.

L'éditeur de base de données dans ME sera, eh bien, très pratique, merci.

 
Renat Fatkhullin:

Non, il s'agit de bases de fichiers - elles peuvent être échangées, mais il est risqué d'y accéder simultanément à partir de différents EA en raison d'un accès potentiellement monopolistique lorsque les bases sont ouvertes en même temps.

Python dispose d'une bibliothèque appeléeSqlite3Worker qui permet d'effectuer des entrées/sorties sécurisées lorsque vous travaillez avec une base de données à partir de plusieurs threads.
Il serait peut-être judicieux de penser à porter l'implémentation vers mql, pour permettre un travail asynchrone avec la base de données de plusieurs Expert Advisors.
Eh bien, ou emprunter l'idée et mettre en œuvre votre propre E/S asynchrone.

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

Python dispose de la bibliothèqueSqlite3Worker, pour une entrée/sortie sécurisée par les threads, lorsque vous travaillez avec la base de données à partir de plusieurs threads.
Peut-être y a-t-il lieu d'envisager le portage de l'implémentation vers mql, pour permettre un travail asynchrone avec la base de données de plusieurs Expert Advisors.
Eh bien, ou emprunter l'idée et mettre en œuvre votre propre E/S asynchrone.

Avez-vous vu le tableau des performances ci-dessus ? Il est souvent plus rapide en MQL5 qu'en C++.

Nous avons le multithreading, bien sûr, et tout est correct.

La question porte sur un autre sujet : que se passe-t-il si différents programmes/processus accèdent indépendamment au même fichier de base de données ? Pas un seul programme (MQL5), mais plusieurs programmes indépendants qui ne se connaissent pas et n'utilisent pas le même gestionnaire de base de données.

 
Est-il difficile en principe de synchroniser l'accès à la même base de données à partir de différents programmes/terminaux ? Tu deviens encore un ersatz ?