Uma conexão é só um arquivo aberto
O SQLite não tem servidor. Não existe daemon escutando numa porta, host pra discar nem credenciais pra negociar. "Conectar ao SQLite" significa que o driver abre um arquivo no disco e começa a ler e gravar páginas nele. É esse o modelo mental — ponto.
Cada linguagem tem um driver que envelopa a biblioteca C do SQLite. A cara muda, mas as peças são sempre as mesmas: um caminho para o arquivo do banco, uma chamada de abertura, um handle onde você executa as instruções e uma chamada de fechamento no fim.
-- Conceitualmente, todo driver faz isto:
-- 1. Abrir ou criar o arquivo no caminho informado.
-- 2. Obter um handle.
-- 3. Executar SQL por meio de prepared statements.
-- 4. Fechar o handle.
O resto desta página mostra como isso fica no código real, além de alguns ajustes que vale a pena configurar antes da sua primeira query.
Python: sqlite3 na biblioteca padrão
O Python já vem com o módulo sqlite3 embutido — não precisa instalar nada. A estrutura básica é assim:
-- Python
import sqlite3
conn = sqlite3.connect("app.db")
conn.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)")
conn.execute("INSERT INTO notes (body) VALUES (?)", ("primeira nota",))
conn.commit()
for row in conn.execute("SELECT id, body FROM notes"):
print(row)
conn.close()
Vale a pena ter algumas coisas em mente:
sqlite3.connect("app.db")cria o arquivo caso ele não exista. Passe":memory:"se quiser um banco que vive apenas em memória.sqlite3.connect("file:app.db?mode=ro", uri=True)abre o banco em modo somente leitura usando a forma de URI.- O
?no SQL é um placeholder — sempre use parameter binding, nunca concatenação de strings. O próximo capítulo entra em detalhes nisso. - O
conn.commit()é obrigatório, a menos que você use um context manager (with conn:), que faz o commit automaticamente.
Em uma aplicação que fica rodando por muito tempo, configure um busy timeout para que escritas concorrentes esperem em vez de estourar erro:
-- Python
conn.execute("PRAGMA busy_timeout = 5000") -- aguarda até 5s
conn.execute("PRAGMA journal_mode = WAL") -- melhor concorrência
Node.js com better-sqlite3
No ecossistema Node existem algumas opções, mas a better-sqlite3 é a que a maioria dos times acaba escolhendo. Ela é síncrona — o que à primeira vista soa estranho para Node, mas na prática fica mais rápido para o SQLite, já que as queries retornam em microssegundos.
-- Node.js
const Database = require("better-sqlite3");
const db = new Database("app.db");
db.exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
const insert = db.prepare("INSERT INTO notes (body) VALUES (?)");
insert.run("primeira nota");
const rows = db.prepare("SELECT id, body FROM notes").all();
console.log(rows);
db.close();
db.prepare(...) devolve um statement reutilizável. Use .run() para escritas, .all() para retornar todas as linhas e .get() para pegar apenas uma. É o mesmo padrão que você já viu na maioria dos drivers SQL.
Configure as pragmas logo na inicialização:
-- Node.js
db.pragma("journal_mode = WAL");
db.pragma("busy_timeout = 5000");
db.pragma("foreign_keys = ON"); -- desativado por padrão, quase sempre desejado
foreign_keys = ON merece destaque: o SQLite não aplica chaves estrangeiras a menos que você peça explicitamente, e isso vale por conexão. Se esquecer, suas cláusulas REFERENCES viram pura decoração.
Go: database/sql com um driver para SQLite
O pacote padrão database/sql do Go é agnóstico em relação ao driver. Para SQLite, as escolhas mais comuns são o modernc.org/sqlite (Go puro, sem CGO) e o github.com/mattn/go-sqlite3 (com CGO).
-- Go
import (
"database/sql"
_ "modernc.org/sqlite"
)
db, err := sql.Open("sqlite", "app.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)")
if err != nil { panic(err) }
defer db.Close()
_, err = db.Exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)")
_, err = db.Exec("INSERT INTO notes (body) VALUES (?)", "primeira nota")
rows, _ := db.Query("SELECT id, body FROM notes")
defer rows.Close()
for rows.Next() {
var id int; var body string
rows.Scan(&id, &body)
fmt.Println(id, body)
}
A query string depois do nome do arquivo é como esse driver passa pragmas no momento da conexão — o formato muda de driver pra driver, então confira a documentação do que você escolher.
O sql.Open não abre conexão de fato; quem abre é a primeira query. O db é um pool de conexões. No caso do SQLite, um pool pequeno (ou até db.SetMaxOpenConns(1) em cargas com muita escrita) costuma ser a melhor escolha.
Java: SQLite com JDBC
O driver org.xerial:sqlite-jdbc é o padrão da comunidade. As URLs JDBC seguem o formato jdbc:sqlite:<caminho>:
-- Java
import java.sql.*;
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:app.db")) {
try (Statement st = conn.createStatement()) {
st.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
st.execute("PRAGMA journal_mode = WAL");
st.execute("PRAGMA busy_timeout = 5000");
}
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO notes (body) VALUES (?)")) {
ps.setString(1, "primeira nota");
ps.executeUpdate();
}
try (PreparedStatement ps = conn.prepareStatement("SELECT id, body FROM notes");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
}
Em memória: jdbc:sqlite::memory:. Somente leitura: adicione ?open_mode=1 ou utilize um objeto SQLiteConfig.
PHP: conectando no SQLite com PDO
No PDO, a DSN do SQLite segue o formato sqlite:<caminho>:
-- PHP
$db = new PDO("sqlite:app.db");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
$db->exec("PRAGMA journal_mode = WAL");
$db->exec("PRAGMA busy_timeout = 5000");
$stmt = $db->prepare("INSERT INTO notes (body) VALUES (?)");
$stmt->execute(["primeira nota"]);
foreach ($db->query("SELECT id, body FROM notes") as $row) {
echo $row["id"] . " " . $row["body"] . "\n";
}
sqlite::memory: para um banco em memória. Sempre defina ATTR_ERRMODE para lançar exceções — falhas silenciosas são um inferno de depurar.
String de conexão e caminhos de arquivo
Independentemente do driver, você vai topar com dois sabores de "string de conexão":
- Caminho simples:
app.db,./data/app.db,/var/lib/myapp/app.db. Caminhos relativos são resolvidos a partir do diretório de trabalho do processo — o que raramente é o que você quer em produção. Use caminhos absolutos. - Formato URI:
file:app.db?mode=rwc&cache=shared. Permite passar flags comomode=ro(somente leitura),mode=rwc(leitura, escrita e criação, o padrão),cache=sharedenolock=1.
Alguns valores especiais que vão aparecer:
:memory:— um banco em memória privado. Cada conexão ganha o seu.file::memory:?cache=shared— um banco em memória que várias conexões dentro do mesmo processo conseguem compartilhar.""(string vazia) — um banco temporário em disco, privado, que é apagado quando você fecha.
O JDBC prefixa a URI com jdbc:sqlite:. O PDO usa sqlite:. Os drivers de Go e o sqlite3 do Python aceitam tanto o caminho quanto a URI direto.
E os pools de conexão?
O SQLite tem um único escritor. A qualquer momento, exatamente uma conexão segura o lock de escrita; todas as outras ficam na fila. Manter um pool cheio de escritores não acelera as escritas — só aumenta o número de candidatos disputando o mesmo lock.
Dito isso, um pool pequeno ajuda em alguns cenários:
- Leituras concorrentes no modo WAL, onde leitores não bloqueiam uns aos outros nem o escritor.
- Evitar gargalo de fila quando uma consulta lenta trava o app inteiro.
Padrões razoáveis para uma aplicação web:
- Modo WAL ativado.
busy_timeoutde alguns segundos, para que a contenção espere com paciência em vez de estourar erro.- Tamanho de pool de 1 escritor + N leitores, ou apenas uma conexão compartilhada se o tráfego for baixo.
- Foreign keys ligadas em toda conexão.
-- Aplique estes em cada nova conexão:
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
PRAGMA synchronous = NORMAL; -- seguro com WAL; mais rápido que FULL
synchronous = NORMAL é o par clássico do WAL — sobrevive a crash da aplicação, fica um pouco mais frouxo em caso de crash do sistema operacional, e é bem mais rápido que o FULL padrão.
Fechando conexões (e por que isso importa)
Todo driver tem sua chamada de fechamento: conn.close(), db.Close(), db.close(). Esquecer de fechar vaza file descriptors e pode deixar o arquivo WAL crescendo sem parar.
Em serviços que rodam por muito tempo, o padrão mais comum é uma conexão (ou pool) durante todo o ciclo de vida do processo, e não abrir e fechar a cada request. Abrir uma conexão SQLite é barato, mas reaplicar os pragmas toda vez é desperdício — e fácil de esquecer.
-- Python — conexão por processo, reutilizada entre requisições
DB = sqlite3.connect("app.db", check_same_thread=False)
DB.execute("PRAGMA journal_mode = WAL")
DB.execute("PRAGMA busy_timeout = 5000")
DB.execute("PRAGMA foreign_keys = ON")
No caso específico do Python, você precisa de check_same_thread=False se for usar a mesma conexão em várias threads — e aí vale a pena ter um lock ou um pool pra serializar as chamadas.
Checklist antes de subir pra produção
Antes de jogar tráfego real em cima de um banco SQLite:
- Use caminho absoluto para o arquivo do banco.
- Ative o modo WAL (
PRAGMA journal_mode = WAL). - Defina um
busy_timeoutentre 2 e 10 segundos. - Ligue as foreign keys em toda conexão.
- Use prepared statements com parâmetros — nunca interpolação de string.
- Garanta que o diretório do banco tenha permissão de escrita para o processo (no modo WAL, o SQLite cria os arquivos
-wale-shmao lado do arquivo principal). - Pense em backup antes de precisar —
VACUUM INTOe o comando.backupaparecem mais à frente.
Próximo passo: migrations
Conectar é a parte fácil. Difícil mesmo é evoluir o schema com o tempo sem ficar editando o banco de produção na mão. Migrations são justamente o jeito de transformar ALTER TABLE num processo repetível e versionado — é o assunto da próxima página.
Perguntas frequentes
Como faço para conectar ao SQLite pelo código?
Basta apontar o driver para o caminho do arquivo. Em Python fica sqlite3.connect('app.db'); no Node, new Database('app.db') usando better-sqlite3; em Go, sql.Open("sqlite", "app.db"). Como o SQLite não tem servidor, a 'conexão' é só abrir um arquivo — se ele não existe, o próprio SQLite cria.
Como é a string de conexão do SQLite?
A maioria dos drivers aceita tanto um caminho de arquivo simples (./data/app.db) quanto o formato URI (file:app.db?mode=rwc&cache=shared). A forma URI permite definir flags como modo somente leitura, cache compartilhado ou banco :memory:. Já o JDBC usa jdbc:sqlite:app.db, e o PDO usa sqlite:app.db.
Preciso de pool de conexões com SQLite?
Geralmente não, pelo menos não como no Postgres ou MySQL. O SQLite serializa as escritas no nível do banco, então um pool de escritores não acelera nada. Um pool pequeno ajuda em leituras concorrentes, principalmente no modo WAL. Muitas aplicações funcionam bem com uma única conexão compartilhada, somada a PRAGMA journal_mode=WAL e um busy_timeout razoável.
Como evitar o erro 'database is locked'?
Configure um busy timeout para que o driver espere em vez de falhar na hora: PRAGMA busy_timeout = 5000 (em milissegundos). Ative o modo WAL com PRAGMA journal_mode=WAL, assim leitores não bloqueiam escritores. Mantenha as transações curtas e nunca segure uma transação de escrita aberta enquanto faz tarefas lentas fora do banco.