Postgres Npgsql.PostgresException обнаружена взаимоблокировка - PullRequest
0 голосов
/ 30 мая 2018

Мы разрабатываем приложение группового чата и используем PostgreSQL для хранения сообщений чата.

CREATE TABLE public.chatmessage
(
    chatmessageid uuid NOT NULL DEFAULT uuid_generate_v4(),
    text character varying COLLATE pg_catalog."default",
    planid uuid NOT NULL,
    userid uuid NOT NULL,
    createdat timestamp with time zone NOT NULL DEFAULT timezone('utc'::text, now()),
    updatedat timestamp with time zone,
    deleted boolean NOT NULL DEFAULT false,
    viewedallstatus boolean,
    vieweduserids uuid[],
    alloweduserids uuid[],
    CONSTRAINT chatmessage_pkey PRIMARY KEY (chatmessageid)
)

Для управления статусом чтения мы храним userIds просматриваемых участников в столбце просматриваемых пользователей.

При интенсивном использовании группового чата с 5 или более участниками мы получаем следующее исключение:

Npgsql.PostgresException (0x80004005): 40P01: deadlock detected
   at Npgsql.NpgsqlConnector.<DoReadMessage>d__157.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at Npgsql.NpgsqlConnector.<ReadMessage>d__156.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Npgsql.NpgsqlConnector.<ReadMessage>d__156.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at Npgsql.NpgsqlDataReader.<NextResult>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Npgsql.NpgsqlDataReader.<<NextResultAsync>b__31_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Npgsql.NpgsqlCommand.<Execute>d__71.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at Npgsql.NpgsqlCommand.<ExecuteNonQuery>d__84.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Npgsql.NpgsqlCommand.<>c__DisplayClass83_0.<<ExecuteNonQueryAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Dapper.SqlMapper.<ExecuteImplAsync>d__37.MoveNext() in C:\projects\dapper\Dapper\SqlMapper.Async.cs:line 646
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

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

"Update ChatMessage Set ViewedUserIds = ViewedUserIds || @UserId Where PlanId = @PlanId And @UserId = Any(AllowedUserIds) And Not (@UserId = Any(ViewedUserIds)) And Deleted = False;" 

Как мы можем решить эту проблему?

1 Ответ

0 голосов
/ 31 мая 2018

С блокировкой нет проблем, если она не происходит часто.

Правильное решение - приложение не должно паниковать, а просто повторить транзакцию базы данных.

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

Если вам нужна конкретная помощь, прочитайте файл журнала PostgreSQL, чтобы узнать, какие запросы задействованы.Вам нужно будет понять, какие конфликты блокируют, чтобы определить основную причину.

...