SQLite em MQL5: novas características e testes de desempenho

 

Na construção 2265, implementamos as funções regulares de banco de dados baseadas no SQLite 3.30.1:


Os bancos de dados podem ser mantidos em disco ou na memória somente com a bandeira DATABASE_OPEN_MEMORY. Embalagem de inserções/mudanças maciças no Banco de DadosTransactionBegin/Commit/Rollback transações acelera as operações centenas de vezes.

Como estamos focados no desempenho, aqui estão os resultados dos testes LLVM 9.0.0 vs MQL5. Tempo em milissegundos, quanto menos, melhor:
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

A velocidade em MQL5 é absolutamente a mesma que em C++ nativo com um dos melhores compiladores. Um conjunto de benchmarks para repetição é anexado.


Também implementamos uma função exclusiva DatabaseReadBind que permite a leitura de registros diretamente na estrutura, o que simplifica e acelera as operações a granel.

Aqui está um exemplo simples:

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



Arquivos anexados:
SqLiteTest.zip  2709 kb
 
Grande novidade - a introdução Renat! Existe essa questão.
O arquivo .sqlite pode ser incluído na estrutura do projeto ME? para posterior embalagem em .ex5
Em caso afirmativo, como se comportará o programa .ex5 quando o tamanho do arquivo.sqlite for aumentado? em um programa .ex5 já compilado
 

Obrigado pela nova funcionalidade.
Considero a boa ajuda na nova funcionalidade como a chave para o sucesso no domínio da mesma. Sinto muita falta de exemplos do trabalho na própria ajuda.
Por favor, preste atenção também às seguintes deficiências que foram encontradas:


1) A descrição da função DatabaseExecute não é verdadeira, mas copiada doDatabasePrepare.

2) Descrição incompleta do primeiro parâmetro da funçãoDatabaseRead:intdatabase,// manipulação da base de dados obtida em DatabaseOpen;
Uma vez queDatabasePrepare fornece informações mais completas:Cria uma alça de consulta, que pode então ser executada com DatabaseRead().

3) As funçõesDatabaseTransactionXXX realmente geram as listas de erros GetLastError() dadas ou realizam um "erro de acompanhamento de uma falha anterior"?

4) Nenhuma informação é fornecida para as funções DatabaseTransactionXXX sobre o manuseio de transações aninhadas.

5) Há um erro na descrição do parâmetro da funçãoDatabaseColumnName(deve ser "para obter o nome do campo")
string&name// referência à variável para obter o nome databela

 
Roman:
Ótima notícia, Renat! Surgiu uma pergunta.
Um arquivo .sqlite pode ser incluído na estrutura do projeto ME?
Se sim, como se comportará o programa .ex5 quando o tamanho do arquivo .sqlite for aumentado? em um programa .ex5 já compilado

Muito provavelmente permitiremos em recursos e estes arquivos serão automaticamente extraídos para o disco na primeira vez que o programa for iniciado.

Ou seja, não haverá inchaço da base dentro da ex5. O arquivo só pode ser manipulado em disco.

 
Renat Fatkhullin:

As bases podem ser mantidas em disco ou somente na memória, usando a bandeira DATABASE_OPEN_MEMORY.

Entendo corretamente que este é o mecanismo oficial de intercâmbio de dados entre terminais MT5 (em vez de matar arquivos SSD) e entre programas dentro do terminal (em vez de recursos)?

 
Sergey Dzyublik:

Obrigado pela nova funcionalidade.
Acho que uma boa ajuda para a nova funcionalidade é a chave do sucesso para dominá-la. Realmente sinto falta dos exemplos do trabalho na própria ajuda.
Por favor, preste atenção também às seguintes desvantagens que encontrei:


1) A descrição da função DatabaseExecute não é verdadeira, mas copiada do DatabasePrepare.

2) Descrição incompleta do primeiro parâmetro da funçãoDatabaseRead:intdatabase, // manipulação da base de dados obtida em DatabaseOpen;
Uma vez que DatabasePrepare fornece informações mais completas: s cria uma alça de consulta, que pode então ser executada com DatabaseRead().

