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

T-SQL terça-feira #33 :Trick Shots :Schema Switch-A-Roo




O T-SQL Tuesday deste mês está sendo apresentado por Mike Fal (blog | twitter), e o tópico é Trick Shots, onde somos convidados a contar à comunidade sobre alguma solução que usamos no SQL Server que pareceu, pelo menos para nós, como uma espécie de "trick shot" - algo semelhante a usar massé, "inglês" ou complicadas jogadas de banco no bilhar ou na sinuca. Depois de trabalhar com SQL Server por cerca de 15 anos, tive a oportunidade de criar truques para resolver alguns problemas bastante interessantes, mas um que parece ser bastante reutilizável, se adapta facilmente a muitas situações e é simples de implementar, é algo que chamo de "schema switch-a-roo".

Digamos que você tenha um cenário em que tenha uma grande tabela de pesquisa que precisa ser atualizada periodicamente. Essa tabela de pesquisa é necessária em muitos servidores e pode conter dados preenchidos de uma fonte externa ou de terceiros, por exemplo, Dados de IP ou domínio ou podem representar dados de seu próprio ambiente.

Os primeiros cenários em que eu precisava de uma solução para isso eram disponibilizar metadados e dados desnormalizados para "caches de dados" somente leitura - na verdade, apenas instâncias do SQL Server MSDE (e mais tarde Express) instaladas em vários servidores da Web, de modo que os servidores da Web puxavam esses dados armazenados em cache localmente em vez de incomodar o sistema OLTP primário. Isso pode parecer redundante, mas descarregar a atividade de leitura do sistema OLTP primário e ser capaz de tirar a conexão de rede completamente da equação levou a um aumento real no desempenho geral e, principalmente, para os usuários finais .

Esses servidores não precisavam de cópias atualizadas dos dados; na verdade, muitas das tabelas de cache só eram atualizadas diariamente. Mas como os sistemas funcionavam 24 horas por dia, 7 dias por semana e algumas dessas atualizações podiam levar vários minutos, muitas vezes atrapalhavam os clientes reais que faziam coisas reais no sistema.

A(s) Abordagem(ões) Original(ais)


No início, o código era bastante simplista:excluímos as linhas que haviam sido removidas da fonte, atualizamos todas as linhas que sabíamos que haviam sido alteradas e inserimos todas as novas linhas. Parecia algo assim (tratamento de erros, etc. removido por brevidade):
BEGIN TRANSACTION;
 
DELETE dbo.Lookup 
  WHERE [key] NOT IN 
  (SELECT [key] FROM [source]);
 
UPDATE d SET [col] = s.[col]
  FROM dbo.Lookup AS d
  INNER JOIN [source] AS s
  ON d.[key] = s.[key]
  -- AND [condition to detect change];
 
INSERT dbo.Lookup([cols]) 
  SELECT [cols] FROM [source]
  WHERE [key] NOT IN 
  (SELECT [key] FROM dbo.Lookup);
 
COMMIT TRANSACTION;

Escusado será dizer que esta transação pode causar alguns problemas reais de desempenho quando o sistema estava em uso. Certamente havia outras maneiras de fazer isso, mas todos os métodos que tentamos eram igualmente lentos e caros. Quão lento e caro? "Deixe-me contar as digitalizações..."

Como esse MERGE era anterior e já tínhamos descartado abordagens "externas" como DTS, por meio de alguns testes, determinamos que seria mais eficiente apenas limpar a tabela e preenchê-la novamente, em vez de tentar sincronizar com a fonte :
BEGIN TRANSACTION;
 
TRUNCATE TABLE dbo.Lookup;
 
INSERT dbo.Lookup([cols]) 
  SELECT [cols] FROM [source];
 
COMMIT TRANSACTION;

Agora, como expliquei, essa consulta de [fonte] poderia levar alguns minutos, especialmente se todos os servidores da Web estivessem sendo atualizados em paralelo (tentamos escalonar onde podíamos). E se um cliente estivesse no site e tentasse executar uma consulta envolvendo a tabela de pesquisa, ele teria que esperar a conclusão dessa transação. Na maioria dos casos, se eles estão executando essa consulta à meia-noite, não importa se eles obtiveram a cópia dos dados de pesquisa de ontem ou de hoje; então, fazê-los esperar pela atualização parecia bobo e, na verdade, levou a várias chamadas de suporte.

Então, embora isso fosse melhor, certamente estava longe de ser perfeito.

Minha solução inicial:sp_rename


Minha solução inicial, quando o SQL Server 2000 era legal, era criar uma tabela "sombra":
CREATE TABLE dbo.Lookup_Shadow([cols]);

Dessa forma, eu poderia preencher a tabela de sombra sem interromper os usuários e, em seguida, executar uma renomeação de três vias – uma operação rápida e somente de metadados – somente após a conclusão do preenchimento. Algo assim (novamente, grosseiramente simplificado):
TRUNCATE TABLE dbo.Lookup_Shadow;
 
INSERT dbo.Lookup_Shadow([cols]) 
  SELECT [cols] FROM [source];
 
