SQLite en MQL5: nuevas funciones y pruebas de rendimiento

 

En la versión 2265 hemos implementado las funciones regulares de la base de datos basadas en SQLite 3.30.1:


Las bases de datos pueden mantenerse en disco o en memoria sólo con la bandera DATABASE_OPEN_MEMORY. Envolver inserciones/cambios masivos en transacciones DatabaseTransactionBegin/Commit/Rollback acelera las operaciones cientos de veces.

Como nos centramos al máximo en el rendimiento, aquí están los resultados de las pruebas de LLVM 9.0.0 frente a MQL5. Tiempo en milisegundos, cuanto menos mejor:
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 velocidad en MQL5 es absolutamente la misma que en C++ nativo con uno de los mejores compiladores. Se adjunta un conjunto de puntos de referencia para la reproducción.


También hemos implementado una función única DatabaseReadBind que permite leer registros directamente en la estructura, lo que simplifica y acelera las operaciones masivas.

He aquí un ejemplo sencillo:

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



Archivos adjuntos:
SqLiteTest.zip  2709 kb
 
¡Gran noticia-presentación Renat! Hay una pregunta de este tipo.
¿Se puede incluir el archivo .sqlite en la estructura del proyecto ME? para su posterior empaquetado en .ex5
Si es así, ¿cómo se comportará el programa .ex5 cuando se aumente el tamaño del archivo.sqlite? en un programa .ex5 ya compilado
 

Gracias por la nueva funcionalidad.
Considero que la buena ayuda sobre la nueva funcionalidad es la clave del éxito para dominarla. Realmente echo de menos ejemplos del trabajo en la propia ayuda.
Por favor, preste también atención a las siguientes deficiencias encontradas:


1) La descripción de la función DatabaseExecute no es verdadera, sino copiada deDatabasePrepare.

2) Descripción incompleta del primer parámetro de la funciónDatabaseRead:intdatabase,// database handle obtenido en DatabaseOpen;
Ya queDatabasePrepare proporciona información más completa:Crea un manejador de consulta, que luego puede ser ejecutado con DatabaseRead().

3) ¿Las funcionesDatabaseTransactionXXX generan realmente las listas de errores GetLastError() dadas o realizan un "error de seguimiento de un fallo anterior"?

4) No se proporciona información para las funciones DatabaseTransactionXXX sobre el manejo de transacciones anidadas.

5) Hay una errata en la descripción del parámetro de la funciónDatabaseColumnName(debe ser "para obtener el nombre del campo")
string&name// referencia a la variable para obtener el nombre dela tabla

 
Roman:
¡Gran noticia, Renat! Se ha planteado esta cuestión.
¿Se puede incluir un archivo .sqlite en la estructura del proyecto ME?
Si es así, ¿cómo se comportará el programa .ex5 cuando se aumente el tamaño del archivo .sqlite? en un programa .ex5 ya compilado

Lo más probable es que permitamos incluir recursos y que estos archivos se extraigan automáticamente al disco la primera vez que se inicie el programa.

Es decir, no habrá hinchazón de la base dentro de ex5. El archivo sólo se puede manejar en el disco.

 
Renat Fatkhullin:

Las bases pueden guardarse en disco o sólo en memoria, utilizando el indicador DATABASE_OPEN_MEMORY.

¿Estoy en lo cierto al entender que se trata de un mecanismo oficial de intercambio de datos entre terminales MT5 (en lugar de matar archivos SSD) y entre programas dentro del terminal (en lugar de recursos)?

 
Sergey Dzyublik:

Gracias por la nueva funcionalidad.
Creo que una buena ayuda para la nueva funcionalidad es la clave del éxito para dominarla. Realmente echo de menos los ejemplos del trabajo en la propia ayuda.
Por favor, preste también atención a las siguientes deficiencias que he encontrado:


1) La descripción de la función DatabaseExecute no es verdadera, sino copiada de DatabasePrepare.

2) Descripción incompleta del primer parámetro de la funciónDatabaseRead:intdatabase, // database handle obtenido en DatabaseOpen;
Ya que DatabasePrepare proporciona información más completa: s crea un manejador de consulta, que luego puede ser ejecutado con DatabaseRead().

