Попросили не використовувати транзакції та використовувати обхід, щоб імітувати першу


43

Я розробляв T-SQL протягом декількох років і постійно копаюсь далі, продовжуючи вивчати все, що можу, про всі аспекти мови. Нещодавно я почав працювати в новій компанії і отримав те, що, на мою думку, є дивною пропозицією щодо транзакцій. Ніколи не використовуйте їх. Натомість використовуйте обхідне рішення, яке імітує транзакцію. Це відбувається від нашої DBA, яка працює в одній базі даних з великою кількістю транзакцій, а згодом і багато блокує. База даних, в якій я в основному працюю, не страждає від цього питання, і я бачу, що транзакції використовувалися в минулому.

Я розумію, що блокування очікується при транзакціях, оскільки це за своєю суттю, і якщо ви можете піти, не скориставшись жодним способом, зробіть це. Але я маю багато випадків, коли кожне твердження ПОВИНЕН успішно виконати. Якщо один з них не вдається, всі вони повинні не виконати.

Я завжди зберігав сферу моїх транзакцій якомога вужче, завжди використовувався разом із SET XACT_ABORT ON і завжди в межах TRY / CATCH.

Приклад:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Ось що вони запропонували мені зробити.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

Моє запитання до громади таке. Чи має це сенс життєздатне рішення для операцій?

Моя думка, що я знаю про транзакції і що пропонує рішення, - це те, що ні, це не є життєздатним рішенням і вводить багато пунктів провалу.

У запропонованому вирішенні я бачу чотири неявні транзакції, що відбуваються. Дві вставки в спробу, а потім ще дві транзакції для делетів в улові. Він робить "скасування" вставок, але не відкочуючи нічого, тому насправді нічого не відкочується.

Це дуже базовий приклад для демонстрації концепції, яку вони пропонують. Деякі з фактично збережених процедур, які я робив це, роблять їх вичерпно довгими і складними в управлінні, оскільки "відкат" декількох наборів результатів проти двох значень параметрів у цьому прикладі стає досить складним, як ви могли собі уявити. Оскільки "відкат" зараз робиться вручну, можливість щось пропустити, бо справжнє.

Інше питання, яке, на мою думку, існує - це час та розриви з'єднань. Це все-таки повертається назад? Це моє розуміння того, чому слід використовувати SET XACT_ABORT ON, щоб у цих випадках транзакція поверталася назад.

Дякуємо за ваш відгук заздалегідь!


4
Коментарі, які не відповідають зазначеному призначенню , були видалені або переміщені до відповіді Wiki Community.
Пол Білий

Відповіді:


61

Ви не можете НЕ використовувати транзакції в SQL Server (і , можливо , будь-який інший власне СУБД). За відсутності явних меж транзакції ( begin transaction... commit) кожен оператор SQL починає нову транзакцію, яка неявно вчиняється (або відкочується назад) після завершення (або відмови) оператора.

Моделювання транзакцій, запропоноване особою, яка представляє себе як "DBA", не забезпечує три з чотирьох необхідних властивостей обробки транзакцій, оскільки воно стосується лише "м'яких" помилок і не здатне боротися з "жорсткими" помилками, наприклад, відключення мережі, відключення електроенергії, відмови диска тощо.

  • Атомність: невдача. Якщо "жорстка" помилка трапиться десь посередині вашої псевдо-транзакції, зміна буде неатомарною.

  • Консистенція: невдача. З вищевикладеного випливає, що ваші дані опинятимуться в непослідовному стані після помилки "жорсткого".

  • Ізоляція: невдача. Можливо, що паралельна псевдо-транзакція змінює деякі дані, змінені вашою псевдо-транзакцією до завершення вашої.

  • Довговічність: успіх. Внесені вами зміни будуть довговічними, сервер бази даних забезпечить це; це єдине, що підхід вашого колеги не може накрутити.

Блоки - це широко використовуваний і емпірично успішний метод для забезпечення кислотності транзакцій різного роду або RDBMS (цей веб-сайт є прикладом). Я вважаю дуже малоймовірним, що випадкова DBA може запропонувати краще рішення проблеми паралельності, ніж сотні, можливо, тисячі комп'ютерних вчених та інженерів, які будували цікаві системи баз даних протягом останнього, що, 50? 60 років? (Я усвідомлюю, що це дещо помилково як аргумент "звернення до влади", але я його дотримуватимусь незалежно.)

