[MDEV-28040] Unexpected lock when UPDATE reports an error Created: 2022-03-11  Updated: 2023-03-04

Status: Open
Project: MariaDB Server
Component/s: Data Manipulation - Update, Documentation
Affects Version/s: 10.7.3
Fix Version/s: N/A

Type: Bug Priority: Major
Reporter: John Jove Assignee: Ian Gilfillan
Resolution: Unresolved Votes: 0
Labels: transactions
Environment:

OS: Ubuntu-20.04 LTS



 Description   

With "INSERT IGNORE", the INSERT executes successfully even though there is a NOT NULL constraint on column c1.

In transaction, although the UPDATE executes unsuccessfully and reports an error, it locks the records and blocks other transaction.

/* init */ CREATE TABLE t(c1 BLOB NOT NULL, c2 TEXT);
/* init */ INSERT IGNORE INTO t VALUES (NULL, NULL), (NULL, 'aaa');
/* t1 */ BEGIN;
/* t1 */ UPDATE t SET c2='test' WHERE c1;
/* t1 */ ERROR 1292 (22007): Truncated incorrect DOUBLE value: ''
/* t2 */ BEGIN;
/* t2 */ UPDATE t SET c2 = 'def'; -- t2 is blocked
/* t1 */ COMMIT; -- t2 is unblocked
/* t2 */ COMMIT;



 Comments   
Comment by Marko Mäkelä [ 2022-03-11 ]

Yes, INSERT IGNORE is supposed to ‘fix’ errors by replacing garbage with some other garbage. It is working as designed when it replaces the NULL values with the implicit default value of the NOT NULL column, in this case the empty string.

Here is an mtr version of the test, replacing the INSERT IGNORE with an equivalent plain INSERT. By the way, MDEV-27025 or MDEV-27992 is not playing any role here:

--source include/have_innodb.inc
CREATE TABLE t(c1 BLOB NOT NULL, c2 TEXT) ENGINE=InnoDB;
INSERT INTO t VALUES ('', NULL), ('', 'aaa');
SELECT * FROM t;
 
BEGIN;
--error ER_TRUNCATED_WRONG_VALUE
UPDATE t SET c2='test' WHERE c1;
 
connect con2,localhost,root;
send UPDATE t SET c2='def';
 
connection default;
let $wait_condition=
    select count(*) = 1 from information_schema.processlist
    where state = "Updating" and info like "UPDATE t SET%";
--source include/wait_condition.inc
COMMIT;
 
connection con2;
reap;
disconnect con2;
 
connection default;
SELECT * FROM t;
DROP TABLE t;

The error message confused me at first. But, it turns out that WHERE apparently expects a DOUBLE expression here, or something that can ultimately be interpreted as Boolean. The error message is about converting the value '' of the column c1 to a numeric expression. I would guess that in a non-strict sql_mode, the empty string should be equivalent to 0, which in turn is equivalent to false. The MariaDB data type system does not include a Boolean data type.

I think that this is working as designed. The error during the UPDATE execution is not a fatal one that would force the transaction to be aborted. The transaction will continue to hold some locks. Examples of errors that cause a transaction abort would be a deadlock, and in some cases, a lock wait timeout.

If you think that something should be fixed, please describe it in more detail. I do not defend the current design. I think that we must avoid changes that could break compatibility with existing applications. I guess that it is the reason why MariaDB has retained the INSERT IGNORE syntax.

Comment by Marko Mäkelä [ 2022-03-11 ]

John Jove, I imagine that you could want an sql_mode where some errors would lead to transaction rollback. Is that what you want?

We already have a parameter innodb_rollback_on_timeout that is disabled by default. Its documentation implies that MySQL 4.0 or 4.1 rolled back transactions on timeout. I can imagine that some user might want their transaction to be able to go through even if a timeout occurs while executing some step. That timing-out statement could perhaps be resubmitted. But, perhaps we would not need that much granularity and could indeed have an sql_mode where any error (including lock wait timeouts) will lead to transaction rollback.

Comment by John Jove [ 2022-03-13 ]

Thank you for youre explanation. I understand that it is a design.

I am curious that under READ COMMITTED and READ UNCOMMITTED, an UPDATE that reports error does not release the lock it holds. But an UPDATE where the WHERE clause is evaluated to FALSE releases th lock.

Comment by Marko Mäkelä [ 2022-08-02 ]

READ COMMITTED and READ UNCOMMITTED enable so-called "semi-consistent read", which I implemented in MySQL Bug #3300. Any non-matching rows would be unlocked in that mode. I suppose that when an error occurs while evaluating the WHERE condition, the virtual member function handler::unlock_row() will not be called. I do not remember and did not check if it also applies to DELETE, or only UPDATE.

The isolation levels READ COMMITTED and READ UNCOMMITTED also disable some gap locking, like the removed (MDEV-19544) parameter innodb_locks_unsafe_for_binlog used to do.

I think that this is best addressed in the documentation.

Generated at Thu Feb 08 09:57:35 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.