SQLite in MQL5: nuove caratteristiche e test delle prestazioni

 

Nella build 2265 abbiamo implementato le normali funzioni di database basate su SQLite 3.30.1:


I database possono essere tenuti sia su disco che in memoria solo con il flag DATABASE_OPEN_MEMORY. Avvolgere inserti/modifiche massicci in transazioni DatabaseTransactionBegin/Commit/Rollback velocizza le operazioni di centinaia di volte.

Dato che siamo concentrati al massimo sulle prestazioni, ecco i risultati dei test LLVM 9.0.0 vs MQL5. Tempo in millisecondi, meno è meglio è:
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 velocità in MQL5 è assolutamente la stessa del C++ nativo con uno dei migliori compilatori. Una suite di benchmark per il replay è allegata.


Abbiamo anche implementato una funzione unica DatabaseReadBind che permette di leggere i record direttamente nella struttura, il che semplifica e velocizza le operazioni di massa.

Ecco un semplice esempio:

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



File:
SqLiteTest.zip  2709 kb
 
Grande notizia-introduzione Renat! Esiste una tale domanda.
Un file .sqlite può essere incluso nella struttura del progetto ME? per il successivo confezionamento in .ex5
Se sì, come si comporta il programma .ex5 quando la dimensione del file.sqlite aumenta? in un programma .ex5 già compilato
 

Grazie per la nuova funzionalità.
Considero il buon aiuto sulla nuova funzionalità come la chiave del successo nel padroneggiarla. Mi mancano davvero gli esempi di lavoro nell'aiuto stesso.
Si prega di prestare attenzione anche alle seguenti carenze che ho trovato:


1) La descrizione della funzione DatabaseExecute non è vera, ma copiata daDatabasePrepare.

2) Descrizione incompleta del primo parametro della funzioneDatabaseRead:intdatabase,// handle del database ottenuto in DatabaseOpen;
PoichéDatabasePrepare fornisce informazioni più complete:Crea un handle di query, che può poi essere eseguito con DatabaseRead().

3) Le funzioniDatabaseTransactionXXX generano davvero le liste di errore GetLastError() date o eseguono un "errore di follow-up da un fallimento precedente"?

4) Non vengono fornite informazioni per le funzioni DatabaseTransactionXXX sulla gestione delle transazioni annidate.

5) C'è un refuso nella descrizione del parametro della funzioneDatabaseColumnName(deve essere "per ottenere il nome del campo")
string&name// riferimento alla variabile per ottenere il nome dellatabella

 
Roman:
Grande notizia, Renat! È sorta una domanda.
Un file .sqlite può essere incluso nella struttura del progetto ME?
Se è così, come si comporta il programma .ex5 quando la dimensione del file .sqlite aumenta? in un programma .ex5 già compilato

Molto probabilmente permetteremo l'inclusione di risorse e questi file saranno automaticamente estratti sul disco al primo avvio del programma.

Cioè, non ci sarà alcun rigonfiamento della base all'interno di ex5. Il file può essere gestito solo su disco.

 
Renat Fatkhullin:

Le basi possono essere tenute sia su disco che solo in memoria, usando il flag DATABASE_OPEN_MEMORY.

Ho ragione di capire che questo è un meccanismo ufficiale di scambio di dati tra i terminali MT5 (invece di uccidere i file SSD) e tra i programmi all'interno del terminale (invece di risorse)?

 
Sergey Dzyublik:

Grazie per la nuova funzionalità.
Penso che un buon aiuto per la nuova funzionalità sia la chiave del successo per padroneggiarla. Mi mancano davvero gli esempi di come lavorare nell'aiuto stesso.
Si prega di prestare attenzione anche alle seguenti carenze che ho trovato:


1) La descrizione della funzione DatabaseExecute non è vera, ma copiata da DatabasePrepare.

2) Descrizione incompleta del primo parametro della funzioneDatabaseRead:intdatabase, // handle del database ottenuto in DatabaseOpen;
Poiché DatabasePrepare fornisce informazioni più complete: s crea un handle di query, che può poi essere eseguito con DatabaseRead().

