Toda tabela tem uma coluna secreta
Quando você cria uma tabela comum no SQLite, já vem de brinde uma coluna que você nem chegou a declarar:
Esse tal de rowid existe de verdade. O SQLite atribui um para cada linha de toda tabela comum, queira você ou não. É um inteiro de 64 bits com sinal, único dentro da tabela, e é justamente a chave que o SQLite usa internamente para localizar as linhas na sua estrutura B-tree. Pense nele como a espinha dorsal da tabela — o índice que mantém tudo o mais organizado.
Você normalmente não vê esse campo porque o SELECT * não o inclui. Para enxergá-lo, é preciso pedir explicitamente pelo nome.
O ROWID tem três apelidos no SQLite
Como o rowid aparece bastante em SQL escrito para outros bancos, o SQLite aceita três nomes diferentes para essa mesma coluna:
rowid, oid e _rowid_ apontam todos para a mesma coluna oculta. Se você declarar uma coluna de verdade com algum desses nomes, ela toma o lugar do alias e você perde o atalho — mas é só isso de pegadinha. No dia a dia, use rowid e pronto.
INTEGER PRIMARY KEY é a fórmula mágica
Aqui está o detalhe que pega de jeito quem vem de outros bancos. Quando você declara uma coluna exatamente como INTEGER PRIMARY KEY, ela não é armazenada à parte — ela vira o próprio rowid:
rowid e id são, na prática, a mesma coluna com dois nomes diferentes. Quando você faz um INSERT sem informar o id, o SQLite escolhe automaticamente um inteiro (geralmente o maior rowid atual + 1). É por isso que INTEGER PRIMARY KEY é a forma mais eficiente de criar uma chave primária autoincrementável no SQLite — não há coluna extra nem índice extra, apenas o próprio rowid.
Mas atenção: a grafia exata faz diferença. INT PRIMARY KEY não é a mesma coisa — INT e INTEGER têm comportamentos diferentes aqui:
Na tabela a, id e rowid apontam para o mesmo valor. Já na tabela b, id é só uma coluna comum e o rowid continua sendo aquele inteiro oculto, separado. Pior ainda: b.id não é preenchido automaticamente no insert — fica NULL até você atribuir um valor manualmente. Por isso, use sempre INTEGER PRIMARY KEY (escrito por extenso, sem abreviar) quando quiser o comportamento de alias do rowid.
Como obter o ROWID após um INSERT
Depois de um INSERT, é muito comum precisar do rowid que acabou de ser gerado — geralmente para ligar uma linha filha a esse registro. O SQLite oferece a função last_insert_rowid() justamente para isso:
A função devolve o rowid do último insert bem-sucedido na conexão atual. Boa parte dos drivers de banco expõe esse mesmo valor como cursor.lastrowid ou algo parecido. A cláusula RETURNING (que veremos mais adiante) é outra forma de recuperá-lo já no próprio insert.
O ROWID não é permanente
O rowid de uma linha é estável enquanto ela existir, mas não serve como identificador vitalício. Um VACUUM pode renumerar os rowids, e se você apagar uma linha aquele número pode acabar sendo reaproveitado em um insert futuro:
Repare que a nova linha pode ou não reaproveitar o rowid antigo, dependendo da versão e da situação — o ponto é que você não pode contar com ele permanecer único para sempre. Se você precisa de um identificador que sobreviva a deleções, vacuums e exportações, declare sua própria coluna INTEGER PRIMARY KEY (que fixa o valor naquela linha) e considere a palavra-chave AUTOINCREMENT se você precisar especificamente de valores estritamente crescentes que nunca sejam reutilizados.
Tabelas WITHOUT ROWID no SQLite
Às vezes o rowid é um custo extra que você não quer pagar — geralmente quando sua chave de verdade não é um inteiro. Uma tabela de cidades indexada pelo nome, por exemplo, acaba com duas estruturas: a B-tree do rowid e um índice separado sobre name para garantir a chave primária. O WITHOUT ROWID funde as duas em uma só:
Agora a chave de armazenamento real passa a ser name. As buscas por name pulam um nível de indireção e a tabela ocupa menos espaço. Em troca, você abre mão de algumas coisas:
- Não existem
rowid,oidnem_rowid_— essas colunas simplesmente somem. last_insert_rowid()não é atualizado em inserções nessa tabela.- I/O incremental de BLOB e alguns recursos de replicação ficam indisponíveis.
- A tabela precisa ter um
PRIMARY KEYdeclarado.
WITHOUT ROWID é uma otimização pontual, não o padrão. Use quando a chave primária não for inteira e a tabela for grande ou tiver muita escrita. Para tabelas comuns com chave inteira, o layout padrão com rowid já é o ideal.
Modelo mental
Resumindo o essencial:
- Toda tabela comum no SQLite tem uma chave inteira oculta de 64 bits chamada
rowid. INTEGER PRIMARY KEY(exatamente assim) transforma sua coluna num apelido para orowid.- Use
last_insert_rowid()para ler o valor que acabou de ser atribuído. - Rowids podem ser reaproveitados depois de deleções e renumerados pelo
VACUUM. - Tabelas
WITHOUT ROWIDdescartam a chave oculta e usam diretamente a chave primária que você declarou — útil para chaves não inteiras, mas você abre mão de alguns recursos.
Na maior parte do tempo, você nem pensa no rowid. Declara id INTEGER PRIMARY KEY, deixa o SQLite cuidar da numeração e segue a vida. Os detalhes importam quando você está ajustando armazenamento, lendo esquemas já existentes, ou tentando entender por que INT PRIMARY KEY se comporta diferente de INTEGER PRIMARY KEY.
A seguir: NOT NULL e DEFAULT
Agora que a identidade da linha está resolvida, a próxima camada é garantir que as outras colunas guardem valores que façam sentido. As cláusulas NOT NULL e DEFAULT fazem o grosso desse trabalho — e são o tema do próximo capítulo.
Perguntas frequentes
O que é o ROWID no SQLite?
Toda tabela comum no SQLite tem uma coluna oculta chamada rowid, um inteiro de 64 bits com sinal que identifica cada linha de forma única. Internamente, o SQLite usa esse valor como chave de fato na B-tree de armazenamento. Mesmo sem você ter declarado nada, dá para consultar com SELECT rowid, * FROM t.
Qual a diferença entre ROWID e PRIMARY KEY no SQLite?
O rowid está sempre ali, sem você pedir; já a primary key é algo que você declara. O caso especial é o INTEGER PRIMARY KEY: essa coluna vira um apelido (alias) do rowid, e não uma coluna separada. Qualquer outra forma de chave primária — texto, composta, ou até INT PRIMARY KEY sem o INTEGER completo — fica armazenada ao lado do rowid, não como ele.
Para que serve o WITHOUT ROWID no SQLite?
O WITHOUT ROWID diz ao SQLite para ignorar o rowid oculto e usar a PRIMARY KEY que você declarou como chave real de armazenamento. Isso pode economizar espaço e acelerar buscas em tabelas com chaves não inteiras, mas tem custo: você perde recursos como last_insert_rowid() e o I/O incremental de BLOB. Use com critério, não como padrão.