SQLite é um banco de dados relacional popular que você incorpora em seu aplicativo. Com uma quantidade crescente de dados em seu banco de dados, você precisa aplicar o ajuste de desempenho do SQLite. Este artigo discute índices e suas armadilhas, o uso do planejador de consulta, o modo de diário Write-Ahead-Logging (WAL) e o aumento do tamanho do cache. Ele também explica a importância de medir o impacto de seus ajustes, usando testes automatizados.
Introdução
SQLite é um sistema de banco de dados relacional (DB) popular . Ao contrário de seus irmãos maiores, baseados em cliente-servidor, como o MySQL, o SQLite pode ser incorporado ao seu aplicativo como uma biblioteca . O SQLite tem um conjunto de recursos muito semelhante e também pode lidar com milhões de linhas, desde que você conheça algumas dicas e truques sobre ajuste de desempenho. Como as seções a seguir mostrarão, há mais para saber sobre o ajuste de desempenho do SQLite do que apenas criar índices.
Crie índices, mas com cautela
A ideia básica de um índice é acelerar a leitura de dados específicos , ou seja,
SELECT
declarações com um WHERE
cláusula. Os índices também aceleram a classificação dados (ORDER BY
), ou JOIN
tabelas. Infelizmente, os índices são uma faca de dois gumes, pois consomem espaço adicional em disco e retardam a manipulação de dados (INSERT
, UPDATE
, DELETE
). O conselho geral é criar o menor número possível de índices, mas quantos forem necessários . Além disso, os índices só fazem sentido para maiores bancos de dados, com milhares ou milhões de linhas.
Use o planejador de consultas para analisar suas consultas
A forma como os índices são usados internamente pelo SQLite são documentados, mas não muito fáceis de entender. Conforme detalhado neste artigo, é uma boa ideia analisar uma consulta prefixando-a com
EXPLAIN QUERY PLAN
. Dê uma olhada em cada linha de saída, das quais existem três variantes básicas:SEARCH table ...
linhas são um bom sinal – o SQLite usa um de seus índices!SCAN table ... USING INDEX
é um mau sinal,SCAN table ...
é ainda pior!
Tente evitar
SCAN table [using index]
entradas na saída de EXPLAIN QUERY PLAN
sempre que possível, porque você terá problemas de desempenho em bancos de dados maiores. Use EXPLAIN QUERY PLAN
para iterativo adicionar ou modificar seus índices até que não haja mais SCAN table
entradas aparecem. Otimizar consultas que envolvem IS NOT
Verificando
IS NOT ...
é caro porque o SQLite terá que digitalizar todas as linhas da tabela, mesmo que a coluna afetada tenha um índice . Os índices só são úteis se você procurar valores específicos, ou seja, comparações envolvendo < (menor), > (maior) ou = (igual), mas eles não se aplicam a !=(desigual). Um pequeno truque legal é que você pode substituir a coluna
WHERE column != value
com WHERE column > value OR column < value
. Isso usará o índice da coluna e afetará efetivamente todas as linhas cujo valor não seja igual a value
. Da mesma forma, um WHERE stringColumn != ''
pode ser substituído por WHERE stringColumn > ''
, porque as strings são classificáveis. No entanto, ao aplicar esse truque, certifique-se de saber como o SQLite lida com NULL
comparações. Por exemplo, o SQLite avalia NULL > ''
como FALSE
. Se você usar esse truque de comparação, há outra advertência caso sua consulta contenha
WHERE
e ORDER BY
, cada um com uma coluna diferente:isso tornará a consulta ineficiente novamente. Se possível, use o mesmo coluna em WHERE
e ORDER BY
, ou crie um índice de cobertura que envolve tanto o WHERE
e ORDER BY
coluna. Melhore a velocidade de gravação com o Write-Ahead-Log
O Write-Ahead-Logging (WAL) o modo diário melhora significativamente o desempenho de gravação/atualização , em comparação com o padrão reversão modo diário. No entanto, conforme documentado aqui, há algumas ressalvas . Por exemplo, o modo WAL não está disponível em determinados sistemas operacionais. Além disso, há garantias de consistência de dados reduzidas em caso de falha de hardware . Certifique-se de ler as primeiras páginas para entender o que você está fazendo.
Descobri que o comando
PRAGMA synchronous = NORMAL
fornece uma aceleração de 3-4x. Configurando journal_mode
para WAL
em seguida, melhora o desempenho novamente significativamente (aproximadamente 10x ou mais, dependendo do sistema operacional). Além das advertências que já mencionei, você também deve estar ciente do seguinte:
- Usando o modo de diário WAL, haverá dois arquivos adicionais ao lado do arquivo de banco de dados em seu sistema de arquivos, que têm o mesmo nome do banco de dados, mas com o sufixo “-shm” e “-wal”. Normalmente você não precisa se preocupar, mas se você for enviar o banco de dados para outra máquina enquanto seu aplicativo estiver em execução, não se esqueça de incluir esses dois arquivos. O SQLite compactará esses dois arquivos no arquivo principal sempre que você normalmente fechar todas as conexões de banco de dados abertas.
- O desempenho de inserção ou atualização cairá ocasionalmente, sempre que a consulta acionar a mesclagem do conteúdo do arquivo de log do WAL no arquivo de banco de dados principal. Isso é chamado de ponto de verificação , veja aqui.
- Descobri que
PRAGMA
s que alteramjournal_mode
esynchronous
não parecem ser armazenados persistentemente no banco de dados. Assim, eu sempre execute-os novamente sempre que eu abrir uma nova conexão de banco de dados, em vez de apenas executá-los ao criar as tabelas pela primeira vez.
Meça tudo
Sempre que você adicionar ajustes de desempenho, certifique-se de medir o impacto. Os testes automatizados (unitários) são uma ótima abordagem para isso. Eles ajudam a documentar suas descobertas para sua equipe, e eles descobrirão comportamentos desviantes ao longo do tempo , por exemplo. quando você atualiza para uma versão mais recente do SQLite. Exemplos do que você pode medir:
- Qual é o efeito de usar o WAL modo diário sobre o reversão modo? Qual é o efeito de outros (supostamente)
PRAGMA
que melhoram o desempenho s? - Depois de adicionar/alterar/remover um índice, quanto mais rápido
SELECT
declarações se tornam? Quanto mais lentoINSERT/DELETE/UPDATE
declarações se tornam? - Quanto espaço em disco adicional os índices consomem?
Para qualquer um desses testes, considere repeti-los com tamanhos de banco de dados variados. Por exemplo. execute-os em um banco de dados vazio e também em um banco de dados que já contém milhares (ou milhões) de entradas. Você também deve executar os testes em diferentes dispositivos e sistemas operacionais, especialmente se seu ambiente de desenvolvimento e produção for substancialmente diferente.
Ajuste o tamanho do cache
SQLite armazena informações temporárias em um cache (na RAM), e. ao construir os resultados de um
SELECT
consulta ou ao manipular dados que ainda não foram confirmados. Por padrão, esse tamanho é de apenas 2 MB . As máquinas desktop modernas podem poupar muito mais. Execute PRAGMA cache_size = -kibibytes
para aumentar esse valor (cuidado com o menos assine na frente do valor!). Veja aqui para mais informações. Novamente, meça que impacto esta configuração tem no desempenho! Use REPLACE INTO para criar ou atualizar uma linha
Isso pode não ser tanto um ajuste de desempenho, pois é um pequeno truque. Suponha que você precise atualizar uma linha na tabela
t
, ou criar uma linha se ela ainda não existir. Em vez de usar duas consultas (SELECT
seguido por INSERT
ou UPDATE
), use o comando REPLACE INTO
(documentos oficiais). Para que isso funcione, adicione uma coluna fictícia adicional (por exemplo,
replacer
) para a tabela t
, que tem um UNIQUE
restringir. A declaração da coluna pode, por exemplo, seja ... replacer INTEGER UNIQUE ...
que faz parte do seu CREATE TABLE
demonstração. Em seguida, use uma consulta como
REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)
Code language: SQL (Structured Query Language) (sql)
Quando esta consulta for executada pela primeira vez, ela simplesmente executará um
INSERT
. Quando é executado pela segunda vez, o UNIQUE
restrição do replacer
será acionada e o comportamento de resolução de conflitos fará com que a linha antiga seja descartada, criando uma nova automaticamente. Você também pode achar útil o comando UPSERT relacionado. Conclusão
Quando o número de linhas em seu banco de dados aumenta, os ajustes de desempenho se tornam uma necessidade. Os índices são a solução mais comum. Eles trocam a complexidade de tempo aprimorada por complexidade de espaço reduzida, melhorando as velocidades de leitura, enquanto afetam negativamente o desempenho da modificação de dados. A que demonstrei, você precisa ter muito cuidado ao comparar desigualdade em
SELECT
instruções, porque o SQLite não pode usar índices para esse tipo de comparação. Eu geralmente recomendo usar o planejador de consultas que explica o que acontece internamente para cada consulta SQL. Sempre que você ajustar algo, meça o impacto!