Database
 sql >> Base de Dados >  >> RDS >> Database

Introdução às travas


Em alguns dos meus artigos anteriores sobre ajuste de desempenho, discuti vários tipos de espera e como eles são indicativos de vários gargalos de recursos. Estou iniciando uma nova série sobre cenários em que um mecanismo de sincronização chamado trava é um gargalo de desempenho e, especificamente, travas sem página. Neste post inicial vou explicar por que as travas são necessárias, o que elas realmente são e como elas podem ser um gargalo.

Por que as travas são necessárias?


É um princípio básico da ciência da computação que sempre que uma estrutura de dados existe em um sistema multi-thread, a estrutura de dados deve ser protegida de alguma forma. Esta proteção dá as seguintes ressalvas:
  1. (Garantido) Uma estrutura de dados não pode ser alterada por uma thread enquanto outra a está lendo
  2. (Garantido) Uma estrutura de dados não pode ser lida por um encadeamento enquanto outro encadeamento a está alterando
  3. (Garantido) Uma estrutura de dados não pode ser alterada por dois ou mais threads ao mesmo tempo
  4. (Opcional) Permitir que dois ou mais encadeamentos leiam a estrutura de dados ao mesmo tempo
  5. (Opcional) Permitir que os encadeamentos sejam enfileirados de forma ordenada para acesso à estrutura de dados

Isso pode ser feito de várias maneiras, incluindo:
  • Um mecanismo que só permite que um único thread de cada vez tenha acesso à estrutura de dados. O SQL Server implementa esse mecanismo e o chama de spinlock. Isso permite os itens 1, 2 e 3 acima.
  • Um mecanismo que permite que vários encadeamentos leiam a estrutura de dados ao mesmo tempo (ou seja, eles têm acesso compartilhado), permite que um único encadeamento obtenha acesso exclusivo à estrutura de dados (com exclusão de todos os outros encadeamentos) e implementa uma forma justa de fazer fila para acesso. O SQL Server implementa esse mecanismo e o chama de trava. Isso permite todas as cinco cláusulas acima.

Então, por que o SQL Server usa spinlocks e travas? Algumas estruturas de dados são acessadas com tanta frequência que uma trava é simplesmente muito cara e, portanto, um spinlock muito leve é ​​usado. Dois exemplos de tais estruturas de dados são a lista de buffers livres no buffer pool e a lista de bloqueios no gerenciador de bloqueios.

O que é uma trava?


Uma trava é um mecanismo de sincronização que protege uma única estrutura de dados e há três tipos amplos de trava no SQL Server:
  1. Travas protegendo uma página de arquivo de dados enquanto ela está sendo lida do disco. Eles aparecem enquanto PAGEIOLATCH_XX aguarda, e eu os discuti nesta postagem.
  2. Travas protegendo o acesso a uma página de arquivo de dados que já está na memória (uma página de 8 KB no pool de buffers é apenas uma estrutura de dados). Eles aparecem enquanto PAGELATCH_XX aguarda, e eu os discuti nesta postagem.
  3. Travas que protegem estruturas de dados que não são de página. Eles aparecem como esperas de LATCH_SH e LATCH_EX.

Nesta série, vamos nos concentrar no terceiro tipo de travas.

Uma trava é em si uma pequena estrutura de dados e você pode pensar nela como tendo três componentes:
  • Uma descrição do recurso (do que está protegendo)
  • Um campo de status indicando em quais modos a trava está atualmente retida, quantos encadeamentos mantêm a trava nesse modo e se há encadeamentos aguardando (além de outras coisas com as quais não precisamos nos preocupar)
  • Uma fila de threads de primeiro a entrar, primeiro a sair que está aguardando acesso à estrutura de dados e quais modos de acesso estão aguardando (chamada fila de espera)

Para travas que não são de página, nos limitaremos a considerar apenas os modos de acesso SH (compartilhamento) para leitura da estrutura de dados e EX (exclusivo) para alteração da estrutura de dados. Existem outros modos mais exóticos, mas eles raramente são usados ​​e não aparecerão como pontos de discórdia, então vou fingir que eles não existem pelo resto desta discussão.

Alguns de vocês podem saber que também existem complicações mais profundas em torno de superlatches/sublatches e particionamento de latch para escalabilidade, mas não precisamos ir a essa profundidade para os propósitos desta série.

