Menu

ROWID no SQLite: chave oculta, alias e WITHOUT ROWID

Entenda o que é o ROWID no SQLite, quando INTEGER PRIMARY KEY vira apelido dele e por que existem tabelas WITHOUT ROWID.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

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, oid nem _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 KEY declarado.

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 o rowid.
  • 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 ROWID descartam 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR