Pull to refresh

Comments 44

Спасибо за статью, почерпнул для себя много нового
«Встречали ли вы в C# конструкцию типа using (var scope = new TransactionScope(TransactionScopeOption.Required))? Это значит, что код, выполняющийся в блоке using, заключается в транзакцию и после выхода из этого блока изменения будут зафиксированы или отменены.»

Это значит, что у нас есть экземпляр класса TransactionScope из неопределенного namespace, который гарантированно имплементирует IDisposable и на котором гарантированно будем вызван метод Dispose при выходе из блока using по любой причине. А вот на все остальное мы только надеемся, полагаясь, что авторы внутренностей блока знали, что делают… ;-)
Отличная статья, если хочется получить много теоретических знаний по теме.

Дополню своей практикой:

1) Хорошо, что в статье есть ссылка на другую, про подводные камни. Но ещё лучше было бы и здесь подчеркнуть, что по умолчанию TransactionScope использует Serializable. Интересно, многие ли про это знают? Была бы моя статья — запилил бы опрос :)

Возможно, вам будут говорить, что это неважно и Serializable — отличный выбор. Что же, у меня другое мнение, но кто я такой, чтобы его навязывать? :)

2) Если вдруг у вас в .NET приложении для MS SQL использовались разные уровни изоляции (например, вы в Transact-SQL написали SET TRANSACTION ISOLATION LEVEL SNAPSHOT) и используется connection pool (а он используется по умолчанию) вас ждёт сюрприз (хм, «сюр приз» — раньше не задумывался).

Дело в том, что MS SQL (не буду ничего утверждать про другие СУБД) молча вернёт connection в пул с текущим уровнем изоляции. Подробнее об этом.

Сознательно не привожу более простой пример, когда есть код внутри TransactionScope и вне его (по умолчанию он будет Read Comitted) — хочу спать и не хочу проверять, будут ли на свежем .NET с этим проблемы :)

3) Вот это утверждение расслабляет читателей:

Использование транзакций — дело не сложное, и заключается оно в обертывании кода в блок using с определенными характеристиками. Однако включаемые таким образом в транзакцию управляющие ресурсами должны поддерживать требуемую функциональность со своей стороны.

По факту, даже если забыть про предыдущие два пункта, получаем другую потенциальную проблему. Вот вызвали вы несколько разных методов, который в одном потоке работают с одной и той же БД, но используют чуть разные строки соединения. Здравствуй, распределённая транзакция. Disclaimer: фиг знает, может уже изменили поведение.

4) MS DTC лучше вообще не использовать, если есть такая возможность. Но это совсем другая история…

Резюме: TransactionScope — основательно дырявая абстракция. Так что пользоваться им можно, но отлично понимая последствия.
по умолчанию TransactionScope использует Serializable

Здравствуй, распределённая транзакция

Когда первая половина участников успела закомиттиться, а вторая — нет


Дописал несколько слов по каждой указанной выше теме.

Дело в том, что MS SQL (не буду ничего утверждать про другие СУБД) молча вернёт connection в пул с текущим уровнем изоляции


Правильно я понял, что если мы создаём внутри TransactionScope соединение впервые, то оно создаётся с нужным нам уровнем изоляции? По окончании использования это соединение помещается в пул с уже изменённым уровнем изоляции (если уровень был изменён, понятное дело). Если в следующий раз в TransactionScope будет не создаваться новое соединение, а будет браться из пула вот это первое, то мы получим соединение с уровнем изоляции, отличающимся от желаемого. В этом и состоит проблема.
Если в следующий раз в TransactionScope будет не создаваться новое соединение, а будет браться из пула вот это первое, то мы получим соединение с уровнем изоляции, отличающимся от желаемого. В этом и состоит проблема.


Проблема точно была в том, что если есть код работающий с БД вне TransactionScope, то ему может приехать из пула уровень Serializable, хотя разработчик рассчитывал на Read Committed. TransactionScope устанавливает по умолчанию Serializable принудительно, с ним вряд ли будут проблемы.

И основная-то проблема в том, что из тех разработчиков, которых я знаю (а, скорее всего и тех, кого не знаю), большинство использует дефолтные настройки — а это, при использовании TransactionScope, уже лотерея.
Понятно. Добавил в публикацию упоминание этой проблемы.
п.2 — подтверждаю, лично на это напарывались. SQL 2014 работает как ожидается, SQL 2014 + SP2 меняет уровень изоляции при работе с пулом.
п.3 — и на это напарывались, но не с «чуть разные строки соединения», а просто с разными БД в одной TransactionScope и получали что-то вроде этого. Сразу требуется MS DTC, а к нему настройка. Поскольку именно тогда и именно здесь не уперлось — просто растащили на разные scopes.
Однако суть распределенной транзакции заключается как раз в том, что изменения всех участвующих в транзакции управляющих ресурсов либо фиксируются все вместе, либо откатываются.


