Banco de dados com SQLite e sqlite3
1. Introdução ao SQLite e ao módulo sqlite3
SQLite é um banco de dados relacional embutido, leve e sem servidor, que armazena todo o banco em um único arquivo no disco. Diferente de sistemas como PostgreSQL ou MySQL, não há processo separado para gerenciar conexões — a biblioteca lê e escreve diretamente no arquivo. Isso o torna ideal para aplicações desktop, protótipos, dispositivos embarcados e cenários onde a simplicidade e a portabilidade são mais importantes que a concorrência massiva.
O módulo sqlite3 faz parte da biblioteca padrão do Python desde a versão 2.5, permitindo interagir com bancos SQLite sem instalar dependências externas. Ele segue a especificação DB-API 2.0 (PEP 249), oferecendo uma interface consistente para executar comandos SQL, gerenciar transações e manipular resultados.
Use SQLite quando:
- O volume de dados for moderado (até centenas de milhares de registros)
- A aplicação não exigir concorrência intensa de escrita
- Você precisar de um banco portátil (um único arquivo)
- O deploy simplificado for prioridade
Evite SQLite quando:
- Múltiplos usuários precisarem escrever simultaneamente
- A aplicação exigir recursos avançados como replicação ou stored procedures complexas
- O volume de dados ultrapassar dezenas de gigabytes
2. Configuração e primeiros passos
Para começar, importe o módulo e crie uma conexão com sqlite3.connect(). Se o arquivo do banco não existir, ele será criado automaticamente.
import sqlite3
# Cria (ou abre) o banco de dados
conexao = sqlite3.connect('exemplo.db')
# Cria um cursor para executar comandos SQL
cursor = conexao.cursor()
# Executa um comando simples
cursor.execute("SELECT sqlite_version();")
print(cursor.fetchone()) # Exemplo: ('3.45.1',)
# Fecha a conexão
conexao.close()
A melhor prática é usar o gerenciador de contexto with, que garante o fechamento automático da conexão, mesmo em caso de erro:
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
cursor.execute("SELECT sqlite_version();")
print(cursor.fetchone())
# A conexão é fechada automaticamente ao sair do bloco
3. Criando tabelas e definindo esquemas
SQLite suporta os tipos INTEGER, TEXT, REAL (ponto flutuante) e BLOB (dados binários). Você pode adicionar restrições como PRIMARY KEY, NOT NULL, UNIQUE e DEFAULT.
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
# Cria tabela usuarios
cursor.execute("""
CREATE TABLE IF NOT EXISTS usuarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
idade INTEGER DEFAULT 18,
data_cadastro TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
# Cria tabela pedidos
cursor.execute("""
CREATE TABLE IF NOT EXISTS pedidos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
usuario_id INTEGER NOT NULL,
produto TEXT NOT NULL,
valor REAL NOT NULL,
quantidade INTEGER DEFAULT 1,
FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
)
""")
4. Inserindo, consultando e modificando dados
Para inserir dados, use INSERT INTO com placeholders ? para evitar problemas de segurança:
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
# Inserção com placeholders
cursor.execute(
"INSERT INTO usuarios (nome, email, idade) VALUES (?, ?, ?)",
('Alice', 'alice@email.com', 30)
)
# Inserção em lote
usuarios = [
('Bob', 'bob@email.com', 25),
('Carol', 'carol@email.com', 22)
]
cursor.executemany(
"INSERT INTO usuarios (nome, email, idade) VALUES (?, ?, ?)",
usuarios
)
# Consulta com filtro e ordenação
cursor.execute(
"SELECT nome, email, idade FROM usuarios WHERE idade > ? ORDER BY nome LIMIT 10",
(20,)
)
# fetchall() retorna todos os resultados como lista de tuplas
resultados = cursor.fetchall()
for nome, email, idade in resultados:
print(f"{nome} ({idade}): {email}")
# fetchone() retorna apenas o primeiro resultado
cursor.execute("SELECT COUNT(*) FROM usuarios")
total = cursor.fetchone()[0]
print(f"Total de usuários: {total}")
Atualização e exclusão seguem o mesmo padrão:
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
# Atualiza um registro
cursor.execute(
"UPDATE usuarios SET idade = ? WHERE nome = ?",
(31, 'Alice')
)
# Exclui registros
cursor.execute(
"DELETE FROM usuarios WHERE idade < ?",
(18,)
)
5. Segurança e prevenção de SQL Injection
Nunca formate strings SQL diretamente com dados do usuário. Isso abre brecha para ataques de SQL Injection.
Código vulnerável (NÃO USE):
nome_usuario = "'; DROP TABLE usuarios; --"
cursor.execute(f"SELECT * FROM usuarios WHERE nome = '{nome_usuario}'")
# O banco seria deletado!
Código seguro com placeholders:
nome_usuario = "'; DROP TABLE usuarios; --"
cursor.execute("SELECT * FROM usuarios WHERE nome = ?", (nome_usuario,))
# O SQLite trata o valor como string literal, ignorando caracteres especiais
Use executemany() para inserir ou atualizar múltiplos registros de forma segura e eficiente:
dados = [('Produto A', 29.90, 3), ('Produto B', 49.90, 1)]
cursor.executemany(
"INSERT INTO pedidos (usuario_id, produto, valor, quantidade) VALUES (?, ?, ?, ?)",
[(1, *item) for item in dados]
)
6. Transações e controle de concorrência
Por padrão, o SQLite opera em modo autocommit: cada execute() é confirmado imediatamente. Para controle explícito, use commit() e rollback():
conexao = sqlite3.connect('exemplo.db')
cursor = conexao.cursor()
try:
cursor.execute("INSERT INTO usuarios (nome, email) VALUES (?, ?)", ('Daniel', 'daniel@email.com'))
cursor.execute("INSERT INTO usuarios (nome, email) VALUES (?, ?)", ('Eva', 'eva@email.com'))
conexao.commit() # Confirma as duas inserções
except sqlite3.Error:
conexao.rollback() # Desfaz qualquer alteração
finally:
conexao.close()
O gerenciador de contexto da conexão também gerencia transações: ao sair do bloco sem erros, faz commit(); em caso de exceção, faz rollback().
Para melhor performance em cenários de leitura intensa, ative o modo WAL (Write-Ahead Logging), que permite leituras simultâneas durante escritas:
conexao.execute("PRAGMA journal_mode=WAL;")
7. Trabalhando com funções SQL e tipos especiais
SQLite oferece funções agregadas poderosas:
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
# Agrupamento e agregação
cursor.execute("""
SELECT u.nome, COUNT(p.id) AS total_pedidos, SUM(p.valor * p.quantidade) AS total_gasto
FROM usuarios u
LEFT JOIN pedidos p ON u.id = p.usuario_id
GROUP BY u.id
HAVING total_pedidos > 0
ORDER BY total_gasto DESC
""")
for nome, total, valor in cursor.fetchall():
print(f"{nome}: {total} pedido(s), R$ {valor:.2f}")
Para trabalhar com datas, SQLite armazena como TEXT (ISO 8601), REAL (dias julianos) ou INTEGER (timestamp Unix):
cursor.execute("""
SELECT nome, data_cadastro
FROM usuarios
WHERE data_cadastro >= DATE('now', '-7 days')
""")
Você pode criar funções Python personalizadas e registrá-las no SQLite:
def nivel_usuario(idade):
if idade < 18:
return "Jovem"
elif idade < 60:
return "Adulto"
return "Sênior"
conexao.create_function("nivel", 1, nivel_usuario)
cursor.execute("SELECT nome, nivel(idade) FROM usuarios")
for nome, nivel in cursor.fetchall():
print(f"{nome}: {nivel}")
8. Migrações e manutenção do banco
SQLite suporta ALTER TABLE de forma limitada: você pode renomear tabelas ou adicionar colunas, mas não pode remover ou modificar colunas existentes.
Para alterações complexas, a estratégia recomendada é recriar a tabela:
def migrar_adicionar_telefone():
with sqlite3.connect('exemplo.db') as conexao:
cursor = conexao.cursor()
# 1. Cria nova tabela com o esquema desejado
cursor.execute("""
CREATE TABLE usuarios_nova (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
telefone TEXT,
idade INTEGER DEFAULT 18,
data_cadastro TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
# 2. Copia dados da tabela antiga
cursor.execute("""
INSERT INTO usuarios_nova (id, nome, email, idade, data_cadastro)
SELECT id, nome, email, idade, data_cadastro FROM usuarios
""")
# 3. Substitui a tabela antiga
cursor.execute("DROP TABLE usuarios")
cursor.execute("ALTER TABLE usuarios_nova RENAME TO usuarios")
Para otimizar o banco, use VACUUM para recuperar espaço não utilizado:
conexao.execute("VACUUM")
Monitore o desempenho com EXPLAIN QUERY PLAN:
cursor.execute("EXPLAIN QUERY PLAN SELECT * FROM usuarios WHERE idade > ?", (20,))
for linha in cursor.fetchall():
print(linha)
O módulo sqlite3 oferece uma solução completa e robusta para armazenamento persistente em Python, combinando simplicidade de uso com segurança contra injeção SQL e controle transacional. Para a maioria dos projetos que não exigem concorrência massiva, SQLite é a escolha ideal — e a biblioteca padrão do Python já fornece tudo que você precisa.
Referências
- Documentação oficial do módulo sqlite3 (Python) — Referência completa da API, incluindo tipos, métodos e exemplos de uso.
- SQLite Documentation — Documentação oficial do SQLite, com especificações de sintaxe, pragmas e funções.
- PEP 249 – Python Database API Specification v2.0 — Especificação da DB-API que define a interface padrão para bancos de dados em Python.
- SQLite Tutorial (SQLiteTutorial.net) — Tutoriais práticos sobre SQLite, desde conceitos básicos até tópicos avançados como índices e gatilhos.
- Real Python – Python SQLite Tutorial — Guia completo com exemplos práticos de uso do sqlite3 em Python, incluindo boas práticas e prevenção de SQL Injection.
- SQLite vs MySQL vs PostgreSQL: A Comparison — Artigo comparativo que ajuda a decidir quando usar SQLite versus bancos cliente-servidor.