• Marko Mäkelä's avatar
    MDEV-14637: Fix hang due to DDL with FOREIGN KEY · 20460897
    Marko Mäkelä authored
    When MySQL 5.7.1 introduced WL#6326 to reduce contention on the
    non-leaf levels of B-trees, it introduced a new rw-lock mode SX
    (not conflicting with S, but conflicting with SX and X) and
    new rules to go with it.
    
    A thread that is holding an dict_index_t::lock aka index->lock
    in SX mode is permitted to acquire non-leaf buf_block_t::lock
    aka block->lock X or SX mode, in monotonically descending order.
    That is, once the thread has acquired a block->lock, it is not
    allowed to acquire a lock on its parent or grandparent pages.
    Such arbitrary-order access is only allowed when the thread
    acquired the index->lock in X mode upfront.
    
    A customer encountered a repeatable hang when loading a dump into
    InnoDB while using multiple innodb_purge_threads (default: 4).
    The dump makes very heavy use of FOREIGN KEY constraints.
    By luck, it happened so that two purge worker threads (srv_worker_thread)
    deadlocked with each other. Both were operating on the index FOR_REF
    of the InnoDB internal table SYS_FOREIGN. One of them was legitimately
    holding index->lock S-latch and the root block->lock S-latch. The other
    had acquired index->lock SX-latch, root block->lock SX-latch, and a bunch
    of other latches, including the fil_space_t::latch for freeing some blocks
    and some leaf page latches. This other thread was inside 2 nested calls
    to btr_compress() and it was trying to reacquire the root block->lock
    in X mode, violating the WL#6326 protocol.
    
    This violation led to a deadlock, because while S is compatible with SX
    and a thread can upgrade an SX lock to X when there are no conflicting
    requests, in this case there was a conflicting S lock held by the other
    purge worker thread.
    
    During this deadlock, both threads are holding dict_operation_lock S-latch,
    which would block any subsequent DDL statements, such as CREATE TABLE.
    
    The tables SYS_FOREIGN and SYS_FOREIGN_COLS are special in that they
    define key columns of the type VARCHAR(0), created using the InnoDB
    internal SQL parser. Because InnoDB does not internally enforce the
    maximum length of columns, it would happily write more than 0 bytes
    to these columns. This caused a miscalculation of node_ptr_max_size.
    
    btr_cur_will_modify_tree(): Clean up some code. (No functional change.)
    
    btr_node_ptr_max_size(): Renamed from dict_index_node_ptr_max_size().
    Use a more realistic maximum size for SYS_FOREIGN and SYS_FOREIGN_COLS.
    
    btr_cur_pessimistic_delete(): Refrain from merging pages if it is
    not safe.
    
    This work is based on the following MySQL 5.7.23 fix:
    
    commit 58dcf0b4a4165ed59de94a9a1e7d8c954f733726
    Author: Aakanksha Verma <aakanksha.verma@oracle.com>
    Date:   Wed May 9 18:54:03 2018 +0530
    
        BUG#26225783 MYSQL CRASH ON CREATE TABLE (REPRODUCEABLE) -> INNODB: A
        LONG SEMAPHORE WAIT
    20460897
dict0dict.h 69.6 KB