Bloqueios e desempenho
Como o DB2 adquire bloqueios implicitamente à medida que eles se tornam necessários, com exceção do uso da instrução LOCK TABLE (e, no DB2 para Linux, UNIX e Windows, a instrução ALTER TABLE ) para forçar o DB2 a adquirir bloqueios de tabela, o bloqueio fica praticamente fora do seu controle. Há vários fatores que influenciam a forma como o bloqueio afeta o desempenho. Esses fatores incluem:
- Compatibilidade de bloqueio
- Conversão de bloqueio
- Escalação de bloqueios
- Esperas e tempos limite de bloqueio
- Conflitos
O conhecimento desses fatores e a compreensão de como eles afetam o desempenho podem ajudar você a projetar aplicativos de banco de dados que funcionam bem em ambientes de banco de dados multiusuário.
Se o estado de um bloqueio mantido em um recurso de dados por uma transação permite que outra transação coloque outro bloqueio no mesmo recurso antes que o primeiro bloqueio seja liberado, os bloqueios são considerados compatíveis. E, sempre que uma transação mantém um bloqueio em um recurso de dados e outra transação tenta adquirir um bloqueio no mesmo recurso, o DB2 examina o estado de cada bloqueio e determina se eles são compatíveis. Tablela 3 contém uma matriz de compatibilidade de bloqueios que identifica quais bloqueios são compatíveis.
Tablela 3. Matriz de compatibilidade de bloqueios
| Bloqueio solicitado pela segunda transação | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Estado do bloqueio | IN | IS | NS | S | IX | SIX | U | X | Z | NW | ||
| Bloqueio mantido pela primeira transação | IN | Sim | Sim | Sim | Sim | Sim | Sim | Sim | Sim | Não | Sim | |
| IS | Sim | Sim | Sim | Sim | Sim | Sim | Sim | Não | Não | Não | ||
| NS | Sim | Sim | Sim | Sim | Não | Não | Sim | Não | Não | Sim | ||
| S | Sim | Sim | Sim | Sim | Não | Não | Sim | Não | Não | Não | ||
| IX | Sim | Sim | Não | Não | Sim | Não | Não | Não | Não | Não | ||
| SIX | Sim | Sim | Não | Não | Não | Não | Não | Não | Não | Não | ||
| U | Sim | Sim | Sim | Sim | Não | Não | Não | Não | Não | Não | ||
| X | Sim | Não | Não | Não | Não | Não | Não | Não | Não | Não | ||
| Z | Não | Não | Não | Não | Não | Não | Não | Não | Não | Não | ||
| NW | Sim | Não | Sim | Não | Não | Não | Não | Não | Não | Não | ||
| Sim — Os bloqueios são compatíveis. A solicitação de bloqueio é atendida imediatamente Não — Os bloqueios não são compatíveis. A transação solicitante precisa aguardar a liberação ou o tempo limite do bloqueio para atender a solicitação de bloqueio. | ||||||||||||
| Estados de bloqueio: IN — Intent None IS — Intent Share NS — Scan Share S — Share IX — Intent Exclusive SIX — Share With Intent Exclusive U — Update X — Exclusive Z — Super Exclusive NW — Next Key Weak Exclusive | ||||||||||||
| Adaptado da Tabela 1, encontrada sob Lock type compatibility no Centro de Informações do IBM DB2 10.1 para Linux, UNIX e Windows. (http://publib.boulder.ibm.com/infocenter/db2luw/v10r1/topic/com.ibm.db2.luw.admin.perf.doc/doc/r0005274.html) | ||||||||||||
Conversão/promoção de bloqueio
Se uma transação que mantém um bloqueio em um recurso precisa adquirir um bloqueio mais restritivo sobre ele, em vez de liberar o bloqueio antigo e adquirir um novo, o DB2 tenta alterar o estado do bloqueio para o estado mais restritivo que é necessário. A ação de alterar o estado de um bloqueio já existente é conhecida como conversão de bloqueio (DB2 para Linux, UNIX e Windows) ou promoção do bloqueio (DB2 para z/OS); a conversão/promoção do bloqueio ocorre porque a transação só pode manter um bloqueio em cada recurso. Figura 7 ilustra como a conversão/promoção de bloqueio funciona.
Figura 7. A conversão/promoção altera o bloqueio que está sendo mantido
Na maioria dos casos, a conversão/promoção do bloqueio é realizada em bloqueios de linha, e o processo é razoavelmente objetivo. Por exemplo, se um bloqueio Update (U) é mantido e um bloqueio Exclusive (X) é necessário, o bloqueio Update (U) é convertido/promovido a Exclusive (X), mas isso nem sempre acontece quando se trata de bloqueios Share (S) e Intent Exclusive (IX). Já que nenhum dos bloqueios é considerado mais restritivo que o outro, se um bloqueio é mantido e o outro é solicitado, o bloqueio mantido é convertido/promovido para Share With Intent Exclusive (SIX). Em todos os outros bloqueios, o estado do bloqueio atual é alterado para o estado que está sendo solicitado — desde que o estado solicitado seja mais restritivo. (A conversão/promoção de bloqueio só ocorre se o bloqueio mantido pode aumentar a sua restrição). Depois que o bloqueio é convertido, ele permanece no nível mais alto que foi atingido até que a transação que mantém o bloqueio seja finalizada e o bloqueio seja liberado.
Quando uma conexão ao banco de dados é estabelecida pela primeira vez, uma quantidade específica de memória é separada para conter uma estrutura que o DB2 usa para gerenciar bloqueios. É nessa estrutura, conhecida como lista de bloqueios, que os bloqueios mantidos por todas as transações ativas são armazenados após a aquisição. (A quantidade real de memória que é separada para a lista de bloqueios é controlada por meio do parâmetro de configuração de banco de dados locklist ).
Como a quantidade de memória disponível é limitada e essa memória deve ser compartilhada por todas as transações ativas, o DB2 impõe um limite na quantidade de espaço que cada transação pode consumir na lista de bloqueios. (Esse limite é controlado por meio do parâmetro de configuração de banco de dados maxlocks ). Para impedir que um agente de banco de dados (que atua em nome de uma transação) ultrapasse as suas limitações de espaço na lista de bloqueios, realiza-se um processo conhecido como escalação de bloqueios sempre que uma quantidade excessiva de bloqueios (independentemente do tipo) foi adquirida em nome de uma única transação. Durante a escalação de bloqueios, o espaço na lista de bloqueios é liberado ao substituir vários bloqueios de linha com um único bloqueio de tabela. Figura 8 ilustra como a escalação de bloqueios funciona.
Figura 8. A escalação de bloqueios substitui vários bloqueios de linha individuais por um único bloqueio de tabela
Como a escalação de bloqueios funciona? Quando uma transação solicita um bloqueio e a lista de bloqueios do banco de dados está cheia, uma das tabelas associadas à transação que solicita o bloqueio é selecionada, um bloqueio de tabela é adquirido em nome da transação e todos os bloqueios de linha da tabela são liberados para abrir espaço na lista de bloqueios. Em seguida, o bloqueio de tabela é adicionado à lista de bloqueios e, se mesmo assim a lista de bloqueios não tem o espaço de armazenamento necessário para adquirir o bloqueio solicitado, outra tabela é selecionada é o processo é repetido até que se abra um espaço livre suficiente — somente nesse momento o bloqueio solicitado é adquirido (nesse ponto, a transação tem permissão para continuar). Se o espaço necessário na lista de bloqueios ainda está indisponível (após a escalação de todos os bloqueios de linha da transação), é gerado um erro, todas as mudanças feitas no banco de dados pela transação são retrocedidas e a transação é finalizada graciosamente.
OBSERVAÇÃO: O uso da instrução LOCK TABLE não impede a escalação normal do bloqueio, mas pode reduzir a sua frequência.
Esperas e tempos limite de bloqueio
Como vimos, sempre que uma transação mantém um bloqueio em um recurso específico, outras transações que estão executando simultaneamente podem ter o acesso negado ao recurso até que a transação que mantém o bloqueio seja finalizada (nesse caso, todos os bloqueios adquiridos em nome da transação sejam liberados). Consequentemente, na falta de algum tipo de mecanismo de tempo limite do bloqueio, a transação pode aguardar indefinidamente a liberação de um bloqueio mantido por outra transação. Infelizmente, se uma das transações for finalizada prematuramente por outro usuário ou aplicativo, a consistência de dados pode ser comprometida.
Para impedir que situações como essa aconteçam, um recurso importante conhecido como detecção do tempo limite do bloqueio foi incorporado ao DB2. Quando é utilizado, esse recurso impede que as transações aguardem indefinidamente a liberação de um bloqueio. Designando um valor ao parâmetro locktimeout no arquivo de configuração do banco de dados adequado, é possível controlar quando a detecção do tempo limite do bloqueio ocorre. Esse parâmetro especifica o período de tempo que qualquer transação aguarda para obter um bloqueio solicitado; se o bloqueio desejado não é adquirido dentro do período especificado, todas as mudanças feitas pela transação no banco de dados são retrocedidas e a transação é finalizada graciosamente.
OBSERVAÇÃO: por padrão, o parâmetro de configuração locktimeout é configurado como -1, ou seja, as transações aguardarão indefinidamente para adquirir os bloqueios necessários. Em muitos casos, esse valor padrão deve ser alterado. Além disso, os aplicativos devem ser escritos de forma a capturar qualquer código de retorno SQL de tempo limite (ou conflito) retornado pelo DB2 e reagir adequadamente.
Em muitos casos, o problema da espera indefinida por um bloqueio pode ser evitado usando a semântica atualmente confirmada e especificando um tempo limite do bloqueio. No entanto, não é isso o que acontece quando a contenção de bloqueio provoca uma situação conhecida como conflito. A melhor forma de ilustrar como um conflito pode ocorrer é um exemplo: suponha que a Transação 1 adquira um bloqueio Exclusive (X) na Tabela A, e a Transação 2 adquira um bloqueio Exclusive (X) na Tabela B. Agora, suponha que a Transação 1 tente adquirir um bloqueio Exclusive (X) na Tabela B, e a Transação 2 tente adquirir um bloqueio Exclusive (X) na Tabela A. Já vimos que o processamento das duas transações será suspenso até que a segunda solicitação de bloqueio seja atendida. Já que nenhuma solicitação de bloqueio pode ser atendida até que uma das transações proprietárias libere o bloqueio que mantém no momento (realizando uma operação de confirmação ou retrocesso) e nenhuma das transações pode realizar uma operação de confirmação ou retrocesso porque as duas estão aguardando para adquirir bloqueios, houve uma situação de conflito. Figura 9 ilustra esse cenário.
Figura 9. Conflito
O conflito é denominado, de forma mais precisa, ciclo de conflito porque as transações envolvidas formam um círculo de estados de espera. Cada transação no ciclo aguarda a liberação de um bloqueio mantido por outra transação do círculo (consulte a Figura 9). Quando ocorre um ciclo de conflito, todas as transações envolvidas aguardarão indefinidamente a liberação de um bloqueio, a menos que um agente externo intervenha e quebre o ciclo. No DB2, esse agente é um processo de segundo plano conhecido como detector de conflito, cuja única responsabilidade é localizar e resolver quaisquer conflitos encontrados no subsistema de bloqueio.
Cada banco de dados tem seu próprio detector de conflito, que é ativado como parte do processo de inicialização de banco de dados. Uma vez ativado, o detector de conflito fica "adormecido" na maior parte do tempo, mas acorda em intervalos pré-configurados e examina o subsistema de bloqueio para determinar se há uma situação de conflito. Normalmente, o detector de conflito acorda, vê que não há conflitos no subsistema de bloqueio e volta ao estado suspenso. Se o detector de conflito detecta um ciclo de conflito, ele selecionada aleatoriamente uma das transações envolvidas para ser retrocedida e finalizada; a transação escolhida (conhecida como processo vítima) recebe um código de erro SQL, e todos os bloqueios que ela tinha adquirido são liberados. As transações restantes podem continuar porque o ciclo de conflito foi quebrado. É possível, mas improvável, que haja mais de um ciclo de conflito no subsistema de bloqueio de um banco de dados. Se há vários ciclos de conflito, o detector os localiza e finaliza uma das transações irregulares da mesma forma, até que todos os ciclos de conflito tenham sido quebrados. No final, o detector de conflito volta ao estado suspenso e retorna no próximo intervalo predefinido para examinar novamente o subsistema de bloqueio.
Embora a maioria dos ciclos de conflito envolva dois recursos ou mais, um tipo especial de conflito, conhecido como conflito de conversão, pode ocorrer em um recurso individual. Os conflitos de conversão ocorrem quando duas transações (ou mais) que já mantêm bloqueios compatíveis em um objeto solicitam bloqueios novos e incompatíveis no mesmo objeto. Isso normalmente ocorre quando duas transações simultâneas (ou mais) procuram linhas em uma tabela realizando uma varredura de índice e, em seguida, tentam modificar uma linha recuperada ou mais.