BEGIN TRANSACTION;
 
  EXEC sp_rename N'dbo.Lookup',        N'dbo.Lookup_Fake';
  EXEC sp_rename N'dbo.Lookup_Shadow', N'dbo.Lookup';
 
COMMIT TRANSACTION;
 
-- if successful:
EXEC sp_rename N'dbo.Lookup_Fake', N'dbo.Lookup_Shadow';

A desvantagem dessa abordagem inicial foi que sp_rename tem uma mensagem de saída não supressível avisando sobre os perigos de renomear objetos. No nosso caso, realizamos essa tarefa por meio de trabalhos do SQL Server Agent e lidamos com muitos metadados e outras tabelas de cache, de modo que o histórico do trabalho foi inundado com todas essas mensagens inúteis e, na verdade, fez com que erros reais fossem truncados dos detalhes do histórico. (Reclamei sobre isso em 2007, mas minha sugestão foi descartada e encerrada como "Não será corrigida".)

Uma solução melhor:esquemas


Depois que atualizamos para o SQL Server 2005, descobri esse comando fantástico chamado CREATE SCHEMA. Era trivial implementar o mesmo tipo de solução usando esquemas em vez de renomear tabelas, e agora o histórico do Agente não seria poluído com todas essas mensagens inúteis. Basicamente criei dois novos esquemas:
CREATE SCHEMA fake   AUTHORIZATION dbo;
CREATE SCHEMA shadow AUTHORIZATION dbo;

Em seguida, movi a tabela Lookup_Shadow para o esquema de cache e a renomeei:
ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow;
 
EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';

(Se você estiver apenas implementando esta solução, estará criando uma nova cópia da tabela no esquema, não movendo a tabela existente para lá e renomeando-a.)

Com esses dois esquemas em vigor e uma cópia da tabela de pesquisa no esquema de sombra, minha renomeação de três vias tornou-se uma transferência de esquema de três vias:
TRUNCATE TABLE shadow.Lookup;
 
INSERT shadow.Lookup([cols]) 
  SELECT [cols] FROM [source];
 
-- perhaps an explicit statistics update here
 
BEGIN TRANSACTION;
 
  ALTER SCHEMA fake TRANSFER     dbo.Lookup;
  ALTER SCHEMA dbo  TRANSFER  shadow.Lookup;
 
COMMIT TRANSACTION;
 
ALTER SCHEMA shadow TRANSFER fake.Lookup;

Neste ponto, é claro que você pode esvaziar a cópia de sombra da tabela, no entanto, em alguns casos, achei útil deixar a cópia "antiga" dos dados para fins de solução de problemas:
TRUNCATE TABLE shadow.Lookup;

Qualquer outra coisa que você faça com a cópia de sombra, você deve certificar-se de fazer fora da transação – as duas operações de transferência devem ser o mais concisas e rápidas possível.

Algumas advertências

  • Chaves estrangeiras
    Isso não funcionará imediatamente se a tabela de pesquisa for referenciada por chaves estrangeiras. No nosso caso, não apontamos nenhuma restrição para essas tabelas de cache, mas se você fizer isso, talvez seja necessário usar métodos intrusivos, como MERGE. Ou use métodos somente de acréscimo e desative ou elimine as chaves estrangeiras antes de realizar qualquer modificação de dados (em seguida, recrie ou reative-as posteriormente). Se você se apega às técnicas de MERGE/UPSERT e está fazendo isso entre servidores ou, pior ainda, de um sistema remoto, recomendo obter os dados brutos localmente, em vez de tentar usar esses métodos entre servidores.
  • Estatísticas
    Mudar as tabelas (usando renomeação ou transferência de esquema) levará a estatísticas alternando entre as duas cópias da tabela, e isso pode obviamente ser um problema para os planos. Portanto, considere adicionar atualizações de estatísticas explícitas como parte desse processo.
  • Outras abordagens
    É claro que existem outras maneiras de fazer isso que eu simplesmente não tive a oportunidade de experimentar. A troca de partição e o uso de uma visão + sinônimo são duas abordagens que posso investigar no futuro para um tratamento mais completo do tópico. Eu estaria interessado em ouvir suas experiências e como você resolveu esse problema em seu ambiente. E sim, eu percebo que esse problema é amplamente resolvido por grupos de disponibilidade e secundários legíveis no SQL Server 2012, mas considero um "trick shot" se você puder resolver o problema sem lançar licenças de ponta no problema ou replicar um banco de dados inteiro para tornar algumas tabelas redundantes. :-)

Conclusão


Se você pode viver com as limitações aqui, essa abordagem pode ter um desempenho melhor do que um cenário em que você essencialmente coloca uma tabela offline usando SSIS ou sua própria rotina MERGE / UPSERT, mas certifique-se de testar ambas as técnicas. O ponto mais significativo é que o usuário final que acessa a mesa deve ter exatamente a mesma experiência, a qualquer hora do dia, mesmo que atinja a mesa no meio de sua atualização periódica.