1
/******************************************************
6
Created 3/14/1997 Heikki Tuuri
7
*******************************************************/
12
#include "row0purge.ic"
16
#include "mach0data.h"
21
#include "trx0purge.h"
27
#include "row0mysql.h"
30
/************************************************************************
31
Creates a purge node to a query graph. */
34
row_purge_node_create(
35
/*==================*/
36
/* out, own: purge node */
37
que_thr_t* parent, /* in: parent node, i.e., a thr node */
38
mem_heap_t* heap) /* in: memory heap where created */
42
ut_ad(parent && heap);
44
node = mem_heap_alloc(heap, sizeof(purge_node_t));
46
node->common.type = QUE_NODE_PURGE;
47
node->common.parent = parent;
49
node->heap = mem_heap_create(256);
54
/***************************************************************
55
Repositions the pcur in the purge node on the clustered index record,
59
row_purge_reposition_pcur(
60
/*======================*/
61
/* out: TRUE if the record was found */
62
ulint mode, /* in: latching mode */
63
purge_node_t* node, /* in: row purge node */
64
mtr_t* mtr) /* in: mtr */
68
if (node->found_clust) {
69
found = btr_pcur_restore_position(mode, &(node->pcur), mtr);
74
found = row_search_on_row_ref(&(node->pcur), mode, node->table,
76
node->found_clust = found;
79
btr_pcur_store_position(&(node->pcur), mtr);
85
/***************************************************************
86
Removes a delete marked clustered index record if possible. */
89
row_purge_remove_clust_if_poss_low(
90
/*===============================*/
91
/* out: TRUE if success, or if not found, or
92
if modified after the delete marking */
93
purge_node_t* node, /* in: row purge node */
94
ulint mode) /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
103
mem_heap_t* heap = NULL;
104
ulint offsets_[REC_OFFS_NORMAL_SIZE];
105
rec_offs_init(offsets_);
107
index = dict_table_get_first_index(node->table);
109
pcur = &(node->pcur);
110
btr_cur = btr_pcur_get_btr_cur(pcur);
114
success = row_purge_reposition_pcur(mode, node, &mtr);
117
/* The record is already removed */
119
btr_pcur_commit_specify_mtr(pcur, &mtr);
124
rec = btr_pcur_get_rec(pcur);
126
if (0 != ut_dulint_cmp(node->roll_ptr, row_get_rec_roll_ptr(
127
rec, index, rec_get_offsets(
128
rec, index, offsets_,
129
ULINT_UNDEFINED, &heap)))) {
130
if (UNIV_LIKELY_NULL(heap)) {
133
/* Someone else has modified the record later: do not remove */
134
btr_pcur_commit_specify_mtr(pcur, &mtr);
139
if (UNIV_LIKELY_NULL(heap)) {
143
if (mode == BTR_MODIFY_LEAF) {
144
success = btr_cur_optimistic_delete(btr_cur, &mtr);
146
ut_ad(mode == BTR_MODIFY_TREE);
147
btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
150
if (err == DB_SUCCESS) {
152
} else if (err == DB_OUT_OF_FILE_SPACE) {
159
btr_pcur_commit_specify_mtr(pcur, &mtr);
164
/***************************************************************
165
Removes a clustered index record if it has not been modified after the delete
169
row_purge_remove_clust_if_poss(
170
/*===========================*/
171
purge_node_t* node) /* in: row purge node */
176
/* fputs("Purge: Removing clustered record\n", stderr); */
178
success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF);
184
success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_TREE);
185
/* The delete operation may fail if we have little
186
file space left: TODO: easiest to crash the database
187
and restart with more file space */
189
if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
192
os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
200
/***************************************************************
201
Removes a secondary index entry if possible. */
204
row_purge_remove_sec_if_poss_low(
205
/*=============================*/
206
/* out: TRUE if success or if not found */
207
purge_node_t* node, /* in: row purge node */
208
dict_index_t* index, /* in: index */
209
const dtuple_t* entry, /* in: index entry */
210
ulint mode) /* in: latch mode BTR_MODIFY_LEAF or
216
ibool old_has = 0; /* remove warning */
225
found = row_search_index_entry(index, entry, mode, &pcur, &mtr);
230
/* fputs("PURGE:........sec entry not found\n", stderr); */
231
/* dtuple_print(stderr, entry); */
233
btr_pcur_close(&pcur);
239
btr_cur = btr_pcur_get_btr_cur(&pcur);
241
/* We should remove the index record if no later version of the row,
242
which cannot be purged yet, requires its existence. If some requires,
243
we should do nothing. */
245
mtr_vers = mem_alloc(sizeof(mtr_t));
249
success = row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, mtr_vers);
252
old_has = row_vers_old_has_index_entry(
253
TRUE, btr_pcur_get_rec(&(node->pcur)),
254
mtr_vers, index, entry);
257
btr_pcur_commit_specify_mtr(&(node->pcur), mtr_vers);
261
if (!success || !old_has) {
262
/* Remove the index record */
264
if (mode == BTR_MODIFY_LEAF) {
265
success = btr_cur_optimistic_delete(btr_cur, &mtr);
267
ut_ad(mode == BTR_MODIFY_TREE);
268
btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
270
success = err == DB_SUCCESS;
271
ut_a(success || err == DB_OUT_OF_FILE_SPACE);
275
btr_pcur_close(&pcur);
281
/***************************************************************
282
Removes a secondary index entry if possible. */
285
row_purge_remove_sec_if_poss(
286
/*=========================*/
287
purge_node_t* node, /* in: row purge node */
288
dict_index_t* index, /* in: index */
289
dtuple_t* entry) /* in: index entry */
294
/* fputs("Purge: Removing secondary record\n", stderr); */
296
success = row_purge_remove_sec_if_poss_low(node, index, entry,
303
success = row_purge_remove_sec_if_poss_low(node, index, entry,
305
/* The delete operation may fail if we have little
306
file space left: TODO: easiest to crash the database
307
and restart with more file space */
309
if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
313
os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
321
/***************************************************************
322
Purges a delete marking of a record. */
327
purge_node_t* node) /* in: row purge node */
335
heap = mem_heap_create(1024);
337
while (node->index != NULL) {
340
/* Build the index entry */
341
entry = row_build_index_entry(node->row, NULL, index, heap);
343
row_purge_remove_sec_if_poss(node, index, entry);
345
node->index = dict_table_get_next_index(node->index);
350
row_purge_remove_clust_if_poss(node);
353
/***************************************************************
354
Purges an update of an existing record. Also purges an update of a delete
355
marked record if that record contained an externally stored field. */
358
row_purge_upd_exist_or_extern(
359
/*==========================*/
360
purge_node_t* node) /* in: row purge node */
374
if (node->rec_type == TRX_UNDO_UPD_DEL_REC) {
376
goto skip_secondaries;
379
heap = mem_heap_create(1024);
381
while (node->index != NULL) {
384
if (row_upd_changes_ord_field_binary(NULL, node->index,
386
/* Build the older version of the index entry */
387
entry = row_build_index_entry(node->row, NULL,
390
row_purge_remove_sec_if_poss(node, index, entry);
393
node->index = dict_table_get_next_index(node->index);
399
/* Free possible externally stored fields */
400
for (i = 0; i < upd_get_n_fields(node->update); i++) {
402
const upd_field_t* ufield
403
= upd_get_nth_field(node->update, i);
405
if (dfield_is_ext(&ufield->new_val)) {
407
ulint internal_offset;
410
/* We use the fact that new_val points to
411
node->undo_rec and get thus the offset of
412
dfield data inside the undo record. Then we
413
can calculate from node->roll_ptr the file
414
address of the new_val data */
418
dfield_get_data(&ufield->new_val))
421
ut_a(internal_offset < UNIV_PAGE_SIZE);
423
trx_undo_decode_roll_ptr(node->roll_ptr,
424
&is_insert, &rseg_id,
428
/* We have to acquire an X-latch to the clustered
431
index = dict_table_get_first_index(node->table);
433
mtr_x_lock(dict_index_get_lock(index), &mtr);
435
/* NOTE: we must also acquire an X-latch to the
436
root page of the tree. We will need it when we
437
free pages from the tree. If the tree is of height 1,
438
the tree X-latch does NOT protect the root page,
439
because it is also a leaf page. Since we will have a
440
latch on an undo log page, we would break the
441
latching order if we would only later latch the
442
root page of such a tree! */
444
btr_root_get(index, &mtr);
446
/* We assume in purge of externally stored fields
447
that the space id of the undo log record is 0! */
449
block = buf_page_get(0, 0, page_no, RW_X_LATCH, &mtr);
450
buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
452
data_field = buf_block_get_frame(block)
453
+ offset + internal_offset;
455
ut_a(dfield_get_len(&ufield->new_val)
456
>= BTR_EXTERN_FIELD_REF_SIZE);
457
btr_free_externally_stored_field(
459
data_field + dfield_get_len(&ufield->new_val)
460
- BTR_EXTERN_FIELD_REF_SIZE,
461
NULL, NULL, NULL, 0, RB_NONE, &mtr);
467
/***************************************************************
468
Parses the row reference and other info in a modify undo log record. */
471
row_purge_parse_undo_rec(
472
/*=====================*/
473
/* out: TRUE if purge operation required:
474
NOTE that then the CALLER must unfreeze
476
purge_node_t* node, /* in: row undo node */
477
ibool* updated_extern,
478
/* out: TRUE if an externally stored field
480
que_thr_t* thr) /* in: query thread */
482
dict_index_t* clust_index;
495
trx = thr_get_trx(thr);
497
ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info,
498
updated_extern, &undo_no, &table_id);
499
node->rec_type = type;
501
if (type == TRX_UNDO_UPD_DEL_REC && !(*updated_extern)) {
506
ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr,
510
if (type == TRX_UNDO_UPD_EXIST_REC
511
&& cmpl_info & UPD_NODE_NO_ORD_CHANGE && !(*updated_extern)) {
513
/* Purge requires no changes to indexes: we may return */
518
/* Prevent DROP TABLE etc. from running when we are doing the purge
521
row_mysql_freeze_data_dictionary(trx);
523
mutex_enter(&(dict_sys->mutex));
525
node->table = dict_table_get_on_id_low(table_id);
527
mutex_exit(&(dict_sys->mutex));
529
if (node->table == NULL) {
530
/* The table has been dropped: no need to do purge */
532
row_mysql_unfreeze_data_dictionary(trx);
536
if (node->table->ibd_file_missing) {
537
/* We skip purge of missing .ibd files */
544
clust_index = dict_table_get_first_index(node->table);
546
if (clust_index == NULL) {
547
/* The table was corrupt in the data dictionary */
552
ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
555
ptr = trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id,
556
roll_ptr, info_bits, trx,
557
node->heap, &(node->update));
559
/* Read to the partial row the fields that occur in indexes */
561
if (!(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
562
ptr = trx_undo_rec_get_partial_row(
563
ptr, clust_index, &node->row,
564
type == TRX_UNDO_UPD_DEL_REC,
571
/***************************************************************
572
Fetches an undo log record and does the purge for the recorded operation.
573
If none left, or the current purge completed, returns the control to the
574
parent node, which is always a query thread node. */
579
/* out: DB_SUCCESS if operation successfully
580
completed, else error code */
581
purge_node_t* node, /* in: row purge node */
582
que_thr_t* thr) /* in: query thread */
586
ibool updated_extern;
591
trx = thr_get_trx(thr);
593
node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr,
594
&(node->reservation),
596
if (!node->undo_rec) {
597
/* Purge completed for this query thread */
599
thr->run_node = que_node_get_parent(node);
604
node->roll_ptr = roll_ptr;
606
if (node->undo_rec == &trx_purge_dummy_rec) {
607
purge_needed = FALSE;
609
purge_needed = row_purge_parse_undo_rec(node, &updated_extern,
611
/* If purge_needed == TRUE, we must also remember to unfreeze
616
node->found_clust = FALSE;
618
node->index = dict_table_get_next_index(
619
dict_table_get_first_index(node->table));
621
if (node->rec_type == TRX_UNDO_DEL_MARK_REC) {
622
row_purge_del_mark(node);
624
} else if (updated_extern
625
|| node->rec_type == TRX_UNDO_UPD_EXIST_REC) {
627
row_purge_upd_exist_or_extern(node);
630
if (node->found_clust) {
631
btr_pcur_close(&(node->pcur));
634
row_mysql_unfreeze_data_dictionary(trx);
637
/* Do some cleanup */
638
trx_purge_rec_release(node->reservation);
639
mem_heap_empty(node->heap);
641
thr->run_node = node;
646
/***************************************************************
647
Does the purge operation for a single undo log record. This is a high-level
648
function used in an SQL execution graph. */
653
/* out: query thread to run next or NULL */
654
que_thr_t* thr) /* in: query thread */
661
node = thr->run_node;
663
ut_ad(que_node_get_type(node) == QUE_NODE_PURGE);
665
err = row_purge(node, thr);
667
ut_ad(err == DB_SUCCESS);