Quando um plano de execução inclui uma varredura de uma estrutura de índice b-tree, o mecanismo de armazenamento pode poder escolher entre duas estratégias de acesso físico quando o plano for executado:
- Siga a estrutura do índice b-tree; ou,
- localize páginas usando informações internas de alocação de página.
Onde uma escolha está disponível, o mecanismo de armazenamento toma a decisão de tempo de execução em cada execução. Uma recompilação de plano não necessário para que ele mude de ideia.
A estratégia b-tree começa na raiz da árvore, desce até uma borda extrema do nível da folha (dependendo se a varredura é para frente ou para trás), então segue os links da página no nível da folha até que a outra extremidade do índice seja alcançada . A estratégia de alocação usa estruturas Index Allocation Map (IAM) para localizar as páginas do banco de dados alocadas ao índice. Cada página do IAM mapeia alocações para um intervalo de 4 GB em um único arquivo de banco de dados físico, portanto, a varredura das cadeias do IAM associadas a um índice tende a acessar as páginas de índice na ordem do arquivo físico (pelo menos até onde o SQL Server pode dizer).
As principais diferenças entre as duas estratégias são:
- Uma varredura de b-tree pode entregar linhas ao processador de consulta em ordem de chave de índice; uma varredura orientada por IAM não pode;
- uma varredura de b-tree pode não ser capaz de emitir grandes solicitações de E/S de leitura antecipada se as páginas de índice logicamente contíguas também não forem fisicamente contíguas (por exemplo, como resultado da divisão de página no índice).
Uma varredura de b-tree está sempre disponível para um índice. As condições frequentemente citadas para que as varreduras de ordem de alocação estejam disponíveis são:
- O plano de consulta deve permitir uma varredura não ordenada do índice;
- o índice deve ter pelo menos 64 páginas; e,
- ou um
TABLOCK
ouNOLOCK
dica deve ser especificada.
A primeira condição significa simplesmente que o otimizador de consulta deve ter marcado a varredura com o
Ordered:False
propriedade. Marcando a varredura Ordered:False
significa que os resultados corretos do plano de execução não exigem a varredura para retornar linhas em ordem de chave de índice (embora possa fazê-lo se for conveniente ou necessário). A segunda condição (tamanho) se aplica apenas ao SQL Server 2005 e posterior. Isso reflete o fato de que há um certo custo inicial para realizar uma verificação orientada pelo IAM, portanto, é necessário haver um número mínimo de páginas para que a economia potencial pague o investimento inicial. As “64 páginas” referem-se ao valor de data_pages para o
IN_ROW_DATA
unidade de alocação somente, conforme relatado em sys.allocation_units. Claro, só pode haver uma recompensa de uma varredura de ordem de alocação se as considerações de leitura antecipada possivelmente maiores realmente entram em jogo, mas o SQL Server atualmente não considera esse fator. Em particular, ele não leva em conta quanto do índice está atualmente na memória, nem se importa com o quão fragmentado o índice está.
A terceira condição é provavelmente a descrição menos completa da lista. Na verdade, as dicas não são necessárias , embora possam ser usados para atender aos requisitos reais:os dados devem ser garantidos para não serem alterados durante a verificação, ou (mais controversamente) devemos indicar que não nos importamos sobre resultados potencialmente imprecisos, executando a verificação no nível de isolamento de leitura não confirmada.
Mesmo com esses esclarecimentos, a lista de condições para uma varredura ordenada por alocação ainda não está completa. Há uma série de advertências e exceções importantes, às quais abordaremos em breve.
Demonstração
A consulta a seguir usa o banco de dados de exemplo AdventureWorks:
CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P;
Observe que a tabela Person contém 3.869 páginas. O plano pós-execução (real) é o seguinte (mostrado no SQL Sentry Plan Explorer):
Em termos dos requisitos de varredura de ordem de alocação, temos até agora:
- O plano tem o
Ordered:False
necessário propriedade; e, - a tabela tem mais de 64 páginas; mas,
- não fizemos nada para garantir que os dados não sejam alterados durante a verificação. Assumindo que nossa sessão está usando o padrão leitura confirmada nível de isolamento, a verificação não está sendo executada no ler não confirmado nível de isolamento também.
Como consequência, esperaríamos que essa varredura fosse executada varrendo a b-tree em vez de ser orientada pelo IAM. Os resultados da consulta indicam que isso provavelmente é verdade:
As linhas são retornadas em ordem de chave de índice clusterizado (por
BusinessEntityID
). Devo declarar claramente que essa ordenação de resultados não é absolutamente garantida , e não deve ser invocado. Os resultados ordenados só são garantidos por um ORDER BY
de nível superior apropriado cláusula. No entanto, a ordem de saída observada é uma evidência circunstancial de que a varredura foi realizada desta vez seguindo a estrutura de árvore b do índice clusterizado. Se mais evidências forem necessárias, podemos anexar um depurador e observar o caminho do código que o SQL Server está executando durante a verificação:
A pilha de chamadas mostra claramente a varredura seguindo a b-tree.
Adicionando uma dica de bloqueio de tabela
Agora modificamos a consulta para incluir uma dica de bloqueio de tabela:
CHECKPOINT; DBCC DROPCLEANBUFFERS; SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK);
No nível de isolamento de leitura confirmada de bloqueio padrão, o bloqueio em nível de tabela compartilhada evita possíveis modificações simultâneas nos dados. Com todas as três pré-condições para verificações orientadas por IAM atendidas, agora esperaríamos que o SQL Server usasse uma verificação de ordem de alocação. O plano de execução é o mesmo de antes, então não vou repeti-lo, mas os resultados da consulta certamente parecem diferentes:
Os resultados ainda estão aparentemente ordenados por
BusinessEntityID
, mas o ponto de partida (10866) é diferente. De fato, se rolarmos para baixo os resultados, logo encontraremos seções que estão mais obviamente fora da ordem de chave:A ordenação parcial se deve à varredura da ordem de alocação processando uma página de índice inteira de cada vez. Os resultados dentro de uma página acontece de ser retornado ordenado pela chave de índice, mas a ordem das páginas digitalizadas agora é diferente. Novamente, devo enfatizar que os resultados podem parecer diferentes para você:não há garantia de ordem de saída, mesmo dentro de uma página, sem um
ORDER BY
de nível superior na consulta original. Para comparação com a pilha de chamadas mostrada anteriormente, este é um rastreamento de pilha obtido enquanto o SQL Server estava processando a consulta com o
TABLOCK
dica:Avançando um pouco mais na execução:
Claramente, o SQL Server está executando uma verificação ordenada por alocação quando o bloqueio de tabela é especificado. É uma pena que não haja indicação em um plano de pós-execução de qual tipo de varredura foi usada em tempo de execução. Como lembrete, o tipo de varredura é escolhido pelo mecanismo de armazenamento e pode ser alterado entre as execuções sem uma recompilação do plano.
Outras maneiras de atender à terceira condição
Eu disse antes que para obter uma verificação orientada pelo IAM, precisamos garantir que os dados não possam ser alterados sob a verificação enquanto ela estiver em andamento ou precisamos executar a consulta no nível de isolamento de leitura não confirmada. Vimos que uma dica de bloqueio de tabela no bloqueio de isolamento de confirmação de leitura é suficiente para atender ao primeiro desses requisitos, e é fácil mostrar que usar um
NOLOCK/READUNCOMMITTED
A dica também permite uma verificação de ordem de alocação com a consulta de demonstração. Na verdade, existem muitas maneiras de atender à terceira condição, incluindo:
- Alterar o índice para permitir apenas bloqueios de tabela;
- tornar o banco de dados somente leitura (para que os dados não sejam alterados); ou,
- alterando a sessão nível de isolamento para
READ UNCOMMITTED
.
Existem, no entanto, variações muito mais interessantes sobre este tema que significam que precisamos alterar as três condições indicadas anteriormente…
Níveis de isolamento de controle de versão de linha
Habilite o isolamento de instantâneo confirmado de leitura (RCSI) no banco de dados AdventureWorks e execute o teste com o
TABLOCK
dica novamente (no isolamento confirmado de leitura):ALTER DATABASE AdventureWorks2012 SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK); GO ALTER DATABASE AdventureWorks2012 SET READ_COMMITTED_SNAPSHOT OFF WITH ROLLBACK IMMEDIATE;
Com o RCSI ativo, um ordenado por índice scan é usado com
TABLOCK
, não a varredura de ordem de alocação que vimos antes. O motivo é o TABLOCK
dica especifica um bloqueio compartilhado em nível de tabela, mas com RCSI ativado, sem bloqueios compartilhados são tomadas. Sem o bloqueio de tabela compartilhada, não atendemos ao requisito de evitar modificações simultâneas nos dados enquanto a varredura está em andamento, portanto, uma varredura ordenada por alocação não pode ser usada. No entanto, é possível obter uma varredura ordenada por alocação quando o RCSI está ativado. Uma maneira é usar um
TABLOCKX
dica (para um nível de tabela exclusivo lock) em vez de TABLOCK
. Também poderíamos manter o TABLOCK
dica e adicione outro como READCOMMITTEDLOCK
, ou REPEATABLE READ
ou SERIALIZABLE
… e assim por diante. Tudo isso funciona impedindo a possibilidade de modificações simultâneas ao usar um bloqueio de tabela compartilhado, ao custo de perder os benefícios do RCSI . Também podemos obter uma varredura de ordem de alocação usando um NOLOCK
ou READUNCOMMITTED
dica, claro. A situação sob o isolamento de instantâneo (SI) é muito semelhante ao RCSI e não foi explorada em detalhes por motivos de espaço.
TABLESAMPLE sempre* executa uma varredura de ordem de alocação
O
TABLESAMPLE
cláusula é uma exceção interessante para muitas das coisas que discutimos até agora. Especificando um
TABLESAMPLE
cláusula sempre* resulta em uma varredura de ordem de alocação, mesmo sob RCSI ou SI, e mesmo sem dicas. Para ser claro, a varredura de ordem de alocação que resulta do uso de TABLESAMPLE
mantém a semântica RCSI/SI – a varredura usa versões de linha e a leitura não bloqueia a gravação (e vice-versa). Uma segunda surpresa é que
TABLESAMPLE
sempre* executa uma verificação orientada pelo IAM mesmo que a tabela tenha menos de 64 páginas . Isso faz algum sentido porque a documentação pelo menos sugere que o SYSTEM
O método de amostragem usa a estrutura do IAM (portanto, não há escolha a não ser fazer uma varredura de ordem de alocação):
SYSTEM É um método de amostragem dependente da implementação especificado pelos padrões ISO. No SQL Server, esse é o único método de amostragem disponível e é aplicado por padrão. SYSTEM aplica um método de amostragem baseado em página no qual um conjunto aleatório de páginas da tabela é escolhido para a amostra e todas as linhas dessas páginas são retornadas como o subconjunto de amostra.
* Ocorre uma exceção se o
ROWS
ou PERCENT
especificação no TABLESAMPLE
cláusula funciona para significar 100% da tabela. Especificando mais ROWS
do que os metadados indicam que estão atualmente na tabela também não funcionará. Usando TABLESAMPLE SYSTEM (100 PERCENT)
ou equivalente não forçar uma varredura de ordem de alocação. CHECKPOINT; DBCC DROPCLEANBUFFERS; GO SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P TABLESAMPLE SYSTEM (50 ROWS) REPEATABLE (12345678) --WITH (TABLOCK);
Resultados:
O efeito de TOP e SET ROWCOUNT
Em suma, nenhum deles tem qualquer efeito sobre a decisão de usar ou não uma varredura de ordem de alocação. Isso pode parecer surpreendente nos casos em que é "óbvio" que menos de 64 páginas serão digitalizadas.
Por exemplo, as consultas a seguir usam uma verificação orientada pelo IAM para retornar 5 linhas de uma verificação:
SELECT TOP (5) P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK) SET ROWCOUNT 5; SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (TABLOCK) SET ROWCOUNT 0;
Os resultados são os mesmos para ambos:
Isso significa que
TOP
e SET ROWCOUNT
consultas podem incorrer na sobrecarga de configurar uma varredura de ordem de alocação, mesmo que menos de 64 páginas sejam digitalizadas. Na mitigação, consultas TOP mais complexas com predicados seletivos inseridos na varredura ainda podem se beneficiar de uma varredura de ordem de alocação. Se a varredura precisar processar 10.000 páginas para encontrar as 5 primeiras linhas correspondentes, uma varredura de ordem de alocação ainda poderá ser uma vitória. Evitando todas* varreduras de ordem de alocação em toda a instância
Isso não é algo que você provavelmente faria intencionalmente, mas há uma configuração de servidor que impedirá varreduras de ordem de alocação para todas as consultas de usuários em todos os bancos de dados.
Por mais improvável que pareça, a configuração em questão é a opção de configuração do servidor de limite do cursor, que possui a seguinte descrição nos Manuais Online:
A opção de limite do cursor especifica o número de linhas no conjunto de cursores nas quais os conjuntos de teclas do cursor são gerados de forma assíncrona. Quando os cursores geram um conjunto de chaves para um conjunto de resultados, o otimizador de consulta estima o número de linhas que serão retornadas para esse conjunto de resultados. Se o otimizador de consulta estimar que o número de linhas retornadas é maior que esse limite, o cursor é gerado de forma assíncrona, permitindo que o usuário busque linhas do cursor enquanto o cursor continua sendo preenchido. Caso contrário, o cursor é gerado de forma síncrona e a consulta aguarda até que todas as linhas sejam retornadas.
Se o
cursor threshold
estiver definida como diferente de –1 (o padrão), nenhuma verificação de ordem de alocação ocorrerá para consultas de usuário em qualquer banco de dados na instância do SQL Server. Em outras palavras, se a população de cursor assíncrona estiver habilitada, nenhuma varredura orientada pelo IAM para você.
* A exceção é (não-100%)
TABLESAMPLE
consultas. As consultas internas geradas pelo sistema para criação de estatísticas e atualizações de estatísticas também continuam a usar varreduras ordenadas por alocação. CHECKPOINT; DBCC DROPCLEANBUFFERS; GO -- WARNING! Disables allocation-order scans instance-wide EXECUTE sys.sp_configure @configname = 'cursor threshold', @configvalue = 5000; RECONFIGURE WITH OVERRIDE; GO -- Would normally result in an allocation-order scan SELECT P.BusinessEntityID, P.PersonType FROM Person.Person AS P WITH (READUNCOMMITTED); GO -- Reset to default allocation-order scans EXECUTE sys.sp_configure @configname = 'cursor threshold', @configvalue = -1; RECONFIGURE WITH OVERRIDE;
Resultados (sem varredura de ordem de alocação):
Pode-se apenas adivinhar que a população de cursor assíncrona não funciona bem com varreduras de ordem de alocação por algum motivo. É totalmente inesperado que essa restrição afete todas as consultas de usuários sem cursor também embora. Talvez seja muito difícil para o SQL Server detectar se uma consulta está sendo executada como parte de um cursor de API emitido externamente? Quem sabe.
Seria bom se esse efeito colateral fosse oficialmente documentado em algum lugar, embora seja difícil saber exatamente onde ele deve ir nos Books Online. Gostaria de saber quantos sistemas de produção por aí não estão usando varreduras de ordem de alocação por causa disso? Talvez não muitos, mas nunca se sabe.
Para encerrar, aqui está um resumo. Uma varredura ordenada por alocação está disponível se:
- A opção do servidor
cursor threshold
é definido como –1 (o padrão); e, - o operador de verificação do plano de consulta tem o
Ordered:False
propriedade; e, - o total de páginas de dados do
IN_ROW_DATA
unidades de alocação é de pelo menos 64; e, - ou:
- O SQL Server tem uma garantia aceitável de que modificações simultâneas são impossíveis; ou,
- a verificação está sendo executada no nível de isolamento de leitura não confirmada.
Independentemente de todos os itens acima, uma varredura com um
TABLESAMPLE
A cláusula sempre usa varreduras ordenadas por alocação (com a única exceção técnica observada no texto principal).