Общее решение заключается в использовании строгого порядка блокировки как для таблиц, так и для таблиц. То есть если вы собираетесь обновить таблицы A, B и C, сначала заблокируйте A, затем заблокируйте B, затем заблокируйте C, и если вы собираетесь обновить две записи в A, сначала заблокируйте запись с наименьшим значением первичного ключа. Я видел приложения, разработанные таким образом, но это очень дорого и практически никогда не делалось на практике. Более прагматичный подход - использовать охранников.
Например, предположим, что пользовательский интерфейс и веб-службы работают с клиентами и заказами. Вы можете решить превратить клиента в охранника. Чтобы обновить заказ, сначала нужно заблокировать клиента заказа. Использование одного и того же предохранителя для адреса клиента, строк заказа и т. Д. Означает, что для нескольких вариантов использования должен быть заблокирован только один объект.
Затем эти варианты использования сначала идентифицируют свою защиту (в данном случае клиента) и получают пессимистическую блокировку (https://docs.oracle.com/javaee/7/tutorial/persistence-locking002.htm)). Затем они могут продолжить работу без дальнейших изменений кода. Когда все начнут пытаться получить та же самая блокировка, некоторые транзакции должны будут ждать, но не будет никаких взаимоблокировок, поскольку они блокируют охрану по порядку.
Производительность может быть затронута, поскольку это может уменьшить параллелизм, но при условии, что вы можете определить хорошего защитника, он должен избавиться от тупиков.
Другой вариант - перехватить ошибку и повторить транзакцию, если это возможно.
EDIT:
Если вы можете отследить, вы можете получить график взаимоблокировки с SQL Server (https://docs.microsoft.com/en-us/sql/tools/sql-server-profiler/analyze-deadlocks-with-sql-server-profiler?view=sql-server-2017),, но, просто взглянув на код, вы увидите, какие таблицы вы обновляете.