Adquirindo uma trava


Quando um thread deseja adquirir um latch, ele verifica o status do latch.

Se a thread quiser adquirir o latch no modo EX, só poderá fazê-lo se não houver threads segurando o latch em nenhum modo. Se for esse o caso, o thread adquire o latch no modo EX e define o status para indicar isso. Se houver uma ou mais threads já segurando o latch, a thread define o status para indicar que há uma thread em espera, entra na parte inferior da fila de espera e é suspensa (na lista de espera do agendador em que está ) esperando por LATCH_EX.

Se o encadeamento quiser adquirir o latch no modo SH, ele só poderá fazê-lo se nenhum encadeamento retiver o latch ou se os únicos encadeamentos segurando o latch estiverem no modo SH *e* não houver nenhum encadeamento esperando para adquirir o latch. Se for esse o caso, o encadeamento adquire a trava no modo SH, define o status para indicar isso e incrementa a contagem de encadeamentos que mantêm a trava. Se o latch for mantido no modo EX ou houver um ou mais threads em espera, o thread definirá o status para indicar que há um thread em espera, entrará no final da fila de espera e será suspenso aguardando LATCH_SH.

A verificação de encadeamentos em espera é feita para garantir a justiça de um encadeamento aguardando a trava no modo EX. Ele só terá que esperar por threads segurando a trava no modo SH que adquiriram a trava antes de começar a esperar. Sem essa verificação, um termo de ciência da computação chamado 'inanição' pode ocorrer, quando um fluxo constante de threads adquirindo a trava no modo SH impede que a thread do modo EX seja capaz de adquirir a trava.

Liberar uma trava


Se o encadeamento retiver a trava no modo EX, ele desativa o status mostrando que a trava está retida no modo EX e, em seguida, verifica se há encadeamentos em espera.

Se o encadeamento mantiver a trava no modo SH, ele diminuirá a contagem de encadeamentos no modo SH. Se a contagem agora for diferente de zero, o thread de liberação é feito com a trava. Se a contagem *é* agora zero, ele desativa o status mostrando que a trava é mantida no modo SH e, em seguida, verifica se há threads em espera.

Se não houver threads esperando, o thread de liberação é feito com a trava.

Se o chefe da fila de espera estiver aguardando o modo EX, o thread de liberação faz o seguinte:
  • Define o status para mostrar que a trava é mantida no modo EX
  • Remove o encadeamento em espera do início da fila e o define como o proprietário da trava
  • Sinaliza ao encadeamento em espera que ele é o proprietário e agora pode ser executado (ao mover, conceitualmente, o encadeamento em espera da lista de espera em seu agendador para a fila executável no agendador)
  • E é feito com a trava

Se o chefe da fila de espera estiver aguardando no modo SH (o que só pode ser o caso se o thread de liberação estiver no modo EX), o thread de liberação faz o seguinte:
  • Define o status para mostrar que a trava é mantida no modo SH
  • Para todos os threads na fila de espera que estão aguardando o modo SH
    • Remove o encadeamento em espera do início da fila
    • Incrementa a contagem de threads segurando a trava
    • Sinaliza ao thread em espera que ele é proprietário e agora pode ser executado
  • E é feito com a trava

Como as travas podem ser um ponto de discórdia?


Ao contrário das travas, as travas geralmente são mantidas apenas durante a operação de leitura ou alteração, portanto, são bastante leves, mas devido à incompatibilidade SH vs. EX, elas podem ser um ponto de contenção tão grande quanto as travas. Isso pode acontecer quando muitos threads estão tentando adquirir um latch no modo EX (apenas um de cada vez pode) ou quando muitos threads estão tentando adquirir um latch no modo SH e outro thread mantém o latch no modo EX.

Resumo


Quanto mais threads no sistema disputarem uma trava 'quente', maior será a contenção e mais negativo será o efeito na taxa de transferência da carga de trabalho. Você provavelmente já ouviu falar de problemas conhecidos de contenção de travas, por exemplo, em torno de bitmaps de alocação de tempdb, mas a contenção também pode ocorrer para travas que não são de página.

Agora que dei a você informações suficientes para entender as travas e como elas funcionam, nos próximos artigos examinarei alguns problemas reais de contenção de travas sem página e explicarei como evitá-los ou contorná-los.