Не раскрыта тема временных проблем с сетью или падений серверов во время второй фазы. Когда первая половина участников успела закомиттиться, а вторая — нет.
Кратко и по делу. Давно хотел посмотреть, что такое транзакции в WCF, и этой статьёй пунктик закрыт.

В классе Stm«T» есть проблема: каждое присваивание значения регистрирует нового участника в менеджере транзакций, соответственно, после выполнения
for (int i = 0; i < 10; i++) stm.Value = i;
будет 10 раз вызван Commit
Согласен; такой сценарий не предусматривал. Добавил в класс Stm флаг, который показывает, зарегистрирован ли данный экземпляр как участник транзакции.
пользуясь статическим свойством System.Transactions.Transaction.Current


Вот за такие решения хочется по рукам надавать MS. О вреде глобальных переменных видимо там не в курсе. Выше в коментах пишут про паразитные эффекты с пулом конекшенов… наверняка этого можно было избежать если не делать вот такие уродские API с глобальными контекстами.
А это не глобальная переменная, а контекст выполнения.
Глобальный контекст, замечу. Current же где то хранится, и хранится он по сути в глобальной переменной.
К минусам глобальных переменных относят их небезопасность при многопоточном использовании, здесь этой проблемы нет — у каждого потока свой Transaction.Current.

Какие минусы вы видите от способа получения контекста через вызов static-метода? Мне бы, например, было бы неудобно в 100500 методов бизнес-логики добавлять параметр TransactionContext и таскать его между всеми вызовами — это слишком зашумляет код. А потом появится LoggingContext, SecurityContext — все их тоже передавать параметрами?

Что вы думаете о Console.WriteLine? Она же как-то получает хэндлы, не иначе как из статической переменной. Не лучше ли ей явно передавать хендлы на все необходимые ресурсы, а получать их из точки входа в приложение? Какой-то рай для функциональщиков вырисовывается )))
Какие минусы вы видите от способа получения контекста через вызов static-метода? Мне бы, например, было бы неудобно в 100500 методов бизнес-логики добавлять параметр


Ну подумайте сами, какая разница между «один и глобальный» и «много и где угодно».
Если вы только через параметры умеете передавать контекст — ваша беда.
Я бы предпочел от платформы более лаконичный нераздутый апи, чем вот такие сахарные плюшки. Сахарные плюшки я и сам себе устрою хотя бы с помощью многочисленных IoC фреймворков.
TransactionScope не один глобальный, а их много (отдельно на каждый поток, как минимум). Идеология Scope быть единым для операции, поэтому доступ к нему через static-метод неплохое решение. При желании, можно делать транзации вне Scope или вообще что-то своё сделать для управления. Но фреймворк именно типовые операции обеспечивает кратким синтаксисом.

Тут вопрос между удобством синтаксиса и гибкостью (замусоривание сигнатур методов бизнес-логики всякими системными контекстами не есть хорошо).

IoC не сильно поможет, т.к. зависимости он инжектит лишь при создании экземпляров. Накладно будет пересоздавать экземпляры сервисов на каждую транзакцию, чтобы контекст транзакции передать через зависимости в стиле DI.
IoC не сильно поможет, т.к. зависимости он инжектит лишь при создании экземпляров.


У вас какое то сильно узкое понимание IoC. Что мешает сделать мой ламповый

var transac = MyMagicIoC.GetOrDo<Transaction>()

При этом я сам решу будет это одна на тред, или одна на процесс, аппдомен, конекшен…. MyMagicIoC.GetOrDo — статик, доступно из любой точки кода, на всякий случай поясняю.
Что мешает сделать мой ламповый
Использовать вызовы контейнера в сервисе — антипаттерн при работе с DI-контейнером, нужно конфигурировать классы при создании, и чтобы классы не знали о контейнере.
При этом я сам решу будет это одна на тред, или одна на процесс, аппдомен, конекшен
И конкретно для TransactionScope это недостаток, потому что правильный сценарий использования ровно один (более того, программист может не знать всех тонкостей: например, сделав Scope для треда, столкнуться с багами под IIS, потому что тред возвращается в пул, когда вызывается операция, тяжёлая по IO — то же обращение к БД, и продолжается на другом треде).
Использовать вызовы контейнера в сервисе — антипаттерн при работе с DI-контейнером, нужно конфигурировать классы при создании, и чтобы классы не знали о контейнере.


MyMagicIoC и не знает ничего про места вызова… Выше патернов есть еще мозг, который надо включать время от времени.

сделав Scope для треда, столкнуться с багами под IIS


Вы сами себе противоречите. Я то как раз за то чтобы не было никакой связи глобального статик Cuurent ни с текущим тредом, ни с текщим коннекшеном, пулом… Потому что эти связи неявные, плохо документированые, плохо тестируемые.

