Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-9139

SELECT inside a trigger results in InnoDB gap lock

    XMLWordPrintable

Details

    Description

      Executing a SELECT and an INSERT inside a trigger seems to require a gap lock, while this gap lock is not needed if the two statements are executed in a transaction outside of the trigger. There might be a good reason for this, but it seems to be undocumented.

      For example, "trigger" doesn't appear at all on these pages:

      https://dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html

      https://dev.mysql.com/doc/refman/5.6/en/innodb-record-level-locks.html

      https://mariadb.com/kb/en/mariadb/xtradbinnodb-lock-modes/

      And I don't see this particular locking behavior documented here:

      https://mariadb.com/kb/en/mariadb/triggers-and-implicit-locks/

      First, let's create some tables:

      CREATE OR REPLACE TABLE test (
          id int(3) auto_increment primary key,
          name varchar(10)
      ) ENGINE=InnoDB;
       
      CREATE OR REPLACE TABLE test_log (
          id int(3) auto_increment primary key,
          name varchar(10)
      ) ENGINE=InnoDB;
       
      INSERT INTO test (id, name) VALUES (1, 'test1-1');
      INSERT INTO test (id, name) VALUES (2, 'test2-1');

      Then let's say that we have the following trigger on test:

      DROP TRIGGER IF EXISTS test_trg;
       
      DELIMITER //
       
      CREATE TRIGGER test_trg
      BEFORE UPDATE
      ON test
      FOR EACH ROW
      BEGIN
       
      IF EXISTS(SELECT 1 FROM test_log WHERE id = OLD.id) THEN
      signal SQLSTATE VALUE '99999';
      END IF;
       
      INSERT INTO test_log (id, name) VALUES (OLD.id, OLD.name);
       
      END;
      //
       
      DELIMITER ;

      And then we execute this:

      BEGIN;
      UPDATE test SET name = 'test1-2' WHERE id = 1;

      SHOW ENGINE INNODB STATUS shows that this transaction has a gap lock on the test_log table:

      ---TRANSACTION 9090, ACTIVE 1 sec
      5 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 2
      MySQL thread id 8, OS thread handle 0x7f287b6c8700, query id 131 localhost root init
      show engine innodb status
      Trx #rec lock waits 0 #table lock waits 0
      Trx total rec lock wait time 0 SEC
      Trx total table lock wait time 0 SEC
      TABLE LOCK table `db1`.`test` trx id 9090 lock mode IX lock hold time 1 wait time before grant 0
      RECORD LOCKS space id 26 page no 3 n bits 72 index `PRIMARY` of table `db1`.`test` trx table locks 2 total table locks 1  trx id 9090 lock_mode X locks rec but not gap lock hold time 1 wait time before grant 0
      TABLE LOCK table `db1`.`test_log` trx id 9090 lock mode IX lock hold time 1 wait time before grant 0
      RECORD LOCKS space id 27 page no 3 n bits 72 index `PRIMARY` of table `db1`.`test_log` trx table locks 2 total table locks 1  trx id 9090 lock_mode X lock hold time 1 wait time before grant 0
      RECORD LOCKS space id 27 page no 3 n bits 72 index `PRIMARY` of table `db1`.`test_log` trx table locks 2 total table locks 1  trx id 9090 lock_mode X locks gap before rec lock hold time 1 wait time before grant 0

      This locking behavior does not appear to be equivalent to the locking behavior of the statements that the transactions run.

      For example, if we ran the following:

      BEGIN;
      SELECT 1 FROM test_log WHERE id = 1;
      INSERT INTO test_log (id, name) VALUES (1, 'test1-1');

      Then SHOW ENGINE INNODB STATUS shows the following:

      ---TRANSACTION 9092, ACTIVE 5 sec
      1 lock struct(s), heap size 360, 0 row lock(s), undo log entries 1
      MySQL thread id 8, OS thread handle 0x7f287b6c8700, query id 136 localhost root init
      show engine innodb status
      Trx read view will not see trx with id >= 9093, sees < 9093
      Trx #rec lock waits 0 #table lock waits 0
      Trx total rec lock wait time 0 SEC
      Trx total table lock wait time 0 SEC
      TABLE LOCK table `db1`.`test_log` trx id 9092 lock mode IX lock hold time 3 wait time before grant 0

      To get the same locking behavior as the trigger, you have to use a SELECT FOR UPDATE:

      BEGIN;
      SELECT 1 FROM test_log WHERE id = 1 FOR UPDATE;
      INSERT INTO test_log (id, name) VALUES (1, 'test1-1');

      ---TRANSACTION 9097, ACTIVE 4 sec
      3 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
      MySQL thread id 8, OS thread handle 0x7f287b6c8700, query id 141 localhost root init
      show engine innodb status
      Trx #rec lock waits 0 #table lock waits 0
      Trx total rec lock wait time 0 SEC
      Trx total table lock wait time 0 SEC
      TABLE LOCK table `db1`.`test_log` trx id 9097 lock mode IX lock hold time 4 wait time before grant 0
      RECORD LOCKS space id 27 page no 3 n bits 72 index `PRIMARY` of table `db1`.`test_log` trx table locks 1 total table locks 1  trx id 9097 lock_mode X lock hold time 4 wait time before grant 0
      RECORD LOCKS space id 27 page no 3 n bits 72 index `PRIMARY` of table `db1`.`test_log` trx table locks 1 total table locks 1  trx id 9097 lock_mode X locks gap before rec lock hold time 3 wait time before grant 0

      Do triggers force SELECT statements to lock like SELECT FOR UPDATE?

      Is this locking behavior documented somewhere?

      Attachments

        Activity

          People

            jplindst Jan Lindström (Inactive)
            GeoffMontee Geoff Montee (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.