-
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.
Vasil Dimov authoredmysql.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