Ordem Direta E Inversa - Ordem direta e ordem inversa - Português é Simples
Ordem direta e ordem inversa - Português é Simples

Como funciona a ordenação de varredura em índices

A maioria dos DBAs e desenvolvedores que entra em contato com ordem direta e inversa pela primeira vez vem da ideia simplista de que é só uma questão de ASC versus DESC. Na prática, o assunto é muito mais chato. O otimizador escolhe entre ler um índice na direção normal (ordem direta, do menor para o maior) ou na direção oposta (ordem inversa). Essa decisão impacta diretamente o custo de execução, principalmente quando envolve operações de materialização e buffers. Vou explicar do jeito que eu aprendi, que não foi lendo documentação de banco de dados, mas quebrando a cabeça com queries que levavam minutos e passaram a levar segundos depois de entender o que acontecia no plano de execução.

Entendendo ordem direta e inversa na prática

Quando você cria um índice em uma coluna, o banco armazena as entries nessa ordem. Ordem direta significa que o mecanismo de storage vai navegar pelas páginas do índice sequencialmente, da chave menor para a maior. Ordem inversa é o caminho de trás para frente. Parece óbvio, mas a implicação prática não é. O custo de uma ordem inversa em índices B-tree depende muito do banco. Em alguns SGBDs, a varredura reversa custa basicamente o mesmo que a direta porque o índice já mantém ponteiros doubly-linked nas folhas. Em outros, especialmente onde as folhas são listas encadeadas simplesmente ligadas, a ordem inversa pode ser significativamente mais cara, já que exige lógica adicional para navegar de trás para frente.

Um detalhe que ninguém comenta direito: quando o otimizador escolhe ordem inversa para satisfazer um ORDER BY DESC, ele também precisa lidar com o fato de que as linhas chegam na ordem oposta para as operações que vêm depois, como agregações e JOINs. Isso altera o plano como um todo. Eu tive um caso específico que ilustra bem isso. Tínhamos uma tabela de transações financeiras com cerca de 80 milhões de registros, indexada por (data_transacao DESC, id_conta). Uma query que buscava as últimas movimentações de uma conta específica dentro de uma janela de tempo estava fazendo ordem inversa completa no índice principal, varrendo milhões de linhas desnecessárias porque o filtro de data_transacao estava à esquerda da coluna id_conta no índice. A solução foi criar um índice composto invertendo a ordem das colunas: (id_conta, data_transacao DESC). A query caiu de 47 segundos para 0,3 segundos. O resto era otimizador tentando ser esperto e falhando.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Quando usar cada tipo de varredura

Orde direta é o caminho padrão. Se sua query tem ORDER BY ASC ou se o filtro se alinha naturalmente com a ordem crescente do índice, o otimizador vai para ordem direta e você não precisa pensar nisso. A exception é quando você tem ORDER BY DESC em uma coluna que compõe a seleção de linhas de forma eficiente. Aí a ordem inversa pode ser a escolha mais barata do que fazer um sort materializado após uma varredura completa. O problema é que ordenação inversa não é automaticamente melhor só porque seu ORDER BY pede DESC. Se o predicado WHERE corta a maior parte das linhas antes delas serem ordenadas, o custo de ler na ordem inversa pode ser maior do que o custo de ordenar na memória após uma varredura direta. Eu já vi planos onde o otimizador escolhia ordem inversa por puro viés de seguir o ORDER BY literal, ignorando que o selectivity do predicado tornava a abordagem inversa completamente contraprodutiva.

Uma coisa contra-intuitiva que muitos esquecem: ordem inversa em índices com muitas páginas pode causar um padrão de acesso mais fragmentado em sistemas de storage tradicionais (HDDs), porque a cabeça de leitura começa no final do disco e volta para o início. Em SSDs isso não é problema, mas ainda assim há implicações de prefetch e gestão de buffer que merecem atenção.

Pitfalls comuns e como evitá-los

O primeiro erro é assumir que criar um índice com ORDER BY na definição resolve tudo. Índices não têm "ordem de criação" que force varredura reversa — a ordem é determinada pelo plano de execução e pelos predicados da query. O que você define na criação do índice é apenas a sequência das colunas chave, e essa sequência importa porque afeta a seletividade desde a primeira coluna. O segundo erro é confiar cegamente no plano de execução gerado. Eu costumo sempre verificar o tipo de varredura (Forward vs Backward) no plano antes de considerar um índice como "otimizado". Às vezes o custo estimado é baixo, mas a varredura reversa em uma tabela com crescimento contínuo gera problemas de contenção em buffers que aparecem apenas sob carga.

Se o seu caso envolve queries com ORDER BY DESC em colunas de alta cardinalidade combinadas com filtros de faixa em outras colunas, considere a possibilidade de não usar ordem inversa e forçar um sort. Em tabelas muito grandes com crescimento linear, a ordem inversa constante pode causar fragmentação de prefetch e degradação progressiva. Nesse cenário, um índice covering que atenda à query sem depender da ordem do índice original é frequentemente a melhor saída. O limite mais importante que preciso citar: ordem direta e inversa não ajudam se o predicado não seler a primeira coluna do índice. Independentemente da direção da varredura, se você está fazendo full index scan porque o filtro não se encaixa na árvore B-tree, a direção da leitura é irrelevante para o custo. Nesse ponto, o problema é projeto de índice, não escolha de varredura.