You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

536 lines
15 KiB

MDEV-12698 innodb.innodb_stats_del_mark test failure In my merge of the MySQL fix for Oracle Bug#23333990 / WL#9513 I overlooked some subsequent revisions to the test, and I also failed to notice that the test is actually always failing. Oracle introduced the parameter innodb_stats_include_delete_marked but failed to consistently take it into account in FOREIGN KEY constraints that involve CASCADE or SET NULL. When innodb_stats_include_delete_marked=ON, obviously the purge of delete-marked records should update the statistics as well. One more omission was that statistics were never updated on ROLLBACK. We are fixing that as well, properly taking into account the parameter innodb_stats_include_delete_marked. dict_stats_analyze_index_level(): Simplify an expression. (Using the ternary operator with a constant operand is unnecessary obfuscation.) page_scan_method_t: Revert the change done by Oracle. Instead, examine srv_stats_include_delete_marked directly where it is needed. dict_stats_update_if_needed(): Renamed from row_update_statistics_if_needed(). row_update_for_mysql_using_upd_graph(): Assert that the table statistics are initialized, as guaranteed by ha_innobase::open(). Update the statistics in a consistent way, both for FOREIGN KEY triggers and for the main table. If FOREIGN KEY constraints exist, do not dereference a freed pointer, but cache the proper value of node->is_delete so that it matches prebuilt->table. row_purge_record_func(): Update statistics if innodb_stats_include_delete_marked=ON. row_undo_ins(): Update statistics (on ROLLBACK of a fresh INSERT). This is independent of the parameter; the record is not delete-marked. row_undo_mod(): Update statistics on the ROLLBACK of updating key columns, or (if innodb_stats_include_delete_marked=OFF) updating delete-marks. innodb.innodb_stats_persistent: Renamed and extended from innodb.innodb_stats_del_mark. Reduced the unnecessarily large dataset from 262,144 to 32 rows. Test both values of the configuration parameter innodb_stats_include_delete_marked. Test that purge is updating the statistics. innodb_fts.innodb_fts_multiple_index: Adjust the result. The test is performing a ROLLBACK of an INSERT, which now affects the statistics. include/wait_all_purged.inc: Moved from innodb.innodb_truncate_debug to its own file.
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
MDEV-12698 innodb.innodb_stats_del_mark test failure In my merge of the MySQL fix for Oracle Bug#23333990 / WL#9513 I overlooked some subsequent revisions to the test, and I also failed to notice that the test is actually always failing. Oracle introduced the parameter innodb_stats_include_delete_marked but failed to consistently take it into account in FOREIGN KEY constraints that involve CASCADE or SET NULL. When innodb_stats_include_delete_marked=ON, obviously the purge of delete-marked records should update the statistics as well. One more omission was that statistics were never updated on ROLLBACK. We are fixing that as well, properly taking into account the parameter innodb_stats_include_delete_marked. dict_stats_analyze_index_level(): Simplify an expression. (Using the ternary operator with a constant operand is unnecessary obfuscation.) page_scan_method_t: Revert the change done by Oracle. Instead, examine srv_stats_include_delete_marked directly where it is needed. dict_stats_update_if_needed(): Renamed from row_update_statistics_if_needed(). row_update_for_mysql_using_upd_graph(): Assert that the table statistics are initialized, as guaranteed by ha_innobase::open(). Update the statistics in a consistent way, both for FOREIGN KEY triggers and for the main table. If FOREIGN KEY constraints exist, do not dereference a freed pointer, but cache the proper value of node->is_delete so that it matches prebuilt->table. row_purge_record_func(): Update statistics if innodb_stats_include_delete_marked=ON. row_undo_ins(): Update statistics (on ROLLBACK of a fresh INSERT). This is independent of the parameter; the record is not delete-marked. row_undo_mod(): Update statistics on the ROLLBACK of updating key columns, or (if innodb_stats_include_delete_marked=OFF) updating delete-marks. innodb.innodb_stats_persistent: Renamed and extended from innodb.innodb_stats_del_mark. Reduced the unnecessarily large dataset from 262,144 to 32 rows. Test both values of the configuration parameter innodb_stats_include_delete_marked. Test that purge is updating the statistics. innodb_fts.innodb_fts_multiple_index: Adjust the result. The test is performing a ROLLBACK of an INSERT, which now affects the statistics. include/wait_all_purged.inc: Moved from innodb.innodb_truncate_debug to its own file.
9 years ago
  1. /*****************************************************************************
  2. Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
  3. This program is free software; you can redistribute it and/or modify it under
  4. the terms of the GNU General Public License as published by the Free Software
  5. Foundation; version 2 of the License.
  6. This program is distributed in the hope that it will be useful, but WITHOUT
  7. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. You should have received a copy of the GNU General Public License along with
  10. this program; if not, write to the Free Software Foundation, Inc.,
  11. 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
  12. *****************************************************************************/
  13. /**************************************************//**
  14. @file row/row0uins.cc
  15. Fresh insert undo
  16. Created 2/25/1997 Heikki Tuuri
  17. *******************************************************/
  18. #include "row0uins.h"
  19. #include "dict0dict.h"
  20. #include "dict0stats.h"
  21. #include "dict0boot.h"
  22. #include "dict0crea.h"
  23. #include "trx0undo.h"
  24. #include "trx0roll.h"
  25. #include "btr0btr.h"
  26. #include "mach0data.h"
  27. #include "row0undo.h"
  28. #include "row0vers.h"
  29. #include "row0log.h"
  30. #include "trx0trx.h"
  31. #include "trx0rec.h"
  32. #include "row0row.h"
  33. #include "row0upd.h"
  34. #include "que0que.h"
  35. #include "ibuf0ibuf.h"
  36. #include "log0log.h"
  37. #include "fil0fil.h"
  38. /*************************************************************************
  39. IMPORTANT NOTE: Any operation that generates redo MUST check that there
  40. is enough space in the redo log before for that operation. This is
  41. done by calling log_free_check(). The reason for checking the
  42. availability of the redo log space before the start of the operation is
  43. that we MUST not hold any synchonization objects when performing the
  44. check.
  45. If you make a change in this module make sure that no codepath is
  46. introduced where a call to log_free_check() is bypassed. */
  47. /***************************************************************//**
  48. Removes a clustered index record. The pcur in node was positioned on the
  49. record, now it is detached.
  50. @return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
  51. static MY_ATTRIBUTE((nonnull, warn_unused_result))
  52. dberr_t
  53. row_undo_ins_remove_clust_rec(
  54. /*==========================*/
  55. undo_node_t* node) /*!< in: undo node */
  56. {
  57. btr_cur_t* btr_cur;
  58. ibool success;
  59. dberr_t err;
  60. ulint n_tries = 0;
  61. mtr_t mtr;
  62. dict_index_t* index = node->pcur.btr_cur.index;
  63. bool online;
  64. ut_ad(dict_index_is_clust(index));
  65. ut_ad(node->trx->in_rollback);
  66. mtr_start(&mtr);
  67. mtr.set_named_space(index->space);
  68. dict_disable_redo_if_temporary(index->table, &mtr);
  69. /* This is similar to row_undo_mod_clust(). The DDL thread may
  70. already have copied this row from the log to the new table.
  71. We must log the removal, so that the row will be correctly
  72. purged. However, we can log the removal out of sync with the
  73. B-tree modification. */
  74. online = dict_index_is_online_ddl(index);
  75. if (online) {
  76. ut_ad(node->trx->dict_operation_lock_mode
  77. != RW_X_LATCH);
  78. ut_ad(node->table->id != DICT_INDEXES_ID);
  79. mtr_s_lock(dict_index_get_lock(index), &mtr);
  80. }
  81. success = btr_pcur_restore_position(
  82. online
  83. ? BTR_MODIFY_LEAF | BTR_ALREADY_S_LATCHED
  84. : BTR_MODIFY_LEAF, &node->pcur, &mtr);
  85. ut_a(success);
  86. btr_cur = btr_pcur_get_btr_cur(&node->pcur);
  87. ut_ad(rec_get_trx_id(btr_cur_get_rec(btr_cur), btr_cur->index)
  88. == node->trx->id);
  89. ut_ad(!rec_get_deleted_flag(
  90. btr_cur_get_rec(btr_cur),
  91. dict_table_is_comp(btr_cur->index->table)));
  92. if (online && dict_index_is_online_ddl(index)) {
  93. const rec_t* rec = btr_cur_get_rec(btr_cur);
  94. mem_heap_t* heap = NULL;
  95. const ulint* offsets = rec_get_offsets(
  96. rec, index, NULL, ULINT_UNDEFINED, &heap);
  97. row_log_table_delete(rec, node->row, index, offsets, NULL);
  98. mem_heap_free(heap);
  99. }
  100. if (node->table->id == DICT_INDEXES_ID) {
  101. ut_ad(!online);
  102. ut_ad(node->trx->dict_operation_lock_mode == RW_X_LATCH);
  103. dict_drop_index_tree(
  104. btr_pcur_get_rec(&node->pcur), &(node->pcur), &mtr);
  105. mtr_commit(&mtr);
  106. mtr_start(&mtr);
  107. success = btr_pcur_restore_position(
  108. BTR_MODIFY_LEAF, &node->pcur, &mtr);
  109. ut_a(success);
  110. }
  111. if (btr_cur_optimistic_delete(btr_cur, 0, &mtr)) {
  112. err = DB_SUCCESS;
  113. goto func_exit;
  114. }
  115. btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
  116. retry:
  117. /* If did not succeed, try pessimistic descent to tree */
  118. mtr_start(&mtr);
  119. mtr.set_named_space(index->space);
  120. dict_disable_redo_if_temporary(index->table, &mtr);
  121. success = btr_pcur_restore_position(
  122. BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE,
  123. &node->pcur, &mtr);
  124. ut_a(success);
  125. btr_cur_pessimistic_delete(&err, FALSE, btr_cur, 0, true, &mtr);
  126. /* The delete operation may fail if we have little
  127. file space left: TODO: easiest to crash the database
  128. and restart with more file space */
  129. if (err == DB_OUT_OF_FILE_SPACE
  130. && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
  131. btr_pcur_commit_specify_mtr(&(node->pcur), &mtr);
  132. n_tries++;
  133. os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
  134. goto retry;
  135. }
  136. func_exit:
  137. btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
  138. return(err);
  139. }
  140. /***************************************************************//**
  141. Removes a secondary index entry if found.
  142. @return DB_SUCCESS, DB_FAIL, or DB_OUT_OF_FILE_SPACE */
  143. static MY_ATTRIBUTE((nonnull, warn_unused_result))
  144. dberr_t
  145. row_undo_ins_remove_sec_low(
  146. /*========================*/
  147. ulint mode, /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE,
  148. depending on whether we wish optimistic or
  149. pessimistic descent down the index tree */
  150. dict_index_t* index, /*!< in: index */
  151. dtuple_t* entry, /*!< in: index entry to remove */
  152. que_thr_t* thr) /*!< in: query thread */
  153. {
  154. btr_pcur_t pcur;
  155. btr_cur_t* btr_cur;
  156. dberr_t err = DB_SUCCESS;
  157. mtr_t mtr;
  158. enum row_search_result search_result;
  159. ibool modify_leaf = false;
  160. log_free_check();
  161. memset(&pcur, 0, sizeof(pcur));
  162. mtr_start(&mtr);
  163. mtr.set_named_space(index->space);
  164. dict_disable_redo_if_temporary(index->table, &mtr);
  165. if (mode == BTR_MODIFY_LEAF) {
  166. mode = BTR_MODIFY_LEAF | BTR_ALREADY_S_LATCHED;
  167. mtr_s_lock(dict_index_get_lock(index), &mtr);
  168. modify_leaf = true;
  169. } else {
  170. ut_ad(mode == (BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE));
  171. mtr_sx_lock(dict_index_get_lock(index), &mtr);
  172. }
  173. if (row_log_online_op_try(index, entry, 0)) {
  174. goto func_exit_no_pcur;
  175. }
  176. if (dict_index_is_spatial(index)) {
  177. if (mode & BTR_MODIFY_LEAF) {
  178. mode |= BTR_RTREE_DELETE_MARK;
  179. }
  180. btr_pcur_get_btr_cur(&pcur)->thr = thr;
  181. mode |= BTR_RTREE_UNDO_INS;
  182. }
  183. search_result = row_search_index_entry(index, entry, mode,
  184. &pcur, &mtr);
  185. switch (search_result) {
  186. case ROW_NOT_FOUND:
  187. goto func_exit;
  188. case ROW_FOUND:
  189. break;
  190. case ROW_BUFFERED:
  191. case ROW_NOT_DELETED_REF:
  192. /* These are invalid outcomes, because the mode passed
  193. to row_search_index_entry() did not include any of the
  194. flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
  195. ut_error;
  196. }
  197. if (search_result == ROW_FOUND && dict_index_is_spatial(index)) {
  198. rec_t* rec = btr_pcur_get_rec(&pcur);
  199. if (rec_get_deleted_flag(rec,
  200. dict_table_is_comp(index->table))) {
  201. ib::error() << "Record found in index " << index->name
  202. << " is deleted marked on insert rollback.";
  203. }
  204. }
  205. btr_cur = btr_pcur_get_btr_cur(&pcur);
  206. if (modify_leaf) {
  207. err = btr_cur_optimistic_delete(btr_cur, 0, &mtr)
  208. ? DB_SUCCESS : DB_FAIL;
  209. } else {
  210. /* Passing rollback=false here, because we are
  211. deleting a secondary index record: the distinction
  212. only matters when deleting a record that contains
  213. externally stored columns. */
  214. ut_ad(!dict_index_is_clust(index));
  215. btr_cur_pessimistic_delete(&err, FALSE, btr_cur, 0,
  216. false, &mtr);
  217. }
  218. func_exit:
  219. btr_pcur_close(&pcur);
  220. func_exit_no_pcur:
  221. mtr_commit(&mtr);
  222. return(err);
  223. }
  224. /***************************************************************//**
  225. Removes a secondary index entry from the index if found. Tries first
  226. optimistic, then pessimistic descent down the tree.
  227. @return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
  228. static MY_ATTRIBUTE((nonnull, warn_unused_result))
  229. dberr_t
  230. row_undo_ins_remove_sec(
  231. /*====================*/
  232. dict_index_t* index, /*!< in: index */
  233. dtuple_t* entry, /*!< in: index entry to insert */
  234. que_thr_t* thr) /*!< in: query thread */
  235. {
  236. dberr_t err;
  237. ulint n_tries = 0;
  238. /* Try first optimistic descent to the B-tree */
  239. err = row_undo_ins_remove_sec_low(BTR_MODIFY_LEAF, index, entry, thr);
  240. if (err == DB_SUCCESS) {
  241. return(err);
  242. }
  243. /* Try then pessimistic descent to the B-tree */
  244. retry:
  245. err = row_undo_ins_remove_sec_low(
  246. BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE,
  247. index, entry, thr);
  248. /* The delete operation may fail if we have little
  249. file space left: TODO: easiest to crash the database
  250. and restart with more file space */
  251. if (err != DB_SUCCESS && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
  252. n_tries++;
  253. os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
  254. goto retry;
  255. }
  256. return(err);
  257. }
  258. /***********************************************************//**
  259. Parses the row reference and other info in a fresh insert undo record. */
  260. static
  261. void
  262. row_undo_ins_parse_undo_rec(
  263. /*========================*/
  264. undo_node_t* node, /*!< in/out: row undo node */
  265. ibool dict_locked) /*!< in: TRUE if own dict_sys->mutex */
  266. {
  267. dict_index_t* clust_index;
  268. byte* ptr;
  269. undo_no_t undo_no;
  270. table_id_t table_id;
  271. ulint type;
  272. ulint dummy;
  273. bool dummy_extern;
  274. ut_ad(node);
  275. ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &dummy,
  276. &dummy_extern, &undo_no, &table_id);
  277. ut_ad(type == TRX_UNDO_INSERT_REC);
  278. node->rec_type = type;
  279. node->update = NULL;
  280. node->table = dict_table_open_on_id(
  281. table_id, dict_locked, DICT_TABLE_OP_NORMAL);
  282. /* Skip the UNDO if we can't find the table or the .ibd file. */
  283. if (UNIV_UNLIKELY(node->table == NULL)) {
  284. return;
  285. }
  286. if (UNIV_UNLIKELY(!fil_table_accessible(node->table))) {
  287. close_table:
  288. /* Normally, tables should not disappear or become
  289. unaccessible during ROLLBACK, because they should be
  290. protected by InnoDB table locks. TRUNCATE TABLE
  291. or table corruption could be valid exceptions.
  292. FIXME: When running out of temporary tablespace, it
  293. would probably be better to just drop all temporary
  294. tables (and temporary undo log records) of the current
  295. connection, instead of doing this rollback. */
  296. dict_table_close(node->table, dict_locked, FALSE);
  297. node->table = NULL;
  298. } else {
  299. clust_index = dict_table_get_first_index(node->table);
  300. if (clust_index != NULL) {
  301. ptr = trx_undo_rec_get_row_ref(
  302. ptr, clust_index, &node->ref, node->heap);
  303. if (!row_undo_search_clust_to_pcur(node)) {
  304. /* An error probably occurred during
  305. an insert into the clustered index,
  306. after we wrote the undo log record. */
  307. goto close_table;
  308. }
  309. if (node->table->n_v_cols) {
  310. trx_undo_read_v_cols(node->table, ptr,
  311. node->row, false, NULL);
  312. }
  313. } else {
  314. ib::warn() << "Table " << node->table->name
  315. << " has no indexes,"
  316. " ignoring the table";
  317. goto close_table;
  318. }
  319. }
  320. }
  321. /***************************************************************//**
  322. Removes secondary index records.
  323. @return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
  324. static MY_ATTRIBUTE((nonnull, warn_unused_result))
  325. dberr_t
  326. row_undo_ins_remove_sec_rec(
  327. /*========================*/
  328. undo_node_t* node, /*!< in/out: row undo node */
  329. que_thr_t* thr) /*!< in: query thread */
  330. {
  331. dberr_t err = DB_SUCCESS;
  332. dict_index_t* index = node->index;
  333. mem_heap_t* heap;
  334. heap = mem_heap_create(1024);
  335. while (index != NULL) {
  336. dtuple_t* entry;
  337. if (index->type & DICT_FTS) {
  338. dict_table_next_uncorrupted_index(index);
  339. continue;
  340. }
  341. /* An insert undo record TRX_UNDO_INSERT_REC will
  342. always contain all fields of the index. It does not
  343. matter if any indexes were created afterwards; all
  344. index entries can be reconstructed from the row. */
  345. entry = row_build_index_entry(
  346. node->row, node->ext, index, heap);
  347. if (UNIV_UNLIKELY(!entry)) {
  348. /* The database must have crashed after
  349. inserting a clustered index record but before
  350. writing all the externally stored columns of
  351. that record, or a statement is being rolled
  352. back because an error occurred while storing
  353. off-page columns.
  354. Because secondary index entries are inserted
  355. after the clustered index record, we may
  356. assume that the secondary index record does
  357. not exist. */
  358. } else {
  359. err = row_undo_ins_remove_sec(index, entry, thr);
  360. if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
  361. goto func_exit;
  362. }
  363. }
  364. mem_heap_empty(heap);
  365. dict_table_next_uncorrupted_index(index);
  366. }
  367. func_exit:
  368. node->index = index;
  369. mem_heap_free(heap);
  370. return(err);
  371. }
  372. /***********************************************************//**
  373. Undoes a fresh insert of a row to a table. A fresh insert means that
  374. the same clustered index unique key did not have any record, even delete
  375. marked, at the time of the insert. InnoDB is eager in a rollback:
  376. if it figures out that an index record will be removed in the purge
  377. anyway, it will remove it in the rollback.
  378. @return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
  379. dberr_t
  380. row_undo_ins(
  381. /*=========*/
  382. undo_node_t* node, /*!< in: row undo node */
  383. que_thr_t* thr) /*!< in: query thread */
  384. {
  385. dberr_t err;
  386. ibool dict_locked;
  387. ut_ad(node->state == UNDO_NODE_INSERT);
  388. ut_ad(node->trx->in_rollback);
  389. ut_ad(trx_undo_roll_ptr_is_insert(node->roll_ptr));
  390. dict_locked = node->trx->dict_operation_lock_mode == RW_X_LATCH;
  391. row_undo_ins_parse_undo_rec(node, dict_locked);
  392. if (node->table == NULL) {
  393. return(DB_SUCCESS);
  394. }
  395. /* Iterate over all the indexes and undo the insert.*/
  396. node->index = dict_table_get_first_index(node->table);
  397. ut_ad(dict_index_is_clust(node->index));
  398. /* Skip the clustered index (the first index) */
  399. node->index = dict_table_get_next_index(node->index);
  400. dict_table_skip_corrupt_index(node->index);
  401. err = row_undo_ins_remove_sec_rec(node, thr);
  402. if (err == DB_SUCCESS) {
  403. log_free_check();
  404. if (node->table->id == DICT_INDEXES_ID) {
  405. if (!dict_locked) {
  406. mutex_enter(&dict_sys->mutex);
  407. }
  408. }
  409. // FIXME: We need to update the dict_index_t::space and
  410. // page number fields too.
  411. err = row_undo_ins_remove_clust_rec(node);
  412. if (node->table->id == DICT_INDEXES_ID
  413. && !dict_locked) {
  414. mutex_exit(&dict_sys->mutex);
  415. }
  416. if (err == DB_SUCCESS && node->table->stat_initialized) {
  417. /* Not protected by dict_table_stats_lock() for
  418. performance reasons, we would rather get garbage
  419. in stat_n_rows (which is just an estimate anyway)
  420. than protecting the following code with a latch. */
  421. dict_table_n_rows_dec(node->table);
  422. /* Do not attempt to update statistics when
  423. executing ROLLBACK in the InnoDB SQL
  424. interpreter, because in that case we would
  425. already be holding dict_sys->mutex, which
  426. would be acquired when updating statistics. */
  427. if (!dict_locked) {
  428. dict_stats_update_if_needed(node->table);
  429. }
  430. }
  431. }
  432. dict_table_close(node->table, dict_locked, FALSE);
  433. node->table = NULL;
  434. return(err);
  435. }