Ваша религия понятна, спасибо что уделили время.
MyMagicIoC и не знает ничего про места вызова
Зато класс, в котором написана строчка
var transac = MyMagicIoC.GetOrDo<Transaction>()
знает о MyMagicIoC.
Вы сами себе противоречите. Я то как раз за то чтобы не было никакой связи глобального статик Cuurent ни с текущим тредом, ни с текщим коннекшеном, пулом
Так программист должен будет явно задать эти связи, конфигурируя контейнер. И самое простое предположение, связать scope с тредом, оказывается неверным.
Ваша религия понятна, спасибо что уделили время.
Лол )))
Зато класс, в котором написана строчка знает о MyMagicIoC


И? Точно также как ваш код знает сейчас про Current. Точно также как код знает про IoC… Коду в общем то все равно каким способом получить Current. Только в моем случае у меня больше свободы.

Так программист должен будет явно задать эти связи, конфигурируя контейнер


И прекрасно. Это трудно? Явное лучше не явного. Гибкое лучше жесткого. А то как дяди из MS сейчас задали, с получением сюрпризов от пула коннекшенов — это норма по вашему.
А то как дяди из MS сейчас задали, с получением сюрпризов от пула коннекшенов — это норма по вашему.
Фича с пулом никак не связана с получением контекста из static-метода. Точно так же приложение могло бы таскать ссылки на менеджер транзакций (который управляет пулом) и текущий scope через параметры методов, DI-контейнер, или как вам ещё нравится, а этот менеджер складывал бы все коннекты в свой пул без учёта уровня изоляции.
ссылки на менеджер транзакций (который управляет пулом)


Тогда бы вышло что каждый менеджер управляет одним и тем же пулом. Что вообще полная ерунда.
На то он и пул, чтобы шарить ресурсы.
Я вас окончательно не понимаю… Пул тоже может быть глобальным а может локальным инстансом.
Вы действительно не понимаете, что локальность/глобальность и способ достучаться до пула не влияют на этот баг?

В типичном случае приложению нужен 1 менеджер транзакций и 1 пул коннектов. Если приложение запрашивает из пула коннект, делает там транзакцию serializable, возвращает коннект в пул, а затем запрашивает из пула коннект без указания изоляции (на что обычно получало бы read commited, но сейчас получило serializable), то как этой проблеме могла помочь явная передача ссылок на пулы/менеджеры (которых, напоминаю, в простом случае по одному).
В типичном случае приложению нужен 1 менеджер транзакций и 1 пул коннектов.


Кто решать будет какое типичное а какое Атипичное? Как бы потребности у всех разные. Конечно джуниору может и хватит Current и он будет счастлив чтоон так легко доступен. А мне вот например, эта завязка на Current всего и вся рубит на корню желание независимых юнит тестов. И я считаю что забота о «типичных случаях» никак не должна мешать строить самые разные связи между объектами. На то оно и ООП.
Вы ловко меняете тему и уходите от вопроса. Как угодно, если вы не хотите, можно не продолжать.
Не вы один любите вопросы задавать и не отвечать. Я тоже их не получаю.
Глобальными контекстами могут быть только те ресурсы которые и аппаратно и реально глобальны. Как то тред, процесс, домен,… Но вот пул конекшенов глобальный — уже непонять зачем. Ваша отговорка «для типичного аппа» — ну вы еще ни раз не встречали заказчика у которого взгляды полностью противоположны вашим? А юнит тесты требующие независимость окружения для теста? Причин много, чтобы не делать вот таких исскуственно завязаных на какие то глобальные контексты решений, которые вообще не глобальны по природе.
1. Вы путаете понятия «доступен через статический метод» и «глобальный». Transaction.Current — не глобальный.

2. Никто не навязывает использовать системный пул коннектов. Хотите — создавайте коннекты сами (через драйвера хоть Oracle, хоть Firebird) и скармливайте их ADO или EF, эти фреймворки поддерживают такие сценарии.
А вы в юнит-тестах используете транзакции и подключение к БД? Каким боком эти две темы соприкасаются? У меня ни разу не было проблем в юнит-тестах с транзакциями :), несмотря на то, что приходилось писать тестовый провайдер базы в котором юнит-тест задавал результат транзакции…
Вам никто не мешает реализовать свой собственный механизм управления транзакциями, но вот код провайдеров доступа к БД, или прочим транзакционным штукам, никак не должны зависеть от вашей фантазии о том, как оно будет лучше.
Механизм транзакций в .Net — это внутренний стандарт, которому следуют все те, кто пишет системные библиотеки для других. И все потребители этих библиотек ожидают стандартного поведения, безо всяких преферансов и куртизанок :)
И ещё раз — Current тут не статическая переменная, а аксессор к текущему контексту транзакции.
И это стандарт, и не только для .Net.
Например, для бэкенда, HttpContext.Current даёт доступ к веб-запросу, в рамках которого исполняется код. Аналоги есть в других фреймворках, и почти везде это будет та или иная глобальная точка доступа…
Например, для бэкенда, HttpContext.Current даёт доступ к веб-запросу, в рамках которого исполняется код


