O que é, na prática, um índice
Um índice no SQLite é uma estrutura de dados separada — uma B-tree ordenada — que permite ao SQLite localizar linhas pelo valor de uma coluna sem precisar varrer a tabela inteira. Sem índice, uma consulta como WHERE email = 'rosa@example.com' lê linha por linha e testa cada uma. Com um índice em email, o SQLite percorre a árvore em aproximadamente log(n) passos e vai direto ao registro.
Mas essa velocidade tem preço. O índice guarda uma cópia da coluna indexada mais um ponteiro de volta para a linha. Toda vez que você faz um INSERT, um UPDATE na coluna indexada ou um DELETE, o índice também precisa ser atualizado. O uso de disco cresce e a vazão de escrita cai um pouquinho. O acordo é esse: você paga nas escritas e economiza bem mais nas leituras.
Como criar um índice no SQLite
A sintaxe básica para create index no SQLite é:
Convenção de nomes: a maioria dos times adota idx_<tabela>_<coluna>, assim fica óbvio para que serve cada índice. Lembre que o nome precisa ser único no banco inteiro, e não só dentro da tabela — por isso o nome da tabela entra no padrão.
Para remover um índice:
DROP INDEX idx_users_email;
Índices são puro andaime de performance. Remover um nunca afeta seus dados — só muda a velocidade com que as consultas rodam.
Índice único no SQLite
Um unique index faz dois trabalhos ao mesmo tempo: acelera as buscas e garante que duas linhas nunca tenham o mesmo valor na coluna indexada.
A terceira inserção falha com UNIQUE constraint failed: accounts.username. O SQLite já cria índices únicos automaticamente para colunas PRIMARY KEY e UNIQUE — você verá esses índices com nomes do tipo sqlite_autoindex_<table>_<n>. Só faz sentido escrever CREATE UNIQUE INDEX na mão quando a restrição não foi declarada direto na tabela.
O que o planejador de consultas faz na prática
Criar um índice no SQLite não significa que ele vai ser usado. O query planner escolhe uma estratégia para cada consulta, e dá pra ver qual ele escolheu com EXPLAIN QUERY PLAN:
Procure por SEARCH ... USING INDEX idx_orders_customer na saída — isso significa que o índice está sendo usado. Se aparecer SCAN orders, o planejador entendeu que um full scan na tabela seria mais barato (o que costuma fazer sentido em tabelas pequenas) ou o formato da sua consulta impediu o uso do índice. Tem um doc inteiro mais adiante só sobre como ler esses planos.
Quando o índice no SQLite não é usado
Os índices têm alguns pontos cegos bem conhecidos. Cada um dos casos abaixo faz o índice em email ser ignorado:
-- A função encapsula a coluna
SELECT * FROM users WHERE lower(email) = 'rosa@example.com';
-- Curinga no início do LIKE
SELECT * FROM users WHERE email LIKE '%@example.com';
-- Incompatibilidade de tipos força uma conversão
SELECT * FROM users WHERE email = 12345;
A B-tree é ordenada pelo valor cru de email, então qualquer coisa que transforme a coluna em tempo de consulta força um full scan. Soluções possíveis: já armazene o dado normalizado (uma coluna email_lower), use um índice de expressão (CREATE INDEX idx ON users(lower(email))), ou recorra ao full-text search do SQLite para buscas por substring.
Covering index no SQLite
Quando o índice já contém todas as colunas que a consulta precisa, o SQLite consegue responder sem nem encostar na tabela — é o famoso covering index. O pulo do gato é incluir colunas extras na definição do índice:
Como as duas colunas que a query precisa já estão dentro do índice, o SQLite retorna USING COVERING INDEX. Não precisa nem buscar a linha na tabela. O covering index no SQLite é uma das otimizações com maior retorno em rotas de leitura críticas — o preço a pagar é um índice maior em disco. Índices com múltiplas colunas são um assunto à parte; o próximo doc trata disso com calma.
Listar índices no SQLite e inspecionar a estrutura
Existem duas formas de ver o que já existe:
Isso retorna todos os índices do banco junto com a instrução CREATE que os criou. Se quiser ver só os de uma tabela, use PRAGMA index_list('products');. E para descobrir quais colunas cada índice cobre, vai de PRAGMA index_info('idx_products_name');. Qualquer coisa que comece com sqlite_autoindex_ foi criada automaticamente por causa de uma PRIMARY KEY ou de uma restrição UNIQUE — esses não dá para remover.
Quando não usar índice no SQLite
Tem situações em que criar um índice só piora as coisas:
- Tabelas pequenas. Algumas centenas de linhas são varridas em microssegundos. O otimizador provavelmente vai ignorar o índice de qualquer jeito, e você só adicionou custo extra nas escritas à toa.
- Colunas com muita escrita e pouca consulta. Toda escrita atualiza todos os índices. Indexar uma coluna pela qual você quase nunca filtra é puro desperdício.
- Colunas de baixa cardinalidade isoladas. Um índice numa coluna
statuscom três valores possíveis não filtra quase nada. Ele até pode ajudar como segunda coluna de um índice composto, ou como índice parcial — mas sozinho, geralmente não compensa. - Já tem cobertura. Se você já tem um índice em
(a, b), não precisa de outro só em(a). O SQLite aproveita as colunas iniciais de um índice composto quando a query filtra apenas pora.
A resposta honesta para "será que devo criar esse índice?" quase sempre é: cria, roda EXPLAIN QUERY PLAN, mede com dados realistas e decide.
A seguir: índices compostos
Um índice de coluna única já resolve bastante coisa, mas na prática as queries costumam filtrar e ordenar por várias colunas ao mesmo tempo. É aí que entram os índices compostos — índices sobre (a, b, c) —, e a ordem das colunas importa muito mais do que a galera imagina. É o tema da próxima página.
Perguntas frequentes
Como criar um índice no SQLite?
Use CREATE INDEX nome_do_indice ON nome_da_tabela(nome_da_coluna);. Se quiser garantir unicidade, use CREATE UNIQUE INDEX. Vale lembrar que o nome precisa ser único no banco inteiro, não só dentro da tabela. Para remover, é só rodar DROP INDEX nome_do_indice;.
Quando vale a pena criar um índice no SQLite?
Crie índices nas colunas que você costuma usar em filtros (WHERE), joins ou ordenação — principalmente quando a tabela é grande e a consulta retorna só uma fração das linhas. Não saia indexando tudo: cada índice deixa INSERT, UPDATE e DELETE mais lentos e ainda ocupa disco. Sempre confirme com EXPLAIN QUERY PLAN que o planner está realmente usando o índice.
Por que o SQLite não está usando meu índice?
Os motivos mais comuns: a tabela é pequena o suficiente para um full scan sair mais barato; a coluna está envolvida por uma função (algo como WHERE lower(email) = ... não usa o índice em email); a consulta tem OR envolvendo colunas sem índice; ou as estatísticas estão desatualizadas. Rode ANALYZE para atualizar as estatísticas e EXPLAIN QUERY PLAN para ver o que o planner escolheu.
Como listar todos os índices de uma tabela no SQLite?
Rode PRAGMA index_list('nome_da_tabela'); para ver os índices de uma tabela específica, ou consulte direto a sqlite_master: SELECT name, sql FROM sqlite_master WHERE type = 'index';. As entradas começando com sqlite_autoindex_* são índices automáticos criados pelas restrições PRIMARY KEY e UNIQUE.