Acesso simultâneo ao SQLite


177

O SQLite3 lida com segurança com acesso simultâneo por vários processos de leitura / gravação do mesmo banco de dados? Existem exceções de plataforma para isso?


3
Esqueci de mencionar o objetivo da recompensa : a maioria das respostas diz que está tudo bem: "O SQLite é rápido o suficiente", "O SQLite lida bem com a concorrência" etc. etc., mas, imho, não responda em detalhes / não explique claramente o que acontece se duas operações de gravação chegaria exatamente ao mesmo tempo (caso teórico muito raro). 1) Isso provocaria um erro e interromperia o programa? ou 2) A segunda operação de gravação aguardaria até a conclusão da primeira? ou 3) Uma das operações de gravação seria descartada (perda de dados!)? 4) Algo mais? Conhecer as limitações da gravação simultânea pode ser útil em muitas situações.
Basj

7
@Basj Em suma, 2) ele aguardará e tentará várias vezes (Configurável), 1) acionará um erro, SQLITE_BUSY.3) você pode registrar um retorno de chamada para manipular erros SQLITE_BUSY.
obgnaw

Respostas:


112

Se a maioria desses acessos simultâneos forem leituras (por exemplo, SELECT), o SQLite poderá lidar com eles muito bem. Mas se você começar a escrever simultaneamente, a contenção de bloqueio pode se tornar um problema. Muito dependeria da rapidez com que seu sistema de arquivos é, já que o mecanismo SQLite é extremamente rápido e possui muitas otimizações inteligentes para minimizar a contenção. Especialmente SQLite 3.

Para a maioria dos aplicativos de desktop / laptop / tablet / telefone, o SQLite é rápido o suficiente, pois não há simultaneidade suficiente. (O Firefox usa o SQLite extensivamente para favoritos, histórico etc.)

Para aplicativos de servidor, alguém disse há algum tempo que qualquer coisa menos do que 100 mil visualizações de páginas por dia poderia ser tratada perfeitamente por um banco de dados SQLite em cenários típicos (por exemplo, blogs, fóruns), e ainda não vi nenhuma evidência ao contrário. De fato, com discos e processadores modernos, 95% dos sites e serviços da Web funcionariam perfeitamente com o SQLite.

Se você deseja um acesso de leitura / gravação muito rápido, use um banco de dados SQLite na memória . A RAM é várias ordens de magnitude mais rápida que o disco.


16
O OP não pergunta sobre eficiência e velocidade, mas sobre acesso simultâneo. Servidores da Web não têm nada a ver com isso. O mesmo acontece no banco de dados de memória.
21817 Jarekczek

1
Você está certo até certo ponto, mas a eficiência / velocidade desempenha um papel. Acessos mais rápidos significam que o tempo gasto na espera de bloqueios é menor, reduzindo as desvantagens do desempenho de simultaneidade do SQLite. Particularmente, se você tiver poucas e rápidas gravações, o banco de dados não parecerá ter nenhum problema de simultaneidade para um usuário.
Caboose

1
como você gerenciaria o acesso simultâneo a um banco de dados sqlite na memória?
P-Gn

42

Sim, o SQLite lida bem com a simultaneidade, mas não é o melhor do ponto de vista do desempenho. Pelo que posso dizer, não há exceções a isso. Os detalhes estão no site da SQLite: https://www.sqlite.org/lockingv3.html

Esta declaração é interessante: "O módulo pager garante que as alterações ocorram ao mesmo tempo, que todas as alterações ocorram ou nenhuma delas, que dois ou mais processos não tentem acessar o banco de dados de maneiras incompatíveis ao mesmo tempo"


2
Aqui estão alguns comentários sobre questões em diferentes plataformas , ou seja, NFS sistemas de arquivos e do Windows (embora possa dizem respeito apenas às versões antigas do Windows ...)
Nate

1
É possível carregar um banco de dados SQLite3 na RAM para ser usado por todos os usuários em PHP? Eu estou supondo que há com ele sendo processual
Anon343224user