Вы как раз привели пример еще более неуклюжего решения. Вот этот HttpContext.Current он текущий в рамках чего? Потока, домена, процесса? Уже вопрос, правда? Догадываюсь что видимо потока. Но постойте, а как же тогда много запросов в рамках одного потока? Запрещено? Ну это же фигня. И кстати где по доке внятно написано, что это за HttpContext.Current, где рамки?

Я в курсе что MS этим грешит то там то тут. И кстати, как правило если есть какой то Current то тут же жди вопросы в гугле " а как мне сделать несколько этого Current".

Глобальные конексты не лишены смысла. Но надо четко знать где их создавать. Например Thread.Current вообще не вызывает вопросов. Сразу понятно что за Current. Читаем классиков на эту тему.
Чтобы ответить на вопрос «где рамки» надо знать инфраструктуру обработки запроса в конкретной системе/фреймворке.
Конкретно .Net снимает с меня эту головную боль, предоставляя удобный и доступный инструмент.
И не надо «много запросов в рамках одного потока» — это тот головняк, который лучше обходить стороной. Лучше, когда наоборот, много потоков на один запрос, и при этом никаких мучений — оно само работает.
Лично я не разу не видел разумного вопроса «а как мне сделать несколько этого Current». У вас есть такой пример?
И не надо «много запросов в рамках одного потока» — это тот головняк, который лучше обходить стороной


Ну ну… а с тредами то головняков нет.

У вас есть такой пример


github.com/dotnet/core/issues/689
а с тредами то головняков нет.
да, нету, если фигнёй не страдать.

github.com/dotnet/core/issues/689
— не вижу связи с вашим «как мне сделать несколько этого Current»
Вот по сути там всё сильно сложнее… Можете сами заглянуть в исходники
А таки да, как изволите получать доступ к контексту исполнения? Наверно, в JavaScript-е не смущает везде в браузере доступная переменная window.document?
Или та же консоль? Откуда всё это брать, как и ещё 100500 объектов, описывающих контекст исполнения?
Наверно, в JavaScript-е не смущает везде в браузере доступная переменная window.document?


Вообщето window.document — пропертя, а не статик. window как статик — очень даже смущает. Погуглите сколько боли на эту тему, и как только не извращаются чтоб получить много окон и один скрипт имеющий доступ ко всем им.
А вот не надо много окон — это сильно небезопасно, потому и ограничен контекст так жёстко.

И да, на другие вопросы будут ответы?
не надо много окон — это сильно небезопасно


Совершенно согласен! ОДно окно и на весь экран.И командная строка и все ок… расскажите это юзерам чтобы засунули Windows в опу )).

И да, на другие вопросы будут ответы?


Про консоль? И консоль сделана через одно место. Чтобы это понять надо осознать что есть процесс, и есть его ввод вывод:

System.Diagnostics.Process.GetCurrentProcess().StandardInput


Теперь вопрос — нахрена вы мне понаделали стопятсот статик методов в Console когда можно было сделать инстанцируемый классс Console, да еще который бы работал на любом потоке. Не знаете? И я не знаю. Подозреваю что так ребятам проще создавать маркетинговые семплы вроде

Console.Write("Hello как это просто и кратко..")


System.Diagnostics.Process.GetCurrentProcess()
— вот это хороший пример статик метода. Почему надеюсь сами разберетесь.
ОДно окно и на весь экран
давайте не путать браузер и десктоп.
инстанцируемый классс Console, да еще который бы работал на любом потоке
— не вижу никакой проблемы с консолью, скорее наоборот, не могу придумать, зафига мне инстанцируемый класс? Не, если мне захочется чего-то особого повытворять со стандартными потоками ввода/вывода, я знаю где их взять и что с ними можно сделать. Но при этом нахожу статический Console удобнее любой альтернативы в 99% случаев…
давайте не путать браузер и десктоп


Ловко вы… сначала сами про браузер заговорили а теперь «давайте не путать».
Наверно, в JavaScript-е не смущает везде в браузере доступная переменная window.document?
Ловко вы…
ОДно окно и на весь экран.И командная строка и все ок…

Сами приплели тут десктоп, а теперь как бы и не при делах? :)
Товарищ, цитируйте полностью, и вашу цитату тогда уж тоже:

не надо много окон — это сильно небезопасно


Закончим диалог, с вами он ниочем
Sign up to leave a comment.