Estou trabalhando com um aplicativo que usa sinônimos SQL para alternar entre tabelas em bancos de dados locais e remotos sem duplicar o código.
BEGIN TRANSACTION
if (@isRemote = 1)
BEGIN
CREATE SYNONYM SY_SUBJECTS FOR [MT00011].[DBO].[SUBJECTS];
END
ELSE
BEGIN
CREATE SYNONYM SY_SUBJECTS FOR [SUBJECTS];
END
-- do some complex work on SY_SUBJECTS
DROP SYNONYM SY_SUBJECTS
COMMIT
Esse código é chamado por várias solicitações simultaneamente. Estou preocupado que isso cause problemas se esse código for chamado ao mesmo tempo. Ele é executado 99% do tempo em um único banco de dados SQL, e o caso "remoto" é executado após o expediente.
há um problema aqui?
Sempre que você cria ou descarta um
SYNONYM
, você está alterando a definição de seu cenário de banco de dados. Então, sim, eu consideraria isso arriscado, a menos que você tenha controles rígidos sobre quando essas etapas podem ser executadas.Se uma conexão for redefinida
SYNONYM
, ela está alterando esse sinônimo para o servidor e o banco de dados, não para a conexão. Isso significa que um conjunto de "trabalho complexo" executado em outro processo pode acabar mudando de seus dados remotos (por exemplo) para os dados locais sem nenhum aviso. Isso deixaria uma bagunça para limpar.Veja isso como se fosse descartar e recriar tabelas em um sistema em execução. Muitas vezes pode funcionar sem que ninguém saiba, mas de fato pode causar problemas.
Obviamente, se outra conexão tentar definir um
SYNONYM
que já existe, ocorrerá um erro como: "Já existe um objeto chamado 'SY_SUBJECTS' no banco de dados." Isso evitará que você alterne o contexto (mas você deve lidar com o erro) até que o sinônimo seja descartado. (Não háALTER SYNONYM
função, o que aparentemente seria muito mais perigoso.)Portanto, se você tentar alterar o
SYNONYM
sem soltá-lo primeiro, ele falhará.Se um caminho de código for executado
DROP SYNONYM
, ele terá sucesso assim que puder adquirir os bloqueios necessários. Do MSDN: "As referências a sinônimos não são vinculadas ao esquema; portanto, você pode descartar um sinônimo a qualquer momento."Portanto, o
DROP SYNONYM
não interromperá uma transação em execução, mas aguardará a conclusão da transação.O código já é thread-safe porque
CREATE SYNONYM
aceita umSCH-M
bloqueio (modificação de esquema), que bloqueia as tentativas de outras pessoas de criar o mesmo sinônimo, e eles aguardarão até que a transação do proprietário do bloqueio seja concluída.Isso também evitará inerentemente erros relacionados ao objeto sinônimo já existente. Na verdade, mesmo verificando a existência do sinônimo (sem usar
NOLOCK
/READ UNCOMMITTED
) de outro thread também bloqueará até que o bloqueio seja liberado.Portanto, esse design nunca poderia permitir operações simultâneas em primeiro lugar. Se o acesso simultâneo for um requisito, o design pode ser alterado da seguinte maneira:
Como na maioria das vezes a versão local do sinônimo seria usada, crie-a permanentemente no banco de dados com a definição local. Quando a versão remota é necessária, o código pode (dentro de uma transação) descartar e recriar o sinônimo com a definição remota, fazer seu trabalho e, em seguida, descartar e recriar com a definição local para retornar ao estado em que estava quando a transação foi iniciada.
Isso bloqueará outros chamadores pelo mesmo motivo acima se o chamador remoto adquirir o bloqueio primeiro. Se um chamador local estiver usando o sinônimo, ele adquire e potencialmente mantém um
SCH-S
bloqueio (estabilidade de esquema), o que impedirá que o sinônimo seja descartado e, portanto, bloqueará um chamador remoto enquanto o bloqueio for mantido. Isso configura uma situação em que um chamador local pode estar em processo e esperar no meio enquanto um chamador remoto faz seu trabalho.Sou um pouco cauteloso em permitir isso porque torna as coisas um pouco imprevisíveis (ou seja, se você estiver registrando métricas de tempo do processo, pode estar em todo lugar). Para resolver isso, podemos usar
sp_getapplock
e solicitar umShared
bloqueio para um chamador local e umExclusive
bloqueio para um chamador remoto. Isso permitirá chamadores locais simultâneos (que aguardarão se um chamador remoto estiver em execução) e um chamador remoto aguardará que todos os chamadores locais sejam concluídos antes de executar.