3) Le funzioniDatabaseTransactionXXX generano davvero gli elenchi di errori dati GetLastError() o eseguono un "errore di follow-up da un fallimento precedente"?

4) Non vengono fornite informazioni per le funzioni DatabaseTransactionXXX sulla gestione delle transazioni annidate.

5) C'è un refuso nella descrizione del parametro della funzione DatabaseColumnName (deve essere "per ottenere il nome del campo")
string&name// riferimento alla variabile per ottenere il nome dellatabella

L'aiuto è già stato parzialmente aggiornato, date un'altra occhiata. Questa è ancora la prima versione dell'aiuto e sarà aggiornata.

Non ci sono transazioni annidate in SQLite, quindi non c'è bisogno di provare a farle.

Gli esempi sono presentati nel topic, non c'è niente di complicato. Più tardi faremo un articolo e una classe wrapper nella libreria standard.

 
fxsaber:

Ho capito bene che questo è un meccanismo ufficiale di scambio di dati tra i terminali MT5 (invece di file che uccidono SSD) e tra i programmi all'interno del terminale (invece di risorse)?

Smettete di diffondere palesi sciocchezze su "uccidere gli SSD" da parte di utenti incompetenti.

No, queste sono basi di file - possono essere scambiate, ma è rischioso accedervi simultaneamente da diversi esperti a causa dell'accesso potenzialmente monopolistico quando le basi sono aperte allo stesso tempo.

I database aperti "in-memory-only by DATABASE_OPEN_MEMORY flag" sono disponibili solo per un programma specifico e non sono condivisi con nessuno.


Applicazioni di database:

  1. Memorizzazione delle impostazioni e degli stati dei programmi MQL5

  2. Archiviazione di massa dei dati

  3. Utilizzando i dati preparati esternamente

    . Per esempio, esportando i dati da Metatrader in SQLite, in Python ha calcolato questi dati utilizzando pacchetti matematici pronti all'uso e ha messo il risultato anche in formato SQlite.

Nella prossima release, ci sarà il supporto nativo per la visualizzazione e la modifica dei database SQLite direttamente nell'editor, il che porterà ad utilizzare questi database come un normale meccanismo di scambio dati.
 
Renat Fatkhullin:

Nella prossima release ci sarà il supporto nativo per la visualizzazione e la modifica dei database SQLite direttamente nell'editor, portando all'uso di questi database come un normale meccanismo di scambio dati.

L'editor di database in ME sarà, beh, molto utile, grazie.

 
Renat Fatkhullin:

No, queste sono basi di file - possono essere scambiate, ma è rischioso accedervi simultaneamente da diversi EA a causa dell'accesso potenzialmente monopolistico quando le basi sono aperte allo stesso tempo.

Python ha una libreria chiamataSqlite3Worker, per un I/O thread-safe quando si lavora con un database da più thread.
Forse ha senso pensare al porting dell'implementazione su mql, per permettere un lavoro asincrono con il database di più Expert Advisors.
Bene, o prendere in prestito l'idea e implementare il proprio I/O asincrono.

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

Python ha la libreriaSqlite3Worker, per un input/output thread-safe, quando si lavora con il database da più thread.
Forse c'è un punto per considerare l'implementazione del porting a mql, per consentire il lavoro asincrono con il database di più Expert Advisors.
Bene, o prendere in prestito l'idea e implementare il proprio I/O asincrono.

Avete visto la tabella delle prestazioni qui sopra? Spesso è più veloce in MQL5 che in C++.

Abbiamo il multithreading, naturalmente, e tutto è corretto.

La domanda riguarda qualcos'altro - cosa succede se diversi programmi/processi accedono indipendentemente allo stesso file di database. Non un singolo programma (MQL5), ma diversi programmi indipendenti che non sanno l'uno dell'altro e non usano lo stesso handle di database.

 
È difficile in linea di principio sincronizzare l'accesso allo stesso database da diversi programmi/terminali? Stai diventando di nuovo ersatz?
Motivazione: