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

Прочетете изолацията на ангажирани моментни снимки

[ Вижте индекса за цялата серия ]

SQL Server предоставя две физически имплементации на readcommitted ниво на изолация, дефинирано от стандарта SQL, заключване на четене, ангажирано и четене, изолиране на моментна снимка (RCSI ). Въпреки че и двете реализации отговарят на изискванията, определени в SQL стандарта за поведение на изолация при четене, RCSI има доста различно физическо поведение от заключващата реализация, която разгледахме в предишната публикация от тази серия.

Логически гаранции

Стандартът на SQL изисква транзакция, работеща на ниво на изолация за четене, да не изпитва никакви мръсни четения. Друг начин да се изрази това изискване е да се каже, че транзакцията с ангажимент за четене трябва да среща само ангажирани данни .

Стандартът също така казва, че четенето на ангажименти транзакции може изпитайте явленията на едновременност, известни като неповтаряеми четения и фантоми (въпреки че всъщност не се изисква да го правят). Както се случва, и двете физически реализации на изолация за четене в SQL Server могат да изпитат неповторими четения и фантомни редове, въпреки че точните подробности са доста различни.

Преглед в момента на ангажираните данни

Ако опцията за база данни READ_COMMITTED_SNAPSHOT в ON , SQL Server използва реализация на версията на реда на нивото на изолация за четене. Когато това е активирано, транзакциите, изискващи изолация за четене, автоматично използват RCSI реализацията; не са необходими промени в съществуващия T-SQL код за използване на RCSI. Обърнете внимание обаче, че това не е същото като казва, че кодътще се държи по същия начин под RCSI, както при използване на заключващата имплементация на read committed, всъщност това доста общо не е така .

В стандарта на SQL няма нищо, което да изисква данните, прочетени от транзакция с извършено четене, да са най-скорошните ангажирани данни. Внедряването на SQL Server RCSI се възползва от това, за да предостави на транзакции с изглед във времето на ангажирани данни, като този момент от време е момента на текущото изявление изпълнение (не в момента, в който е започнала някоя съдържаща транзакция).

Това е доста различно от поведението на SQL Server за заключваща реализация на read Committed, където изявлението вижда най-скоро записаните данни към момента на всеки елемент е физически прочетен . Заключването на извършеното четене освобождава споделените ключалки възможно най-бързо, така че наборът от срещани данни може да идва от много различни моменти във времето.

За да обобщим, заключването на извършеното четене вижда всеки ред както беше по онова време, беше заключено за кратко и физически прочетено; RCSI вижда всички редове каквито бяха в момента на началото на изявлението. И двете реализации са гарантирани, че никога няма да видят незаети данни, но данните, които срещат, могат да бъдат много различни.

Последствията от изглед към момента

Виждането на изглед към момента на ангажираните данни може да изглежда очевидно по-добро от по-сложното поведение на заключващата реализация. Ясно е, например, че изгледът от момента не може да страда от проблемите на липсващите редове или среща един и същ ред няколко пъти , които и двете са възможни при изолация за заключване на четене.

Второ важно предимство на RCSI е, че не придобива споделени ключалки при четене на данни, тъй като данните идват от магазина за версия на ред, а не са достъпни директно. Липсата на споделени заключване може драстично да подобри едновременността чрез елиминиране на конфликти с едновременни транзакции, които искат да придобият несъвместими ключалки. Това предимство обикновено се обобщава, като се казва, че читателите не блокират писатели под RCSI и обратно. Като допълнителна последица от намаляването на блокирането поради несъвместими заявки за заключване, възможността за застой обикновено се намалява значително, когато работи под RCSI.

Тези предимства обаче не идват безразходи и предупреждения . От една страна, поддържането на версии на ангажирани редове консумира системни ресурси, така че е важно физическата среда да е конфигурирана да се справи с това, главно по отношение на tempdb изисквания за производителност и памет/дисково пространство.

Второто предупреждение е малко по-фино:RCSI предоставя изглед на моментна снимка на ангажираните данни както беше в началото на изявлението, но няма нищо, което да попречи на реалните данни да бъдат променени (и тези промени да бъдат ангажирани), докато операторът RCSI се изпълнява. Няма споделени брави, запомнете. Непосредствена последица от тази втора точка е, че T-SQL кодът, работещ под RCSI, може да взема решения въз основа на неактуална информация , в сравнение с текущото ангажирано състояние на базата данни. Ще говорим повече за това скоро.

Има едно последно (специфично за внедряването) наблюдение, което искам да направя за RCSI, преди да продължим. Скаларни функции и функции с множество изрази изпълнете с помощта на различен вътрешен T-SQL контекст от съдържащия израз. Това означава, че изгледът точка-в-време, видян вътре в извикване на скаларна функция или функция с множество изрази, може да бъде по-късен от изгледа в момента, видян от останалата част от израза. Това може да доведе до неочаквани несъответствия, тъй като различните части на едно и също изявление виждат данни от различни моменти във времето . Това странно и объркващо поведениене се прилагат към вградени функции, които виждат същата моментна снимка като оператора, в който се появяват.

Неповтаряеми показания и фантоми

Като се има предвид точка-в-време изглед на ниво оператор на състоянието на ангажимента на базата данни, може да не е непосредствено очевидно как транзакция за четене, ангажирана под RCSI, може да изпита неповторяемото четене или феномена на фантомния ред. Всъщност, ако ограничим мисленето си до обхвата на едно изявление , нито едно от тези явления не е възможно при RCSI.

Четене на едни и същи данни няколко пъти в рамките на едно и също изявление под RCSI винаги ще връща едни и същи стойности на данни, никакви данни няма да изчезнат между тези четения и няма да се появяват нови данни. Ако се чудите какъв вид изявление може да чете едни и същи данни повече от веднъж, помислете за заявки, които препращат към една и съща таблица повече от веднъж, може би в подзаявка.

Последователността на четене на ниво израз е очевидна последица от това, че показанията се издават срещу фиксирана моментна снимка на данните. Причината, поради която RCSIне осигуряване на защита от неповтаряеми четения и фантоми е, че тези стандартни SQL феномени се дефинират на ниво транзакция. Множество оператори в рамките на транзакция, изпълнявана в RCSI, може да виждат различни данни, тъй като всяко изявление вижда изглед към момента към момента този конкретен израз започна.

За да обобщим, всякоизявление в рамките на RCSI транзакция вижда статичен ангажиран набор от данни, но този набор може да се променя между изразите в същата транзакция.

Неактуални данни

Възможността нашият T-SQL код да вземе важно решение въз основа на остаряла информация е повече от малко обезпокоителна. Помислете за момент, че моментната снимка, използвана от един израз, изпълняван под RCSI, може да е произволно стара .

Изявление, което се изпълнява за значителен период от време, ще продължи да вижда състоянието на ангажимента на базата данни, както е било, когато е започнало изявлението. Междувременно в изявлението липсват всички направени промени, настъпили в базата данни от това време.

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

Въпрос на времето

Този проблем с остарели данни по принцип се отнася за всички RCSI изявления, без значение колко бързо могат да бъдат завършени. Колкото и малък да е времевият прозорец, винаги има вероятност едновременна операция да промени набора от данни, с който работим, без ние да сме наясно с тази промяна. Нека разгледаме отново един от простите примери, които използвахме преди, когато изследвахме поведението на заключването на извършеното четене:

INSERT dbo.OverdueInvoices
SELECT I.InvoiceNumber
FROM dbo.Invoices AS I
WHERE I.TotalDue >
(
    SELECT SUM(P.Amount)
    FROM dbo.Payments AS P
    WHERE P.InvoiceNumber = I.InvoiceNumber
);

Когато се изпълнява под RCSI, това изявление не може вижте всички ангажименти модификации на базата данни, които възникват, след като изразът започне да се изпълнява. Въпреки че няма да се сблъскаме с проблемите на пропуснатите или многократно срещани редове, възможни при прилагането на заключване, едновременна транзакция може да добави плащане, което трябва за да попречи на клиент да бъде изпратено строго предупредително писмо за просрочено плащане, след като изявлението по-горе започне да се изпълнява.

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

Разбира се, има много смекчаващи фактори в този конкретен пример. Поведението може да се разглежда като напълно приемливо. В крайна сметка изпращането на напомнящо писмо, защото плащането е пристигнало с няколко секунди твърде късно, е лесно защитимо действие. Принципът обаче остава.

Провали на бизнес правилата и рискове за интегритет

По-сериозни проблеми могат да възникнат от използването на остаряла информация от изпращането на предупредително писмо няколко секунди по-рано. Добър пример за този клас слабост може да се види с код за задействане използва се за налагане на правило за интегритет, което може би е твърде сложно за прилагане с декларативни ограничения за референтна цялост. За да илюстрирате, разгледайте следния код, който използва тригер, за да наложи вариация на ограничение за външен ключ, но такъв, който налага връзката само за определени редове на дъщерна таблица:

ALTER DATABASE Sandpit
SET READ_COMMITTED_SNAPSHOT ON
WITH ROLLBACK IMMEDIATE;
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
CREATE TABLE dbo.Parent (ParentID integer PRIMARY KEY);
GO
CREATE TABLE dbo.Child
(
    ChildID integer IDENTITY PRIMARY KEY,
    ParentID integer NOT NULL,
    CheckMe bit NOT NULL
);
GO
CREATE TRIGGER dbo.Child_AI
ON dbo.Child
AFTER INSERT
AS
BEGIN
    -- Child rows with CheckMe = true
    -- must have an associated parent row
    IF EXISTS
    (
        SELECT ins.ParentID
        FROM inserted AS ins
        WHERE ins.CheckMe = 1
        EXCEPT
        SELECT P.ParentID
        FROM dbo.Parent AS P
    )
    BEGIN
    	RAISERROR ('Integrity violation!', 16, 1);
        ROLLBACK TRANSACTION;
    END
END;
GO
-- Insert parent row #1
INSERT dbo.Parent (ParentID) VALUES (1);

Сега помислете за транзакция, изпълняваща се в друга сесия (използвайте друг прозорец SSMS за това, ако следвате), която изтрива родителски ред #1, но все още не се ангажира:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
DELETE FROM dbo.Parent
WHERE ParentID = 1;

Обратно в нашата първоначална сесия се опитваме да вмъкнем (отметнат) дъщерен ред, който препраща към този родител:

INSERT dbo.Child (ParentID, CheckMe)
VALUES (1, 1);

Кодът за задействане се изпълнява, но тъй като RCSI вижда само commited данни към момента на стартиране на изявлението, той все още вижда родителския ред (а не незавършеното изтриване) и вмъкването е успешно !

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

COMMIT TRANSACTION;
SELECT P.* FROM dbo.Parent AS P;
SELECT C.* FROM dbo.Child AS C;

Разбира се, това е опростен пример и може лесно да бъде заобиколен с помощта на вградените средства за ограничаване. Много по-сложни бизнес правила и ограничения за псевдо-интегритет могат да бъдат записани вътре и вън от тригери . Възможността за неправилно поведение при RCSI трябва да е очевидна.

Поведение на блокиране и последните записани данни

Споменах по-рано, че T-SQL кодът не е гарантиран, че ще се държи по същия начин при четене на RCSI, записано, както при използване на заключващата реализация. Предходният пример с код за задействане е добра илюстрация за това, но трябва да подчертая, че общият проблем не се ограничава до тригери .

RCSI обикновено не е добър избор за всеки T-SQL код, чиято коректност зависи от блокирането, ако съществува едновременна незавършена промяна. RCSI може също да не е правилният избор, ако кодът зависи от четенето на ток ангажименти данни, а не последните ангажирани данни към момента на стартиране на изявлението. Тези две съображения са свързани, но не са едно и също нещо.

Заключващо четене, извършено под RCSI

SQL Server предоставя един начин да поискате заключване четене се извършва, когато RCSI е активиран, като се използва подсказката за таблицата READCOMMITTEDLOCK . Можем да модифицираме нашия тригер, за да избегнем проблемите, показани по-горе, като добавим този намек към таблицата, която се нуждае от поведение на блокиране, за да работи правилно:

ALTER TRIGGER dbo.Child_AI
ON dbo.Child
AFTER INSERT
AS
BEGIN
    -- Child rows with CheckMe = true
    -- must have an associated parent row
    IF EXISTS
    (
        SELECT ins.ParentID
        FROM inserted AS ins
        WHERE ins.CheckMe = 1
        EXCEPT
        SELECT P.ParentID
        FROM dbo.Parent AS P WITH (READCOMMITTEDLOCK) -- NEW!!
    )
    BEGIN
        RAISERROR ('Integrity violation!', 16, 1);
        ROLLBACK TRANSACTION;
    END
END;

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

Идентифициране на заявки, които може да не се изпълняват правилно под RCSI е нетривиална задача, която може да изисква обширно тестване за да се оправите (и моля, не забравяйте, че тези проблеми са доста общи и не се ограничават до код за задействане!) Също така, добавяне на READCOMMITTEDLOCK намек към всяка таблица, която се нуждае от нея, може да бъде досаден и податлив на грешки процес. Докато SQL Server не предостави по-широкообхватна опция за заявяване на внедряване на заключване, където е необходимо, ние сме останали с използването на подсказките в таблицата.

Следващия път

Следващата публикация от тази поредица продължава нашето изследване на изолацията на моментни снимки, извършени за четене, с поглед към изненадващото поведение на операторите за промяна на данни под RCSI.

[ Вижте индекса за цялата серия ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL GROUP BY- 3 лесни съвета за групиране на резултати като професионалист

  2. Повече за CXPACKET чака:изкривен паралелизъм

  3. Модел на библиотечни данни

  4. Как да филтрирате записи с агрегатна функция COUNT

  5. Разрушете стените! Как да деблокирате данните си