На закінчення проігноруйте поради вашої "DBA", якщо можете, боріться з нею, якщо у вас є дух, і повертайтеся сюди зі специфічними проблемами одночасності, якщо вони виникнуть.


14

Є деякі настільки серйозні помилки, що блок CATCH ніколи не вводиться. З документації

Помилки з вираженістю 20 або більше, які зупиняють обробку завдань SQL Server Database Engine для сеансу. Якщо виникла помилка, що має ступінь тяжкості 20 або вище, а з'єднання з базою даних не порушено, Спробуйте ... CATCH буде обробляти помилку.

Уваги, такі як запити на переривання клієнта або порушені клієнтські з'єднання.

Коли сеанс закінчується системним адміністратором за допомогою оператора KILL.

...

Помилки компіляції, такі як синтаксичні помилки, що запобігають запуску пакету.

Помилки, які виникають ... через відкладене дозвіл імені.

Багато з них легко виготовити за допомогою динамічного SQL. Скасування заяв, таких як ви показали, не захистить ваші дані від таких помилок.


2
Правильно - і якщо нічого іншого, клієнт, що гине під час виконання коду, представляв би помилку, "настільки серйозну, що блок CATCH ніколи не вводиться". Незалежно від того, наскільки ви довіряєте програмному забезпеченню (не лише своєму власному коду, але КОЖНІЙ частині ВСІХ пакетів програмного забезпечення), завжди є можливість апаратного збою (знову ж, можливо, у будь-якій точці ланцюга), що зупинить вас холодно в будь-який момент . Маючи це на увазі - це хороший захист від доброго мислення, яке призводить до подібного "вирішення".
dgould

2
Також ви можете стати жертвою тупика. Ваші блоки CATCH запускаються, але кидаються, якщо вони намагаються записати в базу даних.
Джошуа

10

i-one : Пропоноване вам рішення дозволяє (принаймні) порушити "A" кислоти . Наприклад, якщо SP виконується віддаленим клієнтом і розрив з'єднання, то може відбутися часткове "фіксування" / "відкат", оскільки сервер може припинити сеанс між двома вставками / видаленнями (і скасувати виконання SP до того, як він досягне свого кінця) .

Чи має це сенс життєздатне рішення для операцій?

dan-guzman : Ні,CATCHблок ніколи не виконується у випадку очікування запиту, оскільки API клієнта скасував пакет. Без транзакціїSET XACT_ABORT ONне можна відкатати нічого, крім поточного твердження.

tibor-karaszi : У вас є 4 транзакції, тобто більше входу в файл журналу транзакцій. Пам’ятайте, що кожна транзакція вимагає синхронного запису записів журналу до цього моменту, тобто ви погіршуєте продуктивність також з цього аспекту при використанні багатьох транзакцій.

rbarryyoung : Якщо у них виникає велика кількість блокувань, їм потрібно або виправити дизайн даних, раціоналізувати порядок доступу до таблиці, або використовувати більш відповідний рівень ізоляції. Вони припускають, що їх проблеми (і нерозуміння) стануть вашою проблемою. Доказів мільйонів інших баз даних є те, що цього не буде.

Крім того, те, що вони намагаються вручну реалізувати, - це фактично оптимістична паралельність бідних людей. Замість цього вони повинні використовувати деякі найкращі оптимістичні паралелі у світі, вже вбудовані в SQL Server. Це йде до точки ізоляції вище. Ймовірно, їм потрібно перейти з будь-якого песимістичного рівня ізоляції одночасності, який вони зараз використовують, до одного з оптимістичних рівнів ізоляції одночасності, SNAPSHOTабо READ_COMMITTED_SNAPSHOT. Вони будуть ефективно робити те ж саме, що і їх ручний код, за винятком того, що він буде робити це правильно.

ross-presser : Якщо у вас надзвичайно тривалі процеси - як щось відбувається сьогодні, і на наступному тижні щось має продовжуватися, а якщо наступного тижня справа виходить з ладу, сьогоднішній день повинен вийти з ладу заднім числом - ви можете заглянути в саги . Строго кажучи, це знаходиться поза базою даних, оскільки для цього потрібна службова шина.


5

Код поганої ідеї просто буде дорожчим виправити лінію.

Якщо виникають проблеми з блокуванням використання явної транзакції (відкат / фіксація), вкажіть свою DBA в Інтернет, щоб отримати кілька чудових ідей для вирішення проблем.

Ось спосіб полегшити блокування: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transaction

Індекси зменшують кількість запитів, які повинні відбутися в таблиці / сторінці, щоб знайти рядок / набір рядків. Вони, як правило, розглядаються як метод скорочення часу виконання запитів SELECT * і правильно. Вони не вважаються придатними для таблиць, задіяних у великій кількості ОНОВЛЕННЯ. Насправді в цих випадках індекси виявляються несприятливими, оскільки вони збільшують час, необхідний для завершення UPDATE запитів.

Але це не завжди так. Поглиблюючись трохи глибше у виконанні оператора UPDATE, ми виявляємо, що воно також включає спочатку виконання оператора SELECT. Це особливий і часто зустрічається сценарій, коли запити оновлюють взаємовиключні набори рядків. ІНДЕКСИ тут можуть призвести до значного збільшення продуктивності двигуна бази даних всупереч поширеній думці.


4

Підроблена стратегія транзакцій небезпечна тим, що дозволяє виправити проблеми одночасності, які транзакції спеціально запобігають. Зверніть увагу, що у другому прикладі будь-які дані можуть бути змінені між операторами.

Підроблені видалення транзакцій не гарантуються для запуску чи успіху. Якщо сервер бази даних відключається під час підробленої транзакції, деякі, але не всі ефекти залишаться. Вони також не гарантують успіх таким чином, як відкат транзакцій.

Ця стратегія може працювати зі вставками, але, безумовно, не працюватиме з оновленнями або видаленнями (без операторів SQL часу).

Якщо сувора одночасність транзакцій викликає блокування, існує безліч рішень, навіть тих, що знижують рівень захисту ... це правильний спосіб вирішити проблему.

Ваш DBA пропонує рішення, яке може спрацювати нормально, якби був лише один користувач бази даних, але абсолютно непридатний для будь-якого серйозного використання.


4

Це не питання програмування, скоріше це питання міжособистісного / неправильного спілкування. Швидше за все, ваша "DBA" турбується про блокування, а не транзакції.

Інші відповіді вже пояснюють, чому ви повинні використовувати транзакції ... Я маю на увазі, що це робить RDBMS, без належних використаних транзакцій немає цілісності даних, тому я зосередитимусь на тому, як вирішити справжню проблему, а саме: з’ясуйте, чому ваш "DBA" розвинув алергію на транзакції і переконав його передумати.

Я думаю, що цей хлопець плутає "певний сценарій, коли поганий код призводив до жахливої ​​продуктивності", і "всі транзакції погані". Я б не очікував, що компетентна DBA зробить цю помилку, тому це справді дивно. Можливо, у нього був справді поганий досвід із якимсь жахливим кодом?

Розглянемо такий сценарій:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Цей стиль використання транзакцій містить блокування (або кілька блокувань), що означає, що інші транзакції, що потрапляють у ті самі рядки, повинні будуть почекати. Якщо блокування зберігається тривалий час, і особливо якщо багато інших транзакцій хочуть заблокувати ті самі рядки, це може дійсно зашкодити їх продуктивності.

Що ви можете зробити, це запитати у нього, чому у нього таке цікаво неправильне уявлення про використання транзакцій, які типи запитів були проблематичними тощо. Потім спробуйте переконати його, що ви обов'язково уникнете подібних поганих сценаріїв, що ви будете контролювати використання свого блокування та виступ, заспокоїти його тощо.

Те, що він вам каже, - "не торкайтеся викрутки!" тож код, який ви опублікували у своєму запитанні, в основному використовує молоток для забивання гвинта. Набагато кращий варіант - переконати його, що ви знаєте, як користуватися викруткою ...

Я можу припустити кілька прикладів ... ну, вони були на MySQL, але це теж повинно працювати.

Був форум, на якому повний текстовий покажчик потребував певного часу, щоб оновити. Коли користувач надсилає публікацію, транзакція оновлює таблицю тем, щоб збільшити кількість публікацій та останню дату публікації (таким чином блокуючи рядок теми), потім вставивши публікацію, і транзакція буде тримати блокування до повного оновлення індексу повного тексту. і COMMIT було зроблено.

Оскільки це було запущено на rstbucket із занадто малою оперативною пам’яттю, оновлення зазначеного індексу повного тексту часто призводило до декількох секунд інтенсивного випадкового вводу-виводу на одному повільному обертовому диску в коробці.

Проблема полягала в тому, що люди, які натиснули тему, викликали запит, щоб збільшити кількість переглядів по темі, що також вимагало блокування в рядку тем. Таким чином, ніхто не міг переглянути тему, поки її індекс повного тексту оновлювався. Я маю на увазі, рядок можна прочитати, але оновлення буде заблоковано.

Ще гірше, що опублікування оновило б кількість записів у таблиці батьківських форумів, а також утримуватиме блокування, коли індекс повного тексту оновлюється ... що заморозило весь форум на кілька секунд і призвело до безлічі запитів нагромадження у черзі веб-сервера .

Рішення полягало в тому, щоб зробити блокування у правильному порядку: BEGIN, вставити публікацію та оновити індекс повного тексту, не беручи жодних блокувань, а потім швидко оновити рядки теми / форуму з кількістю публікацій та датою останньої публікації та COMMIT. Це повністю вирішило проблему. Це було просто пересування декількох запитів, дійсно простих.

У цьому випадку транзакції не були проблемою ... Набуття непотрібного блокування перед тривалою операцією. Інші приклади матеріалів, яких слід уникати, тримаючи блокування транзакції: очікування введення користувачем, доступ до безлічі кешованих даних із повільнообертових накопичувачів, мережевого вводу даних тощо.

Звичайно, іноді у вас немає вибору, і вам доводиться робити довгу обробку, тримаючи громіздкі замки. Навколо цього є хитрощі (оперувати копією даних тощо), але досить часто вузьке місце продуктивності виникає із блокування, яке не було навмисно придбане, і просто переупорядкування запитів вирішує проблему. Ще краще - бути обізнаним про замки, зроблені під час написання запитів ...

Інших відповідей я не повторюю, але справді ... використовую транзакції. Ваша проблема - переконати свою "DBA", не обробляючи найважливішої функції бази даних ...


3

TLDR: Використовуйте належний рівень ізоляції .

Як ви правильно помітили підхід без транзакцій та з "ручним" відновленням, може бути дуже складним. Висока складність означає зазвичай набагато більше часу на її реалізацію та набагато більше часу на виправлення помилок (адже складність призводить до більшої кількості помилок у реалізації). Це означає, що такий підхід може коштувати вашому клієнту значно дорожче.

Основна турбота вашого колеги "dba" - це продуктивність. Один із способів її покращення - це використання належного рівня ізоляції. Припустимо, у вас є процедура, яка надає користувачеві певні оглядові дані. Для такої процедури не обов'язково використовувати СЕРІАЛІЗАЦІЙНИЙ рівень ізоляції. У багатьох випадках ЧИТАЙТЕ НЕЗАБАВЛЕНО може бути цілком достатньо. Це означає, що така процедура не буде заблокована вашою транзакцією, яка створює або змінює деякі дані.

Я б запропонував переглянути всі існуючі функції / процедури у вашій базі даних, оцінити розумний рівень ізоляції для кожної, пояснити переваги від продуктивності для вашого клієнта. Потім відповідно налаштуйте ці функції / процедури.


2

Ви також можете вирішити використовувати таблиці OLTP In-Memory. Вони, звичайно, все ще використовують транзакції, але блокування не бере участь.
Замість блокування всі операції будуть успішними, але під час фази фіксації двигун перевірить на конфлікти транзакцій, і одна з команд може вийти з ладу. Microsoft використовує термін "Оптимістичне блокування".
Якщо проблема масштабування викликана конфліктом між двома операціями запису, такими як дві одночасні транзакції, що намагаються оновити один і той же рядок, InTP-пам'ять OLTP дозволяє одній транзакції досягти успіху, а іншу транзакцію не вдасться. Невдала транзакція повинна бути повторно подана явно або неявно, повторно спробувавши транзакцію.
Детальніше на: OLTP пам'яті


-5

Існує спосіб обмеженого використання транзакцій, а саме шляхом зміни вашої моделі даних, щоб бути більш орієнтованою на об'єкти. Отже, замість того, щоб, наприклад, зберігати демографічні дані про людину в декількох таблицях і пов'язувати їх між собою і вимагати транзакцій, ви могли б мати один документ JSON, який зберігає все, що ви знаєте про цю людину, в одному полі. Звичайно, розробка далекого розширення домену - це ще одна проблема дизайну, найкраще виконана розробниками, а не DBA

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.