Search
Write a publication
Pull to refresh

Comments 10

1) А уровни изоляции зачем придумали ?

2)

обратите внимание на добавленный оператор FOR UPDATE в SELECT. Он блокирует строку, чтобы другие SELECT запросы ждали завершения транзакции. Это позволяет правильно обрабатывать параллельные запросы.

И получить High load и "мы упёрлись в СУБД" на ровном месте .

Сорри, но дальше не стал читать . Потому, что на обсуждение с разрабами темы "вы зачем используете select for update и потом приходите в отдел администрирования баз данных с жалобами "у нас все тормозит" ? ", было потрачено сколько нервов , времени и бесполезных разговоров, что не хочется опять вспоминать.

Стало быть поторопился. Просто уже столько раз натыкался на попытки перенести логику СУБД на уровень приложения , что уже стойкая настороженность возникает.

Ок. Попробую дочитать.

Эх , если бы современные разрабы это читали и руководствовались затем в реальной жизни

Что бы вы ни выбрали, обязательно проводите нагрузочные тесты, чтобы увидеть, как ваш код работает под нагрузкой. Это поможет избежать неприятных сюрпризов в продакшене, когда резко возрастет трафик.

По личному опыту - "неприятные сюрпризы в продакшене" возникают в 100% . Если нагрузка на информационную систему средняя или высокая.

Если внутри fn() вызываемой в теле `runInTx` случится паника то ваш код не вызовет ни коммит ни роллбэк. Думаю безопаснее делать так

func runInTx(db *sql.DB, fn func(tx *sql.Tx) error) (err error) {
	tx, err := db.Begin()
	if err != nil {
		return
	}
	defer func() {
		rollbackErr := tx.Rollback()
		if rollbackErr != nil {
			err = errors.Join(err, rollbackErr)
		}
	}()

	err = fn(tx)
	if err == nil {
		err = tx.Commit()
	}

	return
}

Теперь благодаря деферу роллбэк всегда гарантированно вызывается, просто если это произошло после коммита то ничего не случится, зато если вылетит паника вы корректно откатите начатую транзакцию.

UFO landed and left these words here

Транзакции в слое логики (избегайте, если можете)

...

По мере роста логики вы должны тщательно обдумывать, что должно выполняться внутри транзакции, а что — вне её. Откат повлияет на всё, что вы поместите внутрь функции.

Если в рамках одного запроса от пользователя мне требуется проводить какие-то изменения внутри транзакции, а другие - строго вне, то это повод задуматься о переосмыслении api. Но даже если требуется делать именно так, то ведь на то это и слой логики, что я волен определять, какие действия должны быть атомарными, а какие - нет :)

Не говоря уже о странном аргументе tx, который нужно передавать в методы репозитория.

Что думаете насчёт того, чтобы не заморачиваться и просто создавать (где нужно) отдельную функцию, которая принимает на вход tx? В UpdateByID по сути так и происходит, просто вложено в другую функцию.

Паттерн UpdateFn (наше основное решение)

Принцип "на, держи юзера, измени в нём, что нужно, а я пока покурю" интересен. Но, кажется, что не совсем удобный в более сложных случаях. У вас был опыт применения, когда требуются изменения сразу в нескольких таблицах? Как будто в этом случае часть бизнес логики будет проникать в репозиторий...

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

UFO landed and left these words here

По своим наблюдениям считаю, что "Транзакции в слое логики" - оптимальное решение, разве что транзакцию прокинуть в контекст для удобства можно и сделать методы репозитория универсальными (работающими как с tx из контекста так и без него). А tx в контекст прокидывать через метод runInTx. Предлагать целевое решение с оговорками, что оно не для хайлоада, ну... Если не хайлоад, то оптимальнее наверное ORM и не думать.
А в целом я считаю, что если понадобились транзакции, то значит транзакция является неотъемлемой частью бизнес логики и не стоит потеть потаясь её куда-то замести под ковер

UFO landed and left these words here
Sign up to leave a comment.

Articles