1
@foxyfennec .. um ponto de partida, embora o SQLite possa não ser o banco de dados ideal para este caso de uso. sqlite.org/inmemorydb.html
kingPuppy

37

Sim. Vamos descobrir por que

SQLite é transacional

Todas as alterações em uma única transação no SQLite ocorrem completamente ou não são de todo

Esse suporte a ACID, bem como a leitura / gravação simultânea, são fornecidos de duas maneiras - usando o chamado registro no diário (vamos chamá-lo de “ maneira antiga ”) ou o registro write-ahead (vamos chamá-lo de “ novo caminho ”)

Diário (maneira antiga)

Nesse modo, o SQLite usa o bloqueio DATABASE-LEVEL . Este é o ponto crucial para entender.

Isso significa que sempre que precisar ler / gravar algo, primeiro adquira um bloqueio no arquivo de banco de dados INTEIRO . Vários leitores podem coexistir e ler algo em paralelo

Durante a gravação, garante que um bloqueio exclusivo seja adquirido e nenhum outro processo seja lido / gravado simultaneamente e, portanto, as gravações são seguras.

É por isso que aqui eles estão dizendo que o SQlite implementa transações serializáveis

Problemas

Como ele precisa bloquear todo um banco de dados todas as vezes e todo mundo espera por um processo que lide com a simultaneidade de gravação, essas gravações / leituras simultâneas têm desempenho bastante baixo

Reversões / interrupções

Antes de escrever algo no arquivo de banco de dados, o SQLite primeiro salvaria o pedaço para ser alterado em um arquivo temporário. Se algo travar no meio da gravação no arquivo do banco de dados, ele pegará esse arquivo temporário e reverterá as alterações

Registro Write-Ahead ou WAL (nova maneira)

Nesse caso, todas as gravações são anexadas a um arquivo temporário ( log de gravação antecipada) ) e esse arquivo é mesclado periodicamente com o banco de dados original. Quando o SQLite está procurando algo, ele primeiro verifica esse arquivo temporário e, se nada for encontrado, continue com o arquivo principal do banco de dados.

Como resultado, os leitores não competem com os escritores e o desempenho é muito melhor em comparação com o Old Way.

Ressalvas

O SQlite depende muito da funcionalidade subjacente de bloqueio do sistema de arquivos, portanto, ele deve ser usado com cuidado, mais detalhes aqui

Também é provável que o erro de execução no banco de dados esteja bloqueado , especialmente no modo de diário, para que seu aplicativo precise ser projetado com esse erro em mente


34

Parece que ninguém mencionou o modo WAL (Write Ahead Log). Verifique se as transações estão organizadas corretamente e com o modo WAL ativado, não há necessidade de manter o banco de dados bloqueado enquanto as pessoas estão lendo coisas enquanto uma atualização está em andamento.

O único problema é que, em algum momento, o WAL precisa ser incorporado novamente no banco de dados principal e faz isso quando a última conexão com o banco de dados é fechada. Com um site muito ocupado, você pode levar alguns segundos para fechar todas as conexões, mas 100 mil acessos por dia não devem ser um problema.


Interessante, mas funciona apenas em uma única máquina, não em cenários onde o banco de dados é acessado na rede.
Bobík 5/17

Vale a pena mencionar que o tempo limite padrão para um escritor espera é 5 segundos e depois disso database is lockederro será raise pelo escritor
Mirhossein

16

Em 2019, existem duas novas opções de gravação simultânea ainda não lançadas, mas disponíveis em ramificações separadas.

"PRAGMA journal_mode = wal2"

A vantagem desse modo de diário em relação ao modo "wal" regular é que os gravadores podem continuar gravando em um arquivo wal enquanto o outro está no ponto de verificação.

COMEÇAR CONCORRENTE - link para o documento detalhado

O aprimoramento BEGIN CONCURRENT permite que vários gravadores processem transações de gravação simultaneamente, se o banco de dados estiver no modo "wal" ou "wal2", embora o sistema ainda serialize comandos COMMIT.

