Database
 sql >> база данни >  >> RDS >> Database

T-SQL вторник #33:Трикове:Схема Switch-A-Roo

Този месец T-SQL вторник се организира от Mike Fal (блог | twitter), а темата е Trick Shots, където сме поканени да разкажем на общността за някакво решение, което използвахме в SQL Server, което чувства, поне за нас, като един вид "трик удар" - нещо подобно на използването на масови, "английски" или сложни банкови удари в билярд или снукър. След като работих със SQL Server в продължение на около 15 години, имах случая да измисля трикове за решаване на някои доста интересни проблеми, но един, който изглежда е доста за многократна употреба, лесно се адаптира към много ситуации и е лесен за прилагане, е нещо, което наричам "schema switch-a-roo."

Да приемем, че имате сценарий, при който имате голяма таблица за справка, която трябва да се обновява периодично. Тази таблица за справка е необходима на много сървъри и може да съдържа данни, които се попълват от външен източник или източник на трета страна, напр. IP или данни за домейн, или може да представлява данни от вашата собствена среда.

Първите няколко сценария, при които имах нужда от решение за това, бяха предоставянето на метаданни и денормализирани данни за „кешове на данни“ само за четене – всъщност само екземпляри на SQL Server MSDE (и по-късно Express), инсталирани на различни уеб сървъри, така че уеб сървърите изтеглиха тези кеширани данни локално, вместо да притесняват основната OLTP система. Това може да изглежда излишно, но разтоварването на дейността за четене далеч от основната OLTP система и възможността да извади мрежовата връзка напълно от уравнението, доведе до истинско увеличение в цялостната производителност и най-вече за крайните потребители .

Тези сървъри не се нуждаеха от актуални копия на данните; всъщност много от кеш таблиците се актуализираха само ежедневно. Но тъй като системите бяха 24×7 и някои от тези актуализации можеха да отнемат няколко минути, те често пречеха на истинските клиенти да правят реални неща в системата.

Оригиналния(и) подход(и)

В самото начало кодът беше доста опростен:изтрихме редове, които бяха премахнати от източника, актуализирахме всички редове, за които можехме да кажем, че са се променили, и вмъкнахме всички нови редове. Изглеждаше нещо подобно (обработката на грешки и т.н. премахнати за краткост):

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;

Излишно е да казвам, че тази транзакция може да причини някои реални проблеми с производителността, когато системата е била използвана. Със сигурност имаше и други начини да направим това, но всеки метод, който опитахме, беше еднакво бавен и скъп. Колко бавно и скъпо? „Нека преброя сканиранията…“

Тъй като това беше предварително MERGE и вече бяхме изхвърлили „външни“ подходи като DTS, чрез някои тестове установихме, че би било по-ефективно просто да изтрием таблицата и да я попълним отново, вместо да се опитваме да синхронизираме с източника :

BEGIN TRANSACTION;
 
TRUNCATE TABLE dbo.Lookup;
 
INSERT dbo.Lookup([cols]) 
  SELECT [cols] FROM [source];
 
COMMIT TRANSACTION;

Сега, както обясних, тази заявка от [източник] може да отнеме няколко минути, особено ако всички уеб сървъри се актуализираха паралелно (опитахме се да залитаме, където можехме). И ако клиент беше на сайта и се опитваше да изпълни заявка, включваща таблицата за търсене, трябваше да изчака тази транзакция да приключи. В повечето случаи, ако изпълняват тази заявка в полунощ, няма да има значение дали са получили вчерашно копие на данните за търсене или днешното; така че да ги накараш да чакат обновяването изглеждаше глупаво и всъщност доведе до редица обаждания за поддръжка.

Така че макар това да беше по-добро, със сигурност беше далеч от идеалното.

Моето първоначално решение:sp_rename

Първоначалното ми решение, когато SQL Server 2000 беше готин, беше да създам таблица в сянка:

CREATE TABLE dbo.Lookup_Shadow([cols]);

По този начин можех да попълня таблицата в сянка, без изобщо да прекъсвам потребителите, и след това да извърша тристранно преименуване – бърза операция само с метаданни – само след като популацията приключи. Нещо като това (отново, грубо опростено):

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';