3) ¿Las funcionesDatabaseTransactionXXX generan realmente las listas de error dadas GetLastError() o realizan un "error de seguimiento de un fallo anterior"?

4) No se proporciona información para las funciones DatabaseTransactionXXX sobre el manejo de transacciones anidadas.

5) Hay una errata en la descripción del parámetro de la función DatabaseColumnName (debe ser "para obtener el nombre del campo")
string&name// referencia a la variable para obtener el nombre dela tabla

La ayuda ya ha sido parcialmente actualizada, echa un vistazo de nuevo. Esta es todavía la primera versión de la ayuda y será actualizada.

No hay transacciones anidadas en SQLite, así que no es necesario intentar hacerlas.

Los ejemplos se presentan en el tema, no hay nada complicado en absoluto. Más adelante haremos un artículo y una clase envolvente en la biblioteca estándar.

 
fxsaber:

¿Entiendo correctamente que se trata de un mecanismo oficial de intercambio de datos entre terminales MT5 (en lugar de archivos de matanza SSD) y entre programas dentro del terminal (en lugar de recursos)?

Dejen de difundir tonterías descaradas sobre "matar a los SSD" por parte de usuarios incompetentes.

No, se trata de bases de datos de archivos, que pueden intercambiarse, pero es arriesgado acceder a ellas simultáneamente desde distintos expertos debido a un acceso potencialmente monopolístico cuando las bases están abiertas al mismo tiempo.

Las bases de datos abiertas "in-memory-only by DATABASE_OPEN_MEMORY flag" sólo están disponibles para un programa específico y no se comparten con nadie.


Aplicaciones de bases de datos:

  1. Almacenamiento de ajustes y estados de los programas MQL5

  2. Almacenamiento masivo de datos

  3. Usando datos preparados externamente

    . Por ejemplo, exportar datos de Metatrader a SQLite, en Python calcular estos datos usando paquetes matemáticos listos para usar y poner el resultado también en formato SQlite.

En la próxima versión, habrá soporte nativo para ver y editar bases de datos SQLite directamente en el editor, lo que llevará a utilizar estas bases de datos como mecanismo de intercambio de datos habitual.
 
Renat Fatkhullin:

En la próxima versión habrá soporte nativo para ver y editar bases de datos SQLite directamente en el editor, lo que permitirá utilizar estas bases de datos como mecanismo habitual de intercambio de datos.

El editor de la base de datos en ME, pues sería muy útil, gracias.

 
Renat Fatkhullin:

No, se trata de bases de archivos - se pueden intercambiar, pero es arriesgado acceder a ellas simultáneamente desde diferentes EAs debido al acceso potencialmente monopolístico cuando las bases están abiertas al mismo tiempo.

Python tiene una biblioteca llamadaSqlite3Worker, para la E/S a prueba de hilos cuando se trabaja con una base de datos desde múltiples hilos.
Quizás tenga sentido pensar en portar la implementación a mql, para permitir el trabajo asíncrono con la base de datos de múltiples Asesores Expertos.
Bueno, o tomar prestada la idea e implementar tu propia E/S asíncrona.

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

Python dispone de la libreríaSqlite3Worker, para la entrada/salida a prueba de hilos, cuando se trabaja con la base de datos desde múltiples hilos.
Tal vez hay un punto para considerar la portación de la implementación a mql, para permitir el trabajo asíncrono con la base de datos de varios Asesores Expertos.
Bueno, o tomar prestada la idea e implementar tu propia E/S asíncrona.

¿Has visto la tabla de rendimiento de arriba? A menudo es más rápido en MQL5 que en C++.

Tenemos multithreading, por supuesto, y todo es correcto.

La pregunta se refiere a otra cosa: qué ocurre si diferentes programas/procesos acceden de forma independiente al mismo archivo de base de datos. No se trata de un único programa (MQL5), sino de varios programas independientes que no se conocen entre sí y no utilizan el mismo manejador de base de datos.

 
¿Es difícil, en principio, sincronizar el acceso a la misma base de datos desde diferentes programas/terminales? ¿Te estás poniendo ersatz otra vez?
Razón de la queja: