Я занимаюсь разработкой T-SQL в течение нескольких лет и постоянно копаюсь, продолжая изучать все, что могу, по всем аспектам языка. Недавно я начал работать в новой компании и получил, как мне кажется, странное предложение относительно транзакций. Никогда не используйте их. Вместо этого используйте обходной путь, имитирующий транзакцию. Это исходит от нашего администратора базы данных, который работает в одной базе данных с большим количеством транзакций и, следовательно, с большим количеством блокировок. База данных, в которой я в основном работаю, не страдает от этой проблемы, и я вижу, что транзакции использовались в прошлом.
Я понимаю, что блокирование ожидается с транзакциями, так как это по своей природе это сделать, и если вы можете уйти, не используя одну, обязательно сделайте это. Но у меня есть много случаев, когда каждое утверждение ДОЛЖНО выполняться успешно. Если кто-то терпит неудачу, они все должны потерпеть неудачу.
Я всегда держал область своих транзакций как можно более узкой, всегда используемой вместе с 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, чтобы в этих случаях транзакция откатывалась.
Спасибо за ваш отзыв заранее!
источник
Ответы:
Вы не можете не использовать транзакции в SQL Server (и , возможно , любой другой собственно СУБД). При отсутствии явных границ транзакции (
begin transaction
...commit
) каждый оператор SQL начинает новую транзакцию, которая неявно фиксируется (или откатывается) после того, как инструкция завершается (или не выполняется).Имитация транзакций, предложенная человеком, который представляет себя вашим «администратором базы данных», не обеспечивает три из четырех необходимых свойств обработки транзакций, поскольку он предназначен только для «мягких» ошибок и не способен справляться с «жесткими» ошибками, такие как отключение от сети, перебои с питанием, сбои дисков и т. д.
Атомность: сбой. Если где-то в середине вашей псевдотранзакции возникает «жесткая» ошибка, изменение будет неатомарным.
Последовательность: не удалось. Из вышесказанного следует, что ваши данные будут в несогласованном состоянии после «жесткой» ошибки.
Изоляция: сбой. Возможно, что параллельная псевдо-транзакция изменяет некоторые данные, измененные вашей псевдо-транзакцией, до того, как ваша завершится.
Прочность: успех. Внесенные вами изменения будут долговременными, сервер базы данных обеспечит это; это единственное, чего не может испортить подход вашего коллеги.
Блокировки являются широко используемым и эмпирически успешным методом обеспечения ACIDity транзакций во всех видах или СУБД (этот сайт является примером). Я считаю очень маловероятным, что случайный администратор баз данных сможет найти лучшее решение проблемы параллелизма, чем сотни, возможно, тысячи компьютерных ученых и инженеров, которые за последние годы создавали интересные системы баз данных, что, 50? 60 лет? (Я понимаю, что это несколько ошибочно в качестве аргумента «апелляции к власти», но я буду придерживаться этого независимо от этого.)
В заключение, не обращайте внимания на советы вашего «администратора баз данных», если можете, сражайтесь с ними, если у вас есть дух, и возвращайтесь сюда с конкретными проблемами параллелизма, если они возникнут.
источник
Есть некоторые ошибки, которые настолько серьезны, что блок CATCH никогда не вводится. Из документации
Многие из них легко создать с помощью динамического SQL. Отмена таких заявлений, как вы показали, не защитит ваши данные от таких ошибок.
источник
i-one : Предлагаемый вам обходной путь делает возможным (по крайней мере) нарушение "A" ACID . Например, если SP выполняется удаленным клиентом и разрывается соединение, то может произойти частичная «фиксация» / «откат», поскольку сервер может прервать сеанс между двумя вставками / удалениями (и прервать выполнение SP, прежде чем он достигнет своего конца) ,
dan-guzman : Нет,
CATCH
блок никогда не выполняется в случае тайм-аута запроса, потому что клиентский API отменил пакет. Без транзакцииSET XACT_ABORT ON
не может откатить ничего, кроме текущего оператора.tibor-karaszi : у вас есть 4 транзакции, что означает больше регистрации в файле журнала транзакций. Помните, что для каждой транзакции требуется синхронная запись записей журнала до этого момента, т.е. при использовании многих транзакций производительность также ухудшается.
rbarryyoung : если они получают много блокировок, им нужно либо исправить свой дизайн данных, рационализировать порядок доступа к таблицам, либо использовать более подходящий уровень изоляции. Они предполагают, что их проблемы (и непонимание этого) станут вашей проблемой. Доказательства из миллионов других баз данных, что это не так.
Кроме того, то, что они пытаются реализовать вручную, - это оптимистичный параллелизм бедняков. Вместо этого им следует использовать лучший в мире оптимистический параллелизм, уже встроенный в SQL Server. Это идет к точке изоляции выше. По всей вероятности, им необходимо переключиться с любого уровня пессимистической изоляции параллелизма, который они используют в настоящее время, на один из уровней изоляции оптимистичного параллелизма
SNAPSHOT
илиREAD_COMMITTED_SNAPSHOT
. Они будут делать то же самое, что и их ручной код, за исключением того, что они будут делать это правильно.ross-presser : Если у вас очень долго запущенные процессы - например, что-то происходит сегодня, а на следующей неделе что-то должно последовать, а если что-то не получится на следующей неделе, то сегодня должно произойти сбой задним числом - вы можете захотеть взглянуть на саги . Строго говоря, это за пределами базы данных, так как требует служебной шины.
источник
Плохой код идеи будет стоить дороже, чтобы исправить это.
Если есть проблемы с блокировкой при использовании явной транзакции (откат / принятие), направьте своего администратора базы данных в Интернет, чтобы найти отличные идеи для решения этих проблем.
Вот способ облегчить блокировку: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions
источник
Стратегия поддельных транзакций опасна, потому что она допускает проблемы параллелизма, которые транзакции специально предотвращают. Учтите, что во втором примере любые данные могут быть изменены между операторами.
Поддельные удаления транзакций не гарантируются для запуска или успеха. Если сервер базы данных отключится во время фиктивной транзакции, некоторые, но не все эффекты останутся. Они также не гарантируют успешное выполнение, скажем, отката транзакции.
Эта стратегия может работать со вставками, но определенно не будет работать с обновлениями или удалениями (без SQL-операторов времени).
Если строгий параллелизм транзакций вызывает блокировку, существует множество решений, даже таких, которые снижают уровень защиты ... это правильный способ решения проблемы.
Ваш администратор БД предлагает решение, которое могло бы работать нормально, если бы был только один пользователь базы данных, но абсолютно не подходит для любого серьезного использования.
источник
Это не проблема программирования, скорее это проблема межличностного общения / недопонимания. Скорее всего, ваш «администратор баз данных» беспокоится о блокировках, а не транзакциях.
Другие ответы уже объясняют, почему вы должны использовать транзакции ... Я имею в виду, что это то, что делает СУБД, без правильно используемых транзакций нет целостности данных, поэтому я сосредоточусь на том, как решить реальную проблему, а именно: выяснить, почему У вашего «DBA» развилась аллергия на сделки и убедите его передумать.
Я думаю, что этот парень путает «особый сценарий, когда плохой код приводит к ужасной производительности» с «все транзакции плохие». Я бы не ожидал, что компетентный администратор сделает эту ошибку, так что это действительно странно. Может быть, у него был действительно плохой опыт с каким-то ужасным кодом?
Рассмотрим такой сценарий:
Этот стиль использования транзакций содержит блокировку (или несколько блокировок), что означает, что другие транзакции, попавшие в те же строки, должны будут ждать. Если блокировки удерживаются в течение длительного времени, и особенно если множество других транзакций хотят заблокировать одни и те же строки, это может серьезно снизить производительность.
Что вы можете сделать, это спросить его, почему у него странное неверное представление о том, что он не использует транзакции, какие типы запросов были проблемными и т. Д. Затем попытайтесь убедить его, что вы обязательно избежите подобных плохих сценариев, что вы будете следить за использованием блокировок и производительность, успокоить его и т. д.
То, что он говорит вам, это "не трогай отвертку!" поэтому код, который вы разместили в своем вопросе, в основном использует молоток для привода винта. Гораздо лучший вариант - убедить его, что вы умеете пользоваться отверткой ...
Я могу вспомнить несколько примеров ... ну, они были на MySQL, но это тоже должно работать.
Был форум, где обновление полнотекстового индекса заняло некоторое время. Когда пользователь отправлял сообщение, транзакция обновляла таблицу тем, чтобы увеличить количество сообщений и дату последнего сообщения (таким образом, блокируя строку темы), затем вставляла сообщение, и транзакция удерживала блокировку, пока полнотекстовый индекс не закончил обновление и коммит был сделан.
Так как он работал на ржавой корзине со слишком малым объемом оперативной памяти, обновление указанного полнотекстового индекса часто приводило к нескольким секундам интенсивного случайного ввода-вывода на одном медленно вращающемся диске в коробке.
Проблема заключалась в том, что люди, щелкнувшие по этой теме, вызывали запрос на увеличение количества просмотров по этой теме, что также требовало блокировки строки темы. Таким образом, никто не мог просматривать тему, пока обновлялся ее полнотекстовый индекс. Я имею в виду, что строку можно прочитать, но ее обновление заблокирует.
Хуже того, публикация обновит счетчик сообщений в родительской таблице форумов, а также удержит блокировку во время обновления полнотекстового индекса ... что заморозило весь форум на несколько секунд и вызвало кучу запросов в очереди веб-сервера. ,
Решение состояло в том, чтобы взять блокировки в правильном порядке: НАЧАТЬ, вставить сообщение и обновить полнотекстовый индекс без каких-либо блокировок, а затем быстро обновить строки темы / форума, указав количество сообщений и дату последнего сообщения, и COMMIT. Это полностью решило проблему. Он просто двигался по нескольким запросам, действительно просто.
В этом случае транзакции не были проблемой ... Он получал ненужную блокировку перед длительной операцией. Другие примеры вещей, которых следует избегать при удержании блокировки в транзакции: ожидание пользовательского ввода, доступ к большому количеству некэшированных данных с медленно вращающихся дисков, сетевой ввод-вывод и т. Д.
Конечно, иногда у вас нет выбора, и вы должны выполнять длительную обработку, удерживая громоздкие блокировки. Есть хитрости вокруг этого (работа с копией данных и т. Д.), Но довольно часто узкое место в производительности возникает из-за блокировки, которая не была умышленно получена, и простая переупорядочивание запросов решает проблему. Еще лучше знать о блокировках, взятых при написании запросов ...
Я не буду повторять другие ответы, но на самом деле ... использовать транзакции. Ваша проблема заключается в том, чтобы убедить вашего «администратора базы данных», а не обойти наиболее важную функцию базы данных ...
источник
TLDR: используйте надлежащий уровень изоляции .
Как вы правильно заметили, подход без транзакций и с «ручным» восстановлением может быть очень сложным. Высокая сложность обычно означает гораздо больше времени для ее реализации и гораздо больше времени для исправления ошибок (поскольку сложность приводит к большему количеству ошибок в реализации). Это означает, что такой подход может стоить вашему клиенту намного дороже.
Основная забота вашего коллеги из dba - производительность. Один из способов улучшить это - использовать надлежащий уровень изоляции. Предположим, у вас есть процедура, предоставляющая пользователю какие-то обзорные данные. Такая процедура не обязательно должна использовать SERIALIZABLE уровень изоляции. Во многих случаях READ UNCOMMITTED может быть вполне достаточно. Это означает, что такая процедура не будет заблокирована вашей транзакцией, которая создает или изменяет некоторые данные.
Я бы посоветовал вам просмотреть все существующие функции / процедуры в вашей базе данных, оценить разумный уровень изоляции для каждой из них, объяснить преимущества производительности для вашего клиента. Затем настройте эти функции / процедуры соответствующим образом.
источник
Вы также можете решить использовать таблицы In-Memory OLTP. Они, конечно, все еще используют транзакции, но блокировка не предусмотрена.
Вместо блокировки все операции будут успешными, но на этапе фиксации механизм проверит конфликты транзакций, и одна из фиксаций может завершиться неудачей. Microsoft использует термин «Оптимистическая блокировка».
Если проблема масштабирования вызвана конфликтом между двумя операциями записи, такими как две параллельные транзакции, пытающиеся обновить одну и ту же строку, In-Memory OLTP позволяет одной транзакции завершиться успешно, а другая транзакция не удалась. Неудачная транзакция должна быть повторно отправлена явно или неявно, повторная попытка транзакции.
Больше на: в памяти OLTP
источник
Существует несколько способов использования транзакций в ограниченном объеме, а именно изменение модели данных, чтобы она была более объектно-ориентированной. Поэтому вместо того, чтобы хранить, например, демографические данные о человеке в нескольких таблицах и связывать их друг с другом и требовать транзакций, вы могли бы иметь один документ JSON, в котором все, что вы знаете об этом человеке, хранится в одном поле. Конечно, далеко не всегда решается, что домены растягиваются - это еще одна проблема проектирования, которую лучше всего делать разработчикам, а не администраторам баз данных.
источник