Недостатъкът на този първоначален подход беше, че sp_rename има неподлежащо на потискане изходно съобщение, което ви предупреждава за опасностите от преименуването на обекти. В нашия случай изпълнихме тази задача чрез задачи на SQL Server Agent и обработвахме много метаданни и други кеш таблици, така че историята на заданията беше наводнена с всички тези безполезни съобщения и всъщност доведе до съкращаване на реални грешки от подробностите за историята. (Оплаках се за това през 2007 г., но предложението ми в крайна сметка беше отхвърлено и затворено като „Няма да се поправи.“)

По-добро решение:Схеми

След като надстроихме до SQL Server 2005, открих тази фантастична команда, наречена CREATE SCHEMA. Беше тривиално да се приложи същия тип решение, използвайки схеми вместо преименуване на таблици и сега историята на агента нямаше да бъде замърсена с всички тези безполезни съобщения. По принцип създадох две нови схеми:

CREATE SCHEMA fake   AUTHORIZATION dbo;
CREATE SCHEMA shadow AUTHORIZATION dbo;

След това преместих таблицата Lookup_Shadow в схемата на кеша и я преименувах:

ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow;
 
EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';

(Ако просто внедрявате това решение, ще създавате ново копие на таблицата в схемата, без да местите съществуващата таблица там и да я преименувате.)

С тези две схеми на място и копие на таблицата за търсене в схемата в сянка, моето трипосочно преименуване се превърна в трипосочно прехвърляне на схема:

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;

В този момент можете, разбира се, да изпразните копието в сянка на таблицата, но в някои случаи намерих за полезно да оставя "старото" копие на данните наоколо за целите на отстраняване на неизправности:

TRUNCATE TABLE shadow.Lookup;

Всичко друго, което правите с копието в сянка, трябва да сте сигурни, че правите извън транзакцията – двете операции по прехвърляне трябва да са възможно най-кратки и бързи.

Някои предупреждения

  • Външни ключове
    Това няма да работи от кутията, ако таблицата за справка се препраща от външни ключове. В нашия случай не посочихме никакви ограничения към тези кеш таблици, но ако го направите, може да се наложи да се придържате към натрапчиви методи като MERGE. Или използвайте методи само за добавяне и деактивирайте или пуснете външните ключове, преди да извършите каквито и да било модификации на данните (след това ги създайте отново или ги активирайте отново след това). Ако се придържате към техниките MERGE / UPSERT и правите това между сървъри или, още по-лошо, от отдалечена система, силно препоръчвам да получавате необработените данни локално, вместо да се опитвате да използвате тези методи между сървърите.
  • Статистика
    Превключването на таблиците (използвайки преименуване или прехвърляне на схема) ще доведе до преливане на статистики напред-назад между двете копия на таблицата и това очевидно може да бъде проблем за плановете. Така че може да помислите за добавяне на изрични актуализации на статистиката като част от този процес.
  • Други подходи
    Разбира се, има и други начини да направя това, които просто не съм имал повод да опитам. Превключването на дялове и използването на изглед + синоним са два подхода, които може да проуча в бъдеще за по-задълбочено разглеждане на темата. Ще ми е интересно да чуя вашия опит и как сте решили този проблем във вашата среда. И да, осъзнавам, че този проблем до голяма степен е решен от групите за наличност и четливите вторични елементи в SQL Server 2012, но смятам, че е "трик", ако можете да разрешите проблема, без да хвърляте лицензи от висок клас към проблема или да репликирате цялата база данни, за да направите няколко таблици излишни. :-)

Заключение

Ако можете да живеете с ограниченията тук, този подход може да се окаже по-добър от сценарий, при който по същество приемате маса офлайн с помощта на SSIS или вашата собствена рутина MERGE / UPSERT, но моля, не забравяйте да тествате и двете техники. Най-важният момент е, че крайният потребител, който има достъп до таблицата, трябва да има точно същото изживяване по всяко време на деня, дори ако е ударил масата по средата на вашата периодична актуализация.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да използвам INNER JOIN в SQL

  2. Прикачване на ContentDocument към персонализиран обект на Salesforce

  3. Пренебрегнати T-SQL скъпоценни камъни

  4. Използване на Docker в Azure Container Service с Swarm Cluster

  5. ИЗПОЛЗВАЙТЕ HINT и DISABLE_OPTIMIZED_NESTED_LOOP