sexta-feira, fevereiro 11, 2011

SQL Server - Transactions & Locking #3

Locking

Locking refere-se ao mecanismo utilizado pelo SQL Server para sincronizar o acesso simultâneo de vários utilizadores aos mesmo objecto (base de dados, tabela, linha, etc.). Logo que uma transação pretenda obter uma dependência (seja para leitura ou escrita) sobre o estado atual de determinados dados, deve proteger-se dos efeitos de outra transação modificar esses dados.
No SQL Server, isso é garantido porque a transação “solicita” um lock sobre os dados a que pretende aceder. O nível do lock determina o grau de dependência da transacção em relação aos dados. E nenhuma transação pode obter um lock que entre em conflito com outro lock que já tenha sido atribuído a outra transação. Sempre que isso se verifica, a transacção será suspendida até que o lock possa ser-lhe atribuído.

Há vários fatores que influenciam este comportamento. Desde o “isolation level” ativo para cada transacção, passando pelo tipo de locks disponíveis no SQL Server, a granularidade dos locks e, no limite, a própria programação das transações.

Consideremos, por agora, apenas as características gerais do mecanismo de locking do SQL Server.

Hierarquia e Granularidade dos Locks


Uma transação pode bloquear tipos diferentes de recursos no servidor. Para minimizar o custo inerente ao locking, o motor da base de dados escolherá sempre o nível de locking adequado para os recursos solicitados pela transação. É importante compreender que quantos menos locks existirem em cada momento, maior será a concorrência e logo o desempenho geral do sistema.

Os recursos que podem ser bloqueados pelo motor são os seguintes:

   * RID – identificador de uma linha utilizado para bloquear um única linha.
   * KEY – um lock sobre uma linha de um índice usado para proteger um conjunto de chaves.
   * PAGE – uma página de 8 KB (de dados ou de um índice).
   * EXTENT – um grupo contíguo de 8 páginas.
   * HOBT – um lock (heap ou B-tree) que protege um índice ou a heap de páginas de dados de uma tabela que não tenha um índice clustered.
   * TABLE – uma tabela inteira (incluindo dados e índices).
   * FILE – um ficheiro da base de dados.
   * APPLICATION – um recurso específico de uma base de dados.
   * METADATA – os metadados.
   * ALLOCATION_UNIT – um “application unit”.
   * DATABASE – uma base de dados completa.

Tipicamente, para proteger devidamente os recursos solicitados por uma transação, o motor colocará locks a vários níveis de granularidade: uma hierarquia de locks.

Tipos de Locks (Lock Modes)

Os vários tipos de locks determinam como os recursos bloqueados podem ser acedidos por transações concorrentes.

Existem os seguintes tipos principais de locks:

    * Shared (S) – usados em operações de leitura que não alteram os dados (SELECT).
    * Exclusive (X) – usados em operações de modificação de dados (INSERT, UPDATE e DELETE) para impedir que o mesmo recurso seja alterado em simultâneo por várias transações.
    * Update (U) – são uma combinação dos dois tipos anteriores (S e X) em operações de atualização (UPDATE) que impliquem a leitura dos dados antes da sua alteração.
    * Intent (I) – usados para estabelecer a hierarquia de locks, podem ser “intent shared” (IS), “intent exclusive” (IX) e “shared with intent exclusive” (SIX).

Compatibilidade entre Locks


A compatibilidade entre os vários tipos de locks controla a atribuição destes às várias transações concorrentes que os solicitem. Quando existir um lock sobre determinado recurso, um pedido para obter um lock sobre esse mesmo recurso só será atribuído se o seu modo for compatível com o do lock já existente. Não sendo compatível, a segunda transação será bloqueada até que o primeiro lock seja libertado (ou o intervalo máximo de espera será atingido).

A seguinte tabela ilustra a compatibilidade entre os vários tipos de locks:
Tipo Existente
Tipo Solicitado IS S U IX SIX X
Intent Shared (IS) Sim Sim Sim Sim Sim Não
Shared (S) Sim Sim Sim Não Não Não
Update (U) Sim Sim Não Não Não Não
Intent Exclusive (IX) Sim Não Não Sim Não Não
Shared with Intent Exclusive (SIX) Sim Não Não Não Não Não
Exclusive (X) Não Não Não Não Não Não

Exemplos

Consideremos dois exemplos muito simples dos locks criados automaticamente de acordo com as operações realizadas por cada transação.

Shared Locks:

BEGIN TRAN
SELECT * FROM TA

Esta operação leva à criação de shared locks sobre a tabela TA:
Shared Locks in SQL Server


Note-se que a duração deste tipo de locks (shared) depende, normalmente, apenas da duração dessa operação. A não ser que não esteja a ser utilizado o “isolation level” por defeito (READ UNCOMMITED). Veremos os efeitos do nível de isolamento no próximo post.

Exclusive Locks:


BEGIN TRAN
UPDATE TA SET A = (...)
Exclusive Locks in SQL Server


Neste caso o lock permanerá activo até que a transação seja completada (com COMMIT ou ROLLBACK). Isto significa que qualquer outra transacção que pretenda aceder aos recursos bloqueados (ex.: SELECT * FROM TA) ficará bloqueada até que a primeira transação termine.

Conclusões

Não é de mais voltar a referir que o mecanismo de locking é um factor crítico no desempenho da base de dados. Apesar da sua utilidade evidente para minimizar os problemas referidos no post anterior, é muito importante que exista em cada momento o menor número de locks ativos possível.
Na verdade existem várias boas práticas para minimizar o locking, maximizando a performance. Este artigo descreve-as muito bem. São as regras de ouro de programação de transações.

Nenhum comentário: