Всеки програмист ще ви каже, че писането на безопасен многонишков код може да бъде трудно. Изисква голямо внимание и добро разбиране на свързаните технически проблеми. Като човек в базата данни може да си помислите, че този вид трудности и усложнения не са приложими при писане на T-SQL. Така че може да бъде малко шок да разберем, че T-SQL кодът също е уязвим към вида на условията на състезанието и други рискове за целостта на данните, които най-често се свързват с многонишковото програмиране. Това е вярно, независимо дали говорим за един T-SQL оператор или за група от оператори, затворени в изрична транзакция.
В основата на проблема е фактът, че системите за бази данни позволяват да се изпълняват множество транзакции едновременно. Това е добре познато (и много желателно) състояние на нещата, но голяма част от производствения T-SQL код все още тихо предполага, че основните данни не се променят по време на изпълнението на транзакция или единичен DML израз като SELECT
, INSERT
, UPDATE
, DELETE
, или MERGE
.
Дори когато авторът на кода е наясно с възможните ефекти от едновременни промени в данните, твърде често се приема, че използването на изрични транзакции осигурява повече защита, отколкото е действително оправдано. Тези предположения и погрешни схващания могат да бъдат фини и със сигурност могат да подведат дори опитни специалисти по база данни.
Сега има случаи, когато тези въпроси няма да имат голямо значение в практически смисъл. Например базата данни може да е само за четене или може да има някаква друга истински гаранция че никой друг няма да промени основните данни, докато работим с тях. По същия начин въпросната операция може да не изисква резултати, които са точно правилно; нашите потребители на данни може да са напълно доволни от приблизителен резултат (дори и такъв, който не представя състоянието на ангажимента на базата данни при който и да е момент във времето).
Проблеми с паралелността
Въпросът за намесата между едновременно изпълнявани задачи е познат проблем за разработчиците на приложения, работещи в езици за програмиране като C# или Java. Решенията са много и разнообразни, но обикновено включват използване на атомни операции или получаване на взаимно изключващ се ресурс (като заключване ) докато се извършва чувствителна операция. Когато не се вземат подходящи предпазни мерки, вероятните резултати са повредени данни, грешка или може би дори пълен срив.
Много от същите понятия (например атомни операции и ключалки) съществуват в света на базата данни, но за съжаление те често имат съществени разлики в значението . Повечето хора в базата данни са наясно със свойствата на ACID на транзакциите в базата данни, където A означава атомен . SQL Server също така използва ключалки (и други вътрешни устройства за взаимно изключване). Нито един от тези термини не означава точно това, което опитен програмист на C# или Java разумно би очаквал, а много специалисти по бази данни също имат объркано разбиране за тези теми (за което ще свидетелства бързо търсене с помощта на любимата ви търсачка).
За да повторя, понякога тези въпроси няма да представляват практически загриженост. Ако напишете заявка за преброяване на броя на активните поръчки в система от база данни, колко важно е, ако броят е малко по-малък? Или ако отразява състоянието на базата данни в някакъв друг момент от време?
Обичайно е реалните системи да правят компромис между едновременност и последователност (дори ако дизайнерът не е осъзнавал това по това време – информиран компромисите са може би по-рядко животно). Истинските системи често работят достатъчно добре , като всички аномалии са краткотрайни или се считат за маловажни. Потребител, който вижда непоследователно състояние на уеб страница, често ще разреши проблема, като опресни страницата. Ако проблемът бъде докладван, най-вероятно той ще бъде затворен като невъзпроизводим. Не казвам, че това е желателно състояние на нещата, просто признавам, че се случва.
Въпреки това е изключително полезно да се разбират проблемите с паралелността на фундаментално ниво. Това, че сме наясно с тях, ни позволява да пишем правилно (или информирани). достатъчно правилно) T-SQL според обстоятелствата. По-важното е, че ни позволява да избягваме писането на T-SQL, което би могло да компрометира логическата цялост на нашите данни.
Но SQL Server предоставя гаранции за ACID!
Да, така е, но те не винаги са това, което бихте очаквали, и не защитават всичко. По-често хората четат много повече в ACID отколкото е оправдано.
Най-често неразбраните компоненти на акронима ACID са думите атомен, последователен и изолиран – ще стигнем до тях след малко. Другият, Издръжлив , е достатъчно интуитивен, стига да помните, че се отнася само за постоянни (възстановим) потребител данни.
С всичко казано, SQL Server 2014 започва донякъде да размива границите на свойството Durable с въвеждането на обща забавена издръжливост и трайност само на OLTP схемата в паметта. Споменавам ги само за пълнота, няма да обсъждаме тези нови функции допълнително. Нека да преминем към по-проблемните свойства на ACID:
Атомното свойство
Много езици за програмиране предоставятатомни операции които могат да се използват за защита срещу условия на състезание и други нежелани ефекти на едновременност, при които множество нишки на изпълнение могат да имат достъп или да променят споделени структури от данни. За разработчика на приложението атомната операция идва с изрична гаранция за пълна изолация от ефектите на друга едновременна обработка в многонишкова програма.
Аналогична ситуация възниква в света на базата данни, където множество T-SQL заявки едновременно осъществяват достъп и променят споделени данни (т.е. базата данни) от различни нишки. Имайте предвид, че тук не говорим за паралелни заявки; обикновените еднонишкови заявки рутинно се планират да се изпълняват едновременно в SQL Server на отделни работни нишки.
За съжаление, атомното свойство от SQL транзакции само гарантира, че модификациите на данните, извършени в рамките на транзакция успешни или неуспешни като единица . Нищо повече от това. Със сигурност няма гаранция за пълна изолация от ефектите на друга едновременна обработка. Забележете също така мимоходом, че свойството на атомната транзакция не казва нищо за никакви гаранции за четене данни.
Единични изявления
Също така няма нищо особено вединично изявление в SQL Server. Където е изрично съдържаща транзакция (BEGIN TRAN...COMMIT TRAN
) не съществува, един DML израз все още се изпълнява в рамките на транзакция за автоматично извършване. Същите гаранции за ACID се прилагат за едно изявление, както и същите ограничения. По-специално, едно изявление идва без специални гаранции, че данните няма да се променят, докато са в ход.
Помислете за следната заявка за играчка AdventureWorks:
SELECT TH.TransactionID, TH.ProductID, TH.ReferenceOrderID, TH.ReferenceOrderLineID, TH.TransactionDate, TH.TransactionType, TH.Quantity, TH.ActualCost FROM Production.TransactionHistory AS TH WHERE TH.ReferenceOrderID = ( SELECT TOP (1) TH2.ReferenceOrderID FROM Production.TransactionHistory AS TH2 WHERE TH2.TransactionType = N'P' ORDER BY TH2.Quantity DESC, TH2.ReferenceOrderID ASC );
Заявката има за цел да покаже информация за поръчката, която е класирана на първо място по количество. Планът за изпълнение е както следва:
Основните операции в този план са:
- Сканирайте таблицата, за да намерите редове с необходимия тип транзакция
- Намерете идентификатора на поръчката, който сортира най-високо според спецификацията в подзаявката
- Намерете редовете (в същата таблица) с избрания идентификатор на поръчка с помощта на неклъстериран индекс
- Потърсете останалите данни в колоната с помощта на клъстерирания индекс
Сега си представете, че едновременен потребител променя поръчка 495, променяйки нейния тип транзакция от P на W и записва тази промяна в базата данни. За късмет тази модификация преминава, докато нашата заявка изпълнява операцията за сортиране (стъпка 2).
Когато сортирането завърши, търсенето на индекс в стъпка 3 намира редовете с избрания идентификатор на поръчка (който се оказва 495) и ключовото търсене на стъпка 4 извлича останалите колони от основната таблица (където типът на транзакцията сега е W) .
Тази последователност от събития означава, че нашата заявка дава очевидно невъзможен резултат:
Вместо да се намират поръчки с тип транзакция P като посочената заявка, резултатите показват тип транзакция W.
Основната причина е ясна:нашата заявка имплицитно предполагаше, че данните не могат да се променят, докато нашата заявка с един израз е в ход. Прозорецът на възможност в този случай беше сравнително голям поради блокиращото сортиране, но едно и също състояние на състезанието може да възникне на всеки етап от изпълнение на заявката, най-общо казано. Естествено, рисковете обикновено са по-високи при повишени нива на едновременни модификации, по-големи таблици и когато блокиращите оператори се появяват в плана на заявката.
Друг постоянен мит в същата обща област е, че MERGE
е за предпочитане пред отделен INSERT
, UPDATE
и DELETE
изрази, защото единичният израз MERGE
е атомен. Това е глупост, разбира се. Ще се върнем към този вид разсъждения по-късно в поредицата.
Общото послание в този момент е, че освен ако не се предприемат изрични стъпки, за да се гарантира друго, редовете с данни и индексните записи могат да се променят, преместват позицията или да изчезнат изцяло по всяко време по време на процеса на изпълнение. Мислена картина на постоянна и произволна промяна в базата данни е добра, която трябва да имате предвид, докато пишете T-SQL заявки.
Свойството на консистентност
Втората дума от акронима ACID също има редица възможни интерпретации. В база данни на SQL Server последователността означава само че транзакцията оставя базата данни в състояние, което не нарушава никакви активни ограничения. Важно е напълно да се оцени колко ограничено е това твърдение:Единствените ACID гаранции за целостта на данните и логическата последователност са тези, предоставени от активни ограничения.
SQL Server предоставя ограничен набор от ограничения за налагане на логическа цялост, включително PRIMARY KEY
, FOREIGN KEY
, CHECK
, UNIQUE
и NOT NULL
. Всички те са гарантирани, че ще бъдат изпълнени в момента на извършване на транзакцията. Освен това SQL Server гарантира физическото целостта на базата данни по всяко време, разбира се.
Вградените ограничения не винаги са достатъчни, за да наложат всички правила за бизнес и целостта на данните, които бихме искали. Със сигурност е възможно да бъдете креативни със стандартните съоръжения, но те бързо стават сложни и могат да доведат до съхранение на дублирани данни.
В резултат на това повечето реални бази данни съдържат поне някои T-SQL рутинни програми, написани за прилагане на допълнителни правила, например в съхранени процедури и тригери. Отговорността за правилното функциониране на този код се носи изцяло от автора – свойството Consistency не предоставя специфични защити.
За да подчертаем въпроса, псевдоограниченията, написани в T-SQL, трябва да се изпълняват правилно, независимо какви едновременни модификации може да се случват. Разработчикът на приложение може да защити чувствителна операция като тази с оператор за заключване. Най-близкото нещо, което T-SQL програмистите имат до това средство за рискови съхранени процедури и код за задействане, е сравнително рядко използвания sp_getapplock
системна съхранена процедура. Това не означава, че това е единствената или дори предпочитана опция, просто съществува и може да бъде правилният избор при някои обстоятелства.
Свойството на изолацията
Това лесно е най-често неразбраната от свойствата на транзакциите на ACID.
По принцип енапълно изолиран транзакцията се изпълнява като единствената задача, която се изпълнява срещу базата данни през нейния живот. Други транзакции могат да започнат само след като текущата транзакция приключи напълно (т.е. ангажименти или връщане назад). Изпълнена по този начин, транзакцията наистина би била атомна операция , в строгия смисъл, който човек без база данни би приписал на фразата.
На практика транзакциите в базата данни работят вместо това с степен на изолация посочено от действащото в момента ниво на изолация на транзакциите (което важи еднакво и за самостоятелните оператори, запомнете). Този компромис (степента на изолация) е практическата последица от компромисите между едновременността и коректността, споменати по-рано. Система, която буквално обработва транзакции една по една, без припокриване във времето, би осигурила пълна изолация, но общата пропускателна способност на системата вероятно би била лоша.
Следващия път
Следващата част от тази поредица ще продължи разглеждането на проблемите с едновременността, свойствата на ACID и изолирането на транзакциите с подробен поглед върху нивото на изолация, което може да се сериализира, друг пример за нещо, което може да не означава това, което смятате, че прави.
[ Вижте индекса за цялата серия ]