Skip to content
  • Vasil Dimov's avatar
    5eaf0f15
    Refactor the code that saves, updates and deletes rows from · 5eaf0f15
    Vasil Dimov authored
    mysql.innodb_table_stats and mysql.innodb_index_stats to prepare it
    for being able to rename a table (update the corresponding rows).
    
    * Remove the foreign key from innodb_index_stats because it is a source
      of various deadlocks. Also the absence of FK would allow users to
      temporarily store some rows in innodb_index_stats that do not have
      corresponding rows in innodb_table_stats.
    
    * When saving table and its indexes stats - save the table stats and each
      index stat in a separate transaction to avoid deadlocks.
    
      The previous mode of operation for saving was:
    
      BEGIN;
      SELECT FOR UPDATE;
      if not found
        INSERT;
      else
        UPDATE;
      COMMIT;
    
      Which had the flaw that if there was no entry and two concurrent save
      functions were called, then one of them could get a deadlock, like this:
    
      t1: SELECT FOR UPDATE; (returns not found)
      t2: SELECT FOR UPDATE; (returns not found)
      t1: INSERT; (hangs, waiting on t2)
      t2: INSERT; (fails because of a deadlock)
    
      Another way of operation would be:
    
      BEGIN;
      INSERT;
      if duplicate
        UPDATE;
      COMMIT;
    
      but that could deadlock with a single DELETE of the corresponding entry in
      the table, like this:
    
      t1: INSERT; (returns duplicate)
      t2: DELETE; (hangs, waiting on t1)
      t1: UPDATE; (fails because of a deadlock)
    
      Another solution would have been to synchronize dict_stats_save() and
      dict_stats_delete_table_stats() with a simple mutex, but que_eval_sql()
      does not allow any latches to be held during its execution except
      dict_sys->mutex.
    
      So we change the save of table stats to:
    
      BEGIN;
      INSERT;
      COMMIT;
      if duplicate
        BEGIN;
        UPDATE;
        COMMIT;
    
      At worse, a row could get deleted after INSERT has returned a duplicate error
      and before UPDATE executes, but then the UPDATE will be noop because the
      WHERE clause will match nothing. The effect will be as if the delete executed
      after the end of the save operation.
    
    * When deleting stats, delete the table and the index stats in separate
      transactions.
    5eaf0f15
    Refactor the code that saves, updates and deletes rows from
    Vasil Dimov authored
    mysql.innodb_table_stats and mysql.innodb_index_stats to prepare it
    for being able to rename a table (update the corresponding rows).
    
    * Remove the foreign key from innodb_index_stats because it is a source
      of various deadlocks. Also the absence of FK would allow users to
      temporarily store some rows in innodb_index_stats that do not have
      corresponding rows in innodb_table_stats.
    
    * When saving table and its indexes stats - save the table stats and each
      index stat in a separate transaction to avoid deadlocks.
    
      The previous mode of operation for saving was:
    
      BEGIN;
      SELECT FOR UPDATE;
      if not found
        INSERT;
      else
        UPDATE;
      COMMIT;
    
      Which had the flaw that if there was no entry and two concurrent save
      functions were called, then one of them could get a deadlock, like this:
    
      t1: SELECT FOR UPDATE; (returns not found)
      t2: SELECT FOR UPDATE; (returns not found)
      t1: INSERT; (hangs, waiting on t2)
      t2: INSERT; (fails because of a deadlock)
    
      Another way of operation would be:
    
      BEGIN;
      INSERT;
      if duplicate
        UPDATE;
      COMMIT;
    
      but that could deadlock with a single DELETE of the corresponding entry in
      the table, like this:
    
      t1: INSERT; (returns duplicate)
      t2: DELETE; (hangs, waiting on t1)
      t1: UPDATE; (fails because of a deadlock)
    
      Another solution would have been to synchronize dict_stats_save() and
      dict_stats_delete_table_stats() with a simple mutex, but que_eval_sql()
      does not allow any latches to be held during its execution except
      dict_sys->mutex.
    
      So we change the save of table stats to:
    
      BEGIN;
      INSERT;
      COMMIT;
      if duplicate
        BEGIN;
        UPDATE;
        COMMIT;
    
      At worse, a row could get deleted after INSERT has returned a duplicate error
      and before UPDATE executes, but then the UPDATE will be noop because the
      WHERE clause will match nothing. The effect will be as if the delete executed
      after the end of the save operation.
    
    * When deleting stats, delete the table and the index stats in separate
      transactions.
Loading