Re: upgrades in row-level locks can deadlock - Mailing list pgsql-hackers

From Oleksii Kliukin
Subject Re: upgrades in row-level locks can deadlock
Date
Msg-id E621393D-A213-41B0-8C9D-584F8EC9287F@hintbits.com
Whole thread Raw
In response to Re: upgrades in row-level locks can deadlock  (Alvaro Herrera <alvherre@2ndquadrant.com>)
Responses Re: upgrades in row-level locks can deadlock
List pgsql-hackers
Oleksii Kliukin <alexk@hintbits.com> wrote:

> Hi,
>
> Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
>
>> On 2019-May-22, Oleksii Kliukin wrote:
>>
>>> X1: select id from job where name = 'a' for key share;
>>> Y: select id from job where name = 'a' for update; -- starts waiting for X1
>>> X2: select id from job where name = 'a' for key share;
>>> X1: update job set name = 'b' where id = 1;
>>> X2: update job set name = 'c' where id = 1; -- starts waiting for X1
>>> X1: rollback;
>>>
>>> At this point, Y is terminated by the deadlock detector:
>>
>> Yeah, this seems undesirable in general terms.  Here's a quick
>> isolationtester spec that reproduces the problem:
>>
>> setup {
>>     drop table if exists tlu_job;
>>     create table tlu_job (id integer primary key, name text);
>>     insert into tlu_job values (1, 'a');
>> }
>>
>> teardown {
>>     drop table tlu_job;
>> }
>>
>> session "s1"
>> setup                { begin; }
>> step "s1_keyshare"    { select id from tlu_job where name = 'a' for key share; }
>> step "s1_update"    { update tlu_job set name = 'b' where id = 1; }
>> step "s1_rollback"    { rollback; }
>>
>> session "s2"
>> setup                { begin; }
>> step "s2_keyshare"    { select id from tlu_job where name = 'a' for key share; }
>> step "s2_update"    { update tlu_job set name = 'c' where id = 1; }
>> step "s2_commit"    { commit; }
>>
>> session "s3"
>> setup                { begin; }
>> step "s3_forupd"    { select id from tlu_job where name = 'a' for update; }
>> teardown            { commit; }
>
> Thank you! I can make it even simpler;  s1 just acquires for share lock, s3
> gets for update one and s2 takes for share lock first, and then tries to
> acquire for update one; once s1 finishes, s3 deadlocks.
>
>> But semantically, I wonder if your transactions are correct.  If you
>> intend to modify the row in s1 and s2, shouldn't you be acquiring FOR NO
>> KEY UPDATE lock instead?  I don't see how can s1 and s2 coexist
>> peacefully.  Also, can your Y transaction use FOR NO KEY UPDATE instead
>> .. unless you intend to delete the tuple in that transaction?
>
> It is correct. I wanted to make sure jobs that acquire for key share lock
> can run concurrently most of the time; they only execute one update at the
> end of the job, and it is just to update the last run timestamp.
>
>> I'm mulling over your proposed fix.  I don't much like the idea that
>> DoesMultiXactIdConflict() returns that new boolean -- seems pretty
>> ad-hoc -- but I don't see any way to do better than that ...  (If we get
>> down to details, DoesMultiXactIdConflict needn't initialize that
>> boolean: better let the callers do.)
>
> I am also not happy about the new parameter to DoesMultiXactIdConflict, but
> calling a separate function to fetch the presence of the current transaction
> in the multixact would mean doing the job of DoesMultiXactIdConflict twice.
> I am open to suggestions on how to make it nicer.
>
> Attached is a slightly modified patch that avoids initializing
> has_current_xid inside DoesMultiXactIdConflict and should apply cleanly to
> the current master.

And here is the v3 that also includes the isolation test I described above
(quoting my previous message in full as I accidentally sent it off-list,
sorry about that).

Cheers,
Oleksii

Attachment

pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Are there still non-ELF BSD systems?
Next
From: Andrey Borodin
Date:
Subject: Re: GiST limits on contrib/cube with dimension > 100?