Quando uma transação de gravação é aberta com "BEGIN CONCURRENT", o bloqueio do banco de dados é adiado até que um COMMIT seja executado. Isso significa que qualquer número de transações iniciadas com BEGIN CONCURRENT pode prosseguir simultaneamente. O sistema usa bloqueio otimizado no nível da página para impedir que transações simultâneas conflitantes sejam confirmadas.

Juntos, eles estão presentes no begin-simulturrent-wal2 ou em um ramo próprio separado .


1
Temos alguma ideia de quando esses recursos entrarão na versão de lançamento? Eles poderiam realmente ser úteis para mim.
Peter Moore

2
Nenhuma idéia. Você poderia construir facilmente a partir dos galhos. Para .NET, eu tenho uma biblioteca com interface de baixo nível e WAL2 + begin simultâneo + FTS5: github.com/Spreads/Spreads.SQLite
VB

Oh, claro, obrigado. Estou mais me perguntando sobre estabilidade. O alto nível do SQLite quando se trata de seus lançamentos, mas não sei como seria arriscado usar uma filial no código de produção.
Peter Moore

2
Consulte este tópico github.com/Expensify/Bedrock/issues/65 e Bedrock em geral. Eles o usam na produção e empurram essas begin concurrentcoisas.
VB

13

O SQLite possui um bloqueio de leitores e gravadores no nível do banco de dados. Várias conexões (possivelmente pertencentes a processos diferentes) podem ler dados do mesmo banco de dados ao mesmo tempo, mas apenas um pode gravar no banco de dados.

O SQLite suporta um número ilimitado de leitores simultâneos, mas permitirá apenas um gravador a qualquer instante no tempo. Para muitas situações, isso não é um problema. Gravador na fila. Cada aplicativo faz seu trabalho de banco de dados rapidamente e segue em frente, e nenhum bloqueio dura mais de algumas dezenas de milissegundos. Mas existem alguns aplicativos que exigem mais simultaneidade e esses aplicativos podem precisar procurar uma solução diferente. - usos apropriados para SQLite @ SQLite.org

O bloqueio de leitores e gravadores permite o processamento de transações independentes e é implementado usando bloqueios exclusivos e compartilhados no nível do banco de dados.

Um bloqueio exclusivo deve ser obtido antes que uma conexão execute uma operação de gravação em um banco de dados. Após a obtenção do bloqueio exclusivo, as operações de leitura e gravação de outras conexões são bloqueadas até que o bloqueio seja liberado novamente.

Detalhes de implementação para o caso de gravações simultâneas

O SQLite possui uma tabela de bloqueio que ajuda a bloquear o banco de dados o mais tarde possível durante uma operação de gravação para garantir a máxima simultaneidade.

O estado inicial é DESBLOQUEADO e, nesse estado, a conexão ainda não acessou o banco de dados. Quando um processo é conectado a um banco de dados e mesmo uma transação foi iniciada com BEGIN, a conexão ainda está no estado UNLOCKED.

Após o estado UNLOCKED, o próximo estado é o estado SHARED. Para poder ler (não gravar) dados do banco de dados, a conexão deve primeiro entrar no estado SHARED, obtendo um bloqueio SHARED. Várias conexões podem obter e manter bloqueios SHARED ao mesmo tempo, para que várias conexões possam ler dados do mesmo banco de dados ao mesmo tempo. Mas enquanto apenas um bloqueio SHARED permanecer inédito, nenhuma conexão poderá concluir com êxito uma gravação no banco de dados.

Se uma conexão deseja gravar no banco de dados, ela deve primeiro obter um bloqueio RESERVADO.

Somente um único bloqueio RESERVADO pode estar ativo ao mesmo tempo, embora vários bloqueios SHARED possam coexistir com um único bloqueio RESERVADO. RESERVADO difere de PENDENTE, pois novos bloqueios compartilhados podem ser adquiridos enquanto houver um bloqueio RESERVADO. - Bloqueio de arquivo e simultaneidade no SQLite versão 3 @ SQLite.org

