LOCK IN SHARE MODE
Uma leitura consistente não é conveniente em alguma circunstâncias. Suponha que você queira adicionar uma nova linha em sua tabela CHILD
, e está certo que ela já possui um pai na tabela PARENT
.
Suponha que você utilize leitura consistente para ler a tabela PARENT
e certamente veja o pai do filho na tabela. Agora você pode adiciona com segurança o registro filho na tabela CHILD
? Não, porque pode ter acontecido de outro usuário ter deletado o registro pai da tabela PARENT
, e você não estar ciente disto.
A solução é realizar o SELECT
em um modo de travamento, LOCK IN SHARE MODE
.
SELECT * FROM PARENT WHERE NAME = 'Jones' LOCK IN SHARE MODE;
Realizar uma leitura em modo compartilhado significa que lemos o dado disponível por último e configuramos travas de leitura nos registros lidos. Se o este dado pertencer a uma transação de outro usuário que ainda não fez commit, esperaremos até que o commit seja realizado. Uma trava em modo compartilhado previne que ocorra atualizações ou deleções de registros já lidos. Depois de vermos que a consulta acima retornou o pai 'Jones'
, podemos com segurança adicionar o seu filho a tabela CHILD
, e realizar o commit de nossa transação. Este exemplo mostra como implementar integridade referêncial no código de sua aplicação.
Deixe-nos mostrar outro exemplo: temos um compo de contador inteiro em uma tabela CHILD_CODES
que usamos para atribuir um identificador único para cada filho que adicionamos na tabela CHILD
. Obviamente, usar uma leitura consistente ou uma leitura em modo compartilhado para ler o valor atual do contador não é uma boa idéia, já que dois usuários do banco de dados podem ver o mesmo valor para o contador e, assim, teríamos um erro de chave duplicada ao adicionarmos os dois filhos com o mesmo identificador para a tabela.
Neste caso existem dois bons modos de se implementar a leitura e o incremento do contador: (1) atualizar o contador primeiro aumentando-o de 1 e só depois disto lê-lo, ou (2) ler o contador primeiro com um modo de bloqueio FOR UPDATE
, e incrementá-lo depois disto:
SELECT COUNTER_FIELD FROM CHILD_CODES FOR UPDATE; UPDATE CHILD_CODES SET COUNTER_FIELD = COUNTER_FIELD + 1;
Um SELECT ... FOR UPDATE
irá ler o dado disponível por último atribuindo travas exclusivas a cada linha que ele ler. Assim ele atribui uma mesma trava que um UPDATE
SQL pesquisado atribuiria nos registros.