• Dmitry Lenev's avatar
    Implementation of simple deadlock detection for metadata locks. · 0228c989
    Dmitry Lenev authored
    This change is supposed to reduce number of ER_LOCK_DEADLOCK
    errors which occur when multi-statement transaction encounters
    conflicting metadata lock in cases when waiting is possible.
    
    The idea is not to fail ER_LOCK_DEADLOCK error immediately when
    we encounter conflicting metadata lock. Instead we release all
    metadata locks acquired by current statement and start to wait
    until conflicting lock go away. To avoid deadlocks we use simple
    empiric which aborts waiting with ER_LOCK_DEADLOCK error if it
    turns out that somebody is waiting for metadata locks owned by
    this transaction.
    
    This patch also fixes bug #46273 "MySQL 5.4.4 new MDL: Bug#989
    is not fully fixed in case of ALTER".
    
    The bug was that concurrent execution of UPDATE or MULTI-UPDATE
    statement as a part of multi-statement transaction that already
    has used table being updated and ALTER TABLE statement might have
    resulted of loss of isolation between this transaction and ALTER
    TABLE statement, which manifested itself as changes performed by
    ALTER TABLE becoming visible in transaction and wrong binary log
    order as a consequence.
    
    This problem occurred when UPDATE or MULTI-UPDATE's wait in
    mysql_lock_tables() call was aborted due to metadata lock
    upgrade performed by concurrent ALTER TABLE. After such abort all
    metadata locks held by transaction were released but transaction
    silently continued to be executed as if nothing has happened.
    
    We solve this problem by changing our code not to release all
    locks in such case. Instead we release only locks which were
    acquired by current statement and then try to reacquire them
    by restarting open/lock tables process. We piggyback on simple
    deadlock detector implementation since this change has to be
    done anyway for it.
    
    mysql-test/include/handler.inc:
      After introduction of basic deadlock detector for metadata locks
      it became necessary to change parts of test for HANDLER statements
      which covered some of scenarios in which ER_LOCK_DEADLOCK error
      was detected in absence of real deadlock (with new deadlock detector
      this no longer happens).
      Also adjusted test to the fact that HANDLER READ for the table no
      longer will be blocked by ALTER TABLE for the same table which awaits
      for metadata lock upgrade (this is due to removal of mysql_lock_abort()
      from wait_while_table_is_used()).
    mysql-test/r/handler_innodb.result:
      After introduction of basic deadlock detector for metadata locks
      it became necessary to change parts of test for HANDLER statements
      which covered some of scenarios in which ER_LOCK_DEADLOCK error
      was detected in absence of real deadlock (with new deadlock detector
      this no longer happens).
      Also adjusted test to the fact that HANDLER READ for the table no
      longer will be blocked by ALTER TABLE for the same table which awaits
      for metadata lock upgrade (this is due to removal of mysql_lock_abort()
      from wait_while_table_is_used()).
    mysql-test/r/handler_myisam.result:
      After introduction of basic deadlock detector for metadata locks
      it became necessary to change parts of test for HANDLER statements
      which covered some of scenarios in which ER_LOCK_DEADLOCK error
      was detected in absence of real deadlock (with new deadlock detector
      this no longer happens).
      Also adjusted test to the fact that HANDLER READ for the table no
      longer will be blocked by ALTER TABLE for the same table which awaits
      for metadata lock upgrade (this is due to removal of mysql_lock_abort()
      from wait_while_table_is_used()).
    mysql-test/r/mdl_sync.result:
      Added test coverage for basic deadlock detection in metadata
      locking subsystem and for bug #46273 "MySQL 5.4.4 new MDL:
      Bug#989 is not fully fixed in case of ALTER".
    mysql-test/r/sp-lock.result:
      Adjusted test coverage for metadata locking for stored routines
      since after introduction of basic deadlock detector for metadata
      locks number of scenarios in which ER_LOCK_DEADLOCK error in
      absence of deadlock has decreased.
    mysql-test/t/mdl_sync.test:
      Added test coverage for basic deadlock detection in metadata
      locking subsystem and for bug #46273 "MySQL 5.4.4 new MDL:
      Bug#989 is not fully fixed in case of ALTER".
    mysql-test/t/sp-lock.test:
      Adjusted test coverage for metadata locking for stored routines
      since after introduction of basic deadlock detector for metadata
      locks number of scenarios in which ER_LOCK_DEADLOCK error in
      absence of deadlock has decreased.
    sql/log_event_old.cc:
      close_tables_for_reopen() now takes one more argument which
      specifies at which point it should stop releasing metadata
      locks acquired by this connection.
    sql/mdl.cc:
      Changed metadata locking subsystem to support basic deadlock detection
      with a help of the following simple empiric -- we assume that there is
      a deadlock if there is a connection which has to wait for a metadata
      lock which is currently acquired by some connection which is itself
      waiting to be able to acquire some shared metadata lock.
      
      To implement this change:
      - Added MDL_context::can_wait_lead_to_deadlock()/_impl() methods
        which allow to find out if there is someone waiting for metadata
        lock which is held by the connection and therefore deadlocks are
        possible if this connection is going to wait for some metadata lock.
        To do this added version of MDL_ticket::has_pending_conflicting_lock()
        method which assumes that its caller already owns LOCK_mdl mutex.
      - Changed MDL_context::wait_for_locks() to use one of the above methods
        to check if somebody is waiting for metadata lock owned by this
        context (and therefore deadlock is possible) and emit ER_LOCK_DEADLOCK
        error in this case. Also now we mark context of connections waiting
        inside of this method by setting MDL_context::m_is_waiting_in_mdl
        member. Thanks to this such connection could be waken up if some
        other connection starts waiting for one of its metadata locks and
        so a deadlock can occur.
      - Adjusted notify_shared_lock() to wake up connections which wait inside
        MDL_context::wait_for_locks() while holding shared metadata lock.
      - Changed MDL_ticket::upgrade_shared_lock_to_exclusive() to add
        temporary ticket for exclusive lock to MDL_lock::waiting queue, so
        request for metadata lock upgrade can be properly detected by our
        empiric.
        Also now this method invokes a callback which forces transactions
        holding shared metadata lock on the table to call MDL_context::
        can_wait_lead_to_deadlock() method even if they don't need any new
        metadata locks. Thanks to this such transactions can detect deadlocks/
        livelocks between MDL and table-level locks.
      
      Also reduced timeouts between calls to notify_shared_lock()
      in MDL_ticket::upgrade_shared_lock_to_exclusive() and
      MDL_context::acquire_exclusive_locks(). This was necessary
      to get rid of call to mysql_lock_abort() in wait_while_table_is_used().
      (Now we instead rely on notify_shared_lock() timely calling
      mysql_lock_abort_for_thread() for the table on which lock
      is being upgraded/acquired).
    sql/mdl.h:
      - Added a version of MDL_ticket::has_pending_conflicting_lock() method
        to be used in situations when caller already has acquired LOCK_mdl
        mutex.
      - Added MDL_context::can_wait_lead_to_deadlock()/_impl() methods
        which allow to find out if there is someone waiting for metadata lock
        which is held by this connection and thus deadlocks are possible if
        this connections will start waiting for some metadata lock.
      - Added MDL_context::m_is_waiting_in_mdl member to mark connections
        waiting in MDL_context::wait_for_locks() method of metadata locking
        subsystem. Added getter method for this private member to make it
        accessible in notify_shared_lock() auxiliary so we can wake-up such
        connections if they hold shared metadata locks.
      - Finally, added mysql_abort_transactions_with_shared_lock() callback
        to be able force transactions which don't need any new metadata
        locks still call MDL_context::can_wait_lead_to_deadlock() and detect
        some of deadlocks between metadata locks and table-level locks.
    sql/mysql_priv.h:
      close_tables_for_reopen() now takes one more argument which
      specifies at which point it should stop releasing metadata
      locks acquired by this connection.
    sql/sql_base.cc:
      Changed approach to metadata locking for multi-statement transactions.
      We no longer fail ER_LOCK_DEADLOCK error immediately when we encounter
      conflicting metadata lock. Instead we release all metadata locks
      acquired by current statement and start to wait until conflicting
      locks to go away by calling MDL_context::wait_for_locks() method.
      To avoid deadlocks the latter implements simple empiric which aborts
      waiting with ER_LOCK_DEADLOCK error if it turns out that somebody
      is waiting for metadata locks owned by this transaction.
      
      To implement the change described above:
      - Introduced Open_table_context::m_start_of_statement_svp member to
        store state of metadata locks at the start of the statement.
      - Changed Open_table_context::request_backoff_action() not to
        fail with ER_LOCK_DEADLOCK immediately if back-off is requested
        due to conflicting metadata lock.
      - Added new argument for close_tables_for_reopen() procedure which
        allows to specify subset of metadata locks to be released.
      - Changed open_tables() not to release all metadata locks acquired
        by current transaction when metadata lock conflict is discovered.
        Instead we release only locks acquired by current statement.
      - Changed open_ltable() and open_and_lock_tables_derived() not to emit
        ER_LOCK_DEADLOCK error when mysql_lock_tables() is aborted in
        multi-statement transaction when somebody tries to acquire exclusive
        metadata lock on the table. Instead we release metadata locks acquired
        by current statement and try to wait until they can be re-acquired.
      - Adjusted tdc_wait_for_old_versions() to check if there is someone
        waiting for one of metadata locks held by this connection and run
        deadlock detection in order to avoid deadlocks in some
        situations.
      - Added mysql_abort_transactions_with_shared_lock() callback which
        allows to force transactions holding shared metadata lock on the
        table to call MDL_context::can_wait_lead_to_deadlock() even if they
        don't need any new metadata locks so they can detect potential
        deadlocks between metadata locking subsystem and table-level locks.
      - Adjusted wait_while_table_is_used() not to set TABLE::version to
        0 as it is now done only when necessary by the above-mentioned
        callback. Also removed unnecessary call to mysql_lock_abort().
        Instead we rely on code performing metadata lock upgrade aborting
        waits on the table-level lock for this table by calling
        mysql_lock_abort_for_thread() (invoked by
        mysql_notify_thread_having_shared_lock()). In future this should
        allow to reduce number of scenarios in which we produce
        ER_LOCK_DEADLOCK error even though no real deadlock exists.
    sql/sql_class.h:
      Introduced Open_table_context::m_start_of_statement_svp member to
      store state of metadata locks at the start of the statement.
      Replaced Open_table_context::m_can_deadlock member with m_has_locks
      member to reflect the fact that we no longer unconditionally emit
      ER_LOCK_DEADLOCK error for transaction having some metadata locks
      when conflicting metadata lock is discovered.
    sql/sql_insert.cc:
      close_tables_for_reopen() now takes one more argument which
      specifies at which point it should stop releasing metadata
      locks acquired by this connection.
    sql/sql_plist.h:
      Made I_P_List_iterator<T, B> usable with const lists.
    sql/sql_show.cc:
      close_tables_for_reopen() now takes one more argument which
      specifies at which point it should stop releasing metadata
      locks acquired by this connection.
    sql/sql_update.cc:
      Changed UPDATE and MULTI-UPDATE code not to release all metadata
      locks when calls to mysql_lock_tables() are aborted. Instead we
      release only locks which are acquired by this statement and then
      try to reacquire them by calling open_tables(). This solves
      bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in
      case of ALTER".
    0228c989
sp-lock.test 25.9 KB