Depois que uma conexão obtém um bloqueio RESERVADO, ela pode começar a processar operações de modificação do banco de dados, embora essas modificações possam ser feitas apenas no buffer, em vez de serem realmente gravadas no disco. As modificações feitas no conteúdo da leitura são salvas no buffer de memória. Quando uma conexão deseja enviar uma modificação (ou transação), é necessário atualizar o bloqueio RESERVADO para um bloqueio EXCLUSIVO. Para obter a trava, você deve primeiro levantar a trava para uma trava PENDENTE.

Um bloqueio PENDING significa que o processo que mantém o bloqueio deseja gravar no banco de dados o mais rápido possível e está aguardando que todos os bloqueios SHARED atuais sejam limpos, para que ele possa obter um bloqueio EXCLUSIVO. Nenhum novo bloqueio SHARED será permitido no banco de dados se um bloqueio PENDING estiver ativo, embora os bloqueios SHARED existentes possam continuar.

É necessário um bloqueio EXCLUSIVO para gravar no arquivo do banco de dados. Somente um bloqueio EXCLUSIVO é permitido no arquivo e nenhum outro tipo de bloqueio pode coexistir com um bloqueio EXCLUSIVO. Para maximizar a simultaneidade, o SQLite trabalha para minimizar a quantidade de tempo que os bloqueios EXCLUSIVOS são mantidos. - Bloqueio de arquivo e simultaneidade no SQLite versão 3 @ SQLite.org

Então, você pode dizer que o SQLite lida com segurança com acesso simultâneo por vários processos gravando no mesmo banco de dados, simplesmente porque não o suporta! Você receberá SQLITE_BUSYou receberá SQLITE_LOCKEDo segundo gravador quando ele atingir a limitação de novas tentativas.


Obrigado. Um exemplo de código com 2 escritores seria ótimo para entender como ele funciona.
Basj

1
@Basj em suma, o sqlite tem um bloqueio de leitura e gravação no arquivo de banco de dados. É o mesmo que escrever um arquivo simultaneamente. E com o WAL, ainda não é possível gravar simultaneamente, mas o WAL pode acelerar a gravação, e a leitura e gravação podem ser simultâneas. E o WAL introduz um novo bloqueio, como WAL_READ_LOCK, WAL_WRITE_LOCK.
obgnaw 31/01

2
Você poderia usar uma Fila e ter vários threads alimentando a fila e apenas um thread gravando no banco de dados usando as instruções SQL na fila. Algo parecido com isto
Gabriel

7

Este thread é antigo, mas acho que seria bom compartilhar o resultado dos meus testes feitos no sqlite: executei 2 instâncias do programa python (processos diferentes, mesmo programa) executando instruções SELECT e UPDATE comandos sql dentro de transação com bloqueio EXCLUSIVO e tempo limite definidos como 10 segundos para obter um bloqueio, e o resultado foi frustrante. Cada instância fez no loop de 10000 etapas:

  • conecte ao db com fechamento exclusivo
  • selecione em uma linha para ler o contador
  • atualize a linha com o novo valor igual ao contador incrementado em 1
  • estreita conexão com db

Mesmo que o sqlite tenha concedido bloqueio exclusivo na transação, o número total de ciclos realmente executados não era igual a 20.000, mas menor (o número total de iterações no contador único contado para os dois processos). O programa Python quase não emitiu nenhuma exceção (apenas uma vez durante a seleção para 20 execuções). A revisão do sqlite no momento do teste foi 3.6.20 e o python v3.3 CentOS 6.5. Na minha opinião, é melhor encontrar produtos mais confiáveis ​​para esse tipo de trabalho ou restringir as gravações no sqlite para um único processo / thread único.


5
Parece que você precisa dizer algumas palavras mágicas para bloquear o python, conforme discutido aqui: stackoverflow.com/a/12848059/1048959 Isso apesar do fato de a documentação do sqlite do python levar você a acreditar que isso with coné suficiente.
Dan Stahlke
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.