Re: UPSERT with non-unique index

Lew Pitcher <[email protected]> Sun, 02 Aug 2020 17:46:39 -0400
Newsgroups comp.databases.mysql
Organization The Pitcher Digital Freehold
Message-ID <[email protected]>
On August 2, 2020 17:28, The Natural Philosopher wrote:

> On 02/08/2020 19:15, J.O. Aho wrote:
>> On 02/08/2020 18.55, Stanimir Stamenkov wrote:
>>> [I've posted this on Stack Overflow:
>>> https://stackoverflow.com/questions/63194116/upsert-with-non-unique-index]
>>>
>>>
>>> I need to implement concurrent-safe UPSERT using a non-unique key and
>>> avoid unnecessary auto-increment of ID.
>>>
>>> Traditional INSERT ... ON DUPLICATE KEY [1] doesn't work for me, so
>>> I'm performing:
>>>
>>> INSERT INTO table (col1, col2, col3, col4, col5)
>>> SELECT 1, 2, 'value3', 'value4', 'value5'
>>> WHERE NOT EXISTS (SELECT 1
>>> FROM table
>>> WHERE col3 = 'value3'
>>> AND col4 = 'value4'
>>> AND col5 = 'value5')
>>>
>>> then if it results in no row inserted, I'm performing:
>>>
>>> UPDATE table
>>> SET col1 = col1 + 1,
>>> col2 = MAX(col2, 2)
>>> WHERE col3 = 'value3'
>>> AND col4 = 'value4'
>>> AND col5 = 'value5'
>>>
>>> There's an index:
>>>
>>> CREATE INDEX ON table (col3, col4, col5)
>>>
>>> It is non-unique as there are legacy data that does not allow me to
>>> declare it unique.  Newer records, however, should not have duplicated
>>> (col3, col4, col5) rows.
>>>
>>> Unsurprisingly, using the given INSERT statement I'm getting mixed
>>> results trying to execute it concurrently from two sessions.  I can
>>> see the second session blocking until the first one commits its
>>> transaction, but then the second transaction is also able to insert a
>>> new row sometimes (or sometimes it achieves the expected of avoiding
>>> to insert a duplicate (col3, col4, col5) row).
>> 
>> Maybe the blocking is more then way you are testing things than what in
>> reality happens.
>> 
>> I would suggest you use
>> 
>> LOCK TABLES table WRITE;
>> 
>> <do what you did before>
>> 
>> UNLOCK TABLES;
>> 
>> 
>> LOCK TABLES table WRITE;
>> 
>> UNLOCK TABLES;
>> 
>> https://www.mysqltutorial.org/mysql-table-locking/
>> https://dev.mysql.com/doc/refman/5.7/en/lock-tables.html
>> 
> I don't know how SQL manages it, but if you have two asynchronous
> instances of read-modify-write, where what is written depends on what is
> read, then you MUST make the read-modify-write cycle ATOMIC - that is
> uninterruptible, by another process doing the same.
> 
> You need locking, and the attendant danger that if aprocess crashes with
> locks set...

I'd roll the whole thing in a transaction

START TRANSACTION;

-- your sql goes here

COMMIT;


-- 
Lew Pitcher
"In Skills, We Trust"