3) As funçõesDatabaseTransactionXXX realmente geram as listas de erros dadas GetLastError() ou elas executam "follow-up error from a previous failure"?

4) Nenhuma informação é fornecida para as funções DatabaseTransactionXXX sobre o manuseio de transações aninhadas.

5) Há um erro na descrição do parâmetro da função DatabaseColumnName (deve ser "para obter o nome do campo")
string&name// referência à variável para obter o nome databela

A ajuda já foi parcialmente atualizada, dê uma olhada novamente. Esta é ainda a primeira versão da ajuda e será atualizada.

Não há transações aninhadas no SQLite, portanto, não há necessidade de tentar fazê-las.

Exemplos são apresentados no tópico, não há nada de complicado ali. Mais tarde faremos um artigo e uma classe de embalagem na biblioteca padrão.

 
fxsaber:

Entendo corretamente que este é um mecanismo oficial de intercâmbio de dados entre terminais MT5 (em vez de arquivos de matança SSD) e entre programas dentro do terminal (em vez de recursos)?

Pare de espalhar disparates flagrantes sobre "matar SSDs" de usuários incompetentes.

Não, são bases de arquivo - podem ser trocadas, mas é arriscado acessá-las simultaneamente de diferentes especialistas devido ao acesso potencialmente monopolístico quando as bases estão abertas ao mesmo tempo.

Os bancos de dados "in-memory-only by DATABASE_OPEN_MEMORY" estão disponíveis apenas para um programa específico e não são compartilhados com ninguém.


Aplicações de banco de dados:

  1. Armazenamento de configurações e estados dos programas MQL5

  2. Armazenamento de dados em massa

  3. Usando dados preparados externamente

    . Por exemplo, dados exportados do Metatrader para o SQLite, em Python calculou estes dados usando pacotes matemáticos prontos para uso e colocou o resultado também no formato SQlite.

No próximo lançamento, haverá suporte nativo para visualização e edição de bancos de dados SQLite diretamente no editor, o que levará ao uso desses bancos de dados como um mecanismo regular de intercâmbio de dados.
 
Renat Fatkhullin:

No próximo lançamento haverá suporte nativo para visualização e edição de bancos de dados SQLite diretamente no editor, levando ao uso desses bancos de dados como um mecanismo regular de intercâmbio de dados.

O editor do banco de dados em ME será, bem, muito útil, obrigado.

 
Renat Fatkhullin:

Não, são bases de arquivo - podem ser trocadas, mas é arriscado acessá-las simultaneamente de diferentes EAs devido ao acesso potencialmente monopolístico quando as bases estão abertas ao mesmo tempo.

Python tem uma biblioteca chamadaSqlite3Worker para E/S com segurança de fios quando se trabalha com um banco de dados de vários fios.
Talvez faça sentido pensar em portar a implementação para o mql, para permitir um trabalho assíncrono com o banco de dados de múltiplos Expert Advisors.
Bem, ou pegue a idéia emprestada e implemente sua própria E/S assíncrona.

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

Python tem a bibliotecaSqlite3Worker, para entrada/saída segura de threads, quando se trabalha com o banco de dados de vários threads.
Talvez haja um ponto a considerar na implementação da portabilidade para o mql, para permitir o trabalho assíncrono com o banco de dados de múltiplos Expert Advisors.
Bem, ou pegue a idéia emprestada e implemente sua própria E/S assíncrona.

Você já viu a tabela de desempenho acima? Muitas vezes é mais rápido na MQL5 do que na C++.

Temos multithreading, é claro, e tudo está correto.

A questão é sobre outra coisa - o que acontece se diferentes programas/processos acessarem independentemente o mesmo arquivo de banco de dados. Não um único programa (MQL5), mas vários programas independentes que não conhecem uns aos outros e não usam o mesmo cabo de banco de dados.

 
É difícil, em princípio, sincronizar o acesso ao mesmo banco de dados a partir de diferentes programas/terminais? Você está recebendo ersatz novamente?