• Dmitry Lenev's avatar
    Fix for bug #51263 "Deadlock between transactional · ae1ae4bd
    Dmitry Lenev authored
    SELECT and ALTER TABLE ...  REBUILD PARTITION".
    
    ALTER TABLE on InnoDB table (including partitioned tables)
    acquired exclusive locks on rows of table being altered.
    In cases when there was concurrent transaction which did
    locking reads from this table this sometimes led to a
    deadlock which was not detected by MDL subsystem nor by
    InnoDB engine (and was reported only after exceeding
    innodb_lock_wait_timeout).
    
    This problem stemmed from the fact that ALTER TABLE acquired
    TL_WRITE_ALLOW_READ lock on table being altered. This lock
    was interpreted as a write lock and thus for table being
    altered handler::external_lock() method was called with
    F_WRLCK as an argument. As result InnoDB engine treated
    ALTER TABLE as an operation which is going to change data
    and acquired LOCK_X locks on rows being read from old
    version of table.
    
    In case when there was a transaction which already acquired
    SR metadata lock on table and some LOCK_S locks on its rows
    (e.g. by using it in subquery of DML statement) concurrent
    ALTER TABLE was blocked at the moment when it tried to
    acquire LOCK_X lock before reading one of these rows.
    The transaction's attempt to acquire SW metadata lock on
    table being altered led to deadlock, since it had to wait
    for ALTER TABLE to release SNW lock. This deadlock was not
    detected and got resolved only after timeout expiring
    because waiting were happening in two different subsystems.
    
    Similar deadlocks could have occured in other situations.
    This patch tries to solve the problem by changing ALTER TABLE
    implementation to use TL_READ_NO_INSERT lock instead of
    TL_WRITE_ALLOW_READ. After this step handler::external_lock()
    is called with F_RDLCK as an argument and InnoDB engine
    correctly interprets ALTER TABLE as operation which only
    reads data from original version of table. Thanks to this
    ALTER TABLE acquires only LOCK_S locks on rows it reads.
    This, in its turn, causes inter-subsystem deadlocks to go
    away, as all potential lock conflicts and thus deadlocks will
    be limited to metadata locking subsystem:
    
    - When ALTER TABLE reads rows from table being altered it
      can't encounter any locks which conflict with LOCK_S row
      locks. There should be no concurrent transactions holding
      LOCK_X row locks. Such a transaction should have been
      acquired SW metadata lock on table first which would have
      conflicted with ALTER's SNW lock.
    - Vice versa, when DML which runs concurrently with ALTER
      TABLE tries to lock row it should be requesting only LOCK_S
      lock which is compatible with locks acquired by ALTER,
      as otherwise such DML must own an SW metadata lock on table
      which would be incompatible with ALTER's SNW lock.
    
    mysql-test/r/innodb_mysql_lock2.result:
      Added test for bug #51263 "Deadlock between transactional
      SELECT and ALTER TABLE ... REBUILD PARTITION".
    mysql-test/suite/rpl_ndb/r/rpl_ndb_binlog_format_errors.result:
      Since CREATE TRIGGER no longer acquires write lock on table
      it is no longer interpreted as an operation which modifies
      table data and therefore no longer fails if invoked for
      SBR-only engine in ROW mode.
    mysql-test/suite/rpl_ndb/t/rpl_ndb_binlog_format_errors.test:
      Since CREATE TRIGGER no longer acquires write lock on table
      it is no longer interpreted as an operation which modifies
      table data and therefore no longer fails if invoked for
      SBR-only engine in ROW mode.
    mysql-test/t/innodb_mysql_lock2.test:
      Added test for bug #51263 "Deadlock between transactional
      SELECT and ALTER TABLE ... REBUILD PARTITION".
    sql/ha_partition.cc:
      When ALTER TABLE creates a new partition to be filled from
      other partition lock it in F_WRLCK mode instead of using
      mode which was used for locking the whole table (it is
      F_RDLCK now).
    sql/lock.cc:
      Replaced conditions which used TL_WRITE_ALLOW_READ
      lock type with equivalent conditions using
      TL_WRITE_ALLOW_WRITE. This should allow to get rid
      of TL_WRITE_ALLOW_READ lock type eventually.
    sql/mdl.cc:
      Updated outdated comment to reflect current situation.
    sql/sql_base.cc:
      Replaced conditions which used TL_WRITE_ALLOW_READ
      lock type with equivalent conditions using
      TL_WRITE_ALLOW_WRITE. This should allow to get rid
      of TL_WRITE_ALLOW_READ lock type eventually.
    sql/sql_table.cc:
      mysql_admin_table():
        Use TL_WRITE_ALLOW_WRITE lock type instead of
        TL_WRITE_ALLOW_READ to determine that we need to acquire
        upgradable metadata lock. This should allow to completely
        get rid of TL_WRITE_ALLOW_READ in long term.
      mysql_recreate_table():
        ALTER TABLE now requires TL_READ_NO_INSERT thr_lock.c lock
        instead of TL_WRITE_ALLOW_READ.
    sql/sql_trigger.cc:
      Changed CREATE/DROP TRIGGER implementation to use
      TL_READ_NO_INSERT lock instead of TL_WRITE_ALLOW_READ lock.
      The latter is no longer necessary since:
      a) We now can rely on metadata locks to achieve proper
         isolation between two DDL statements or DDL and DML
         statements.
      b) This statement does not change any data in table so there
         is no need to inform storage engine about it.
    sql/sql_yacc.yy:
      Changed implementation of ALTER TABLE (and CREATE/DROP INDEX
      as a consequence) to use TL_READ_NO_INSERT lock instead of
      TL_WRITE_ALLOW_READ lock. This is possible since:
      a) We now can rely on metadata locks to achieve proper
         isolation between two DDL statements or DDL and DML
         statements.
      b) This statement only reads data in table being open.
         We write data only to the new version of table and
         then replace with it old version of table under
         X metadata lock.
      
      Thanks to this change InnoDB will no longer acquire LOCK_X
      locks on rows being read by ALTER TABLE (instead LOCK_S
      locks will be acquired) and thus cause of bug #51263
      "Deadlock between transactional SELECT and ALTER TABLE ...
      REBUILD PARTITION" is removed.
      
      Did the similar change for CREATE TRIGGER (see comments
      for sql_trigger.cc for details).
    ae1ae4bd
sql_trigger.cc 71.9 KB