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, FALSE, &mtr);
149
if (err == DB_SUCCESS) {
151
} else if (err == DB_OUT_OF_FILE_SPACE) {
158
btr_pcur_commit_specify_mtr(pcur, &mtr);
163
/***************************************************************
164
Removes a clustered index record if it has not been modified after the delete
168
row_purge_remove_clust_if_poss(
169
/*===========================*/
170
purge_node_t* node) /* in: row purge node */
175
/* fputs("Purge: Removing clustered record\n", stderr); */
177
success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF);
183
success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_TREE);
184
/* The delete operation may fail if we have little
185
file space left: TODO: easiest to crash the database
186
and restart with more file space */
188
if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
191
os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
199
/***************************************************************
200
Removes a secondary index entry if possible. */
203
row_purge_remove_sec_if_poss_low(
204
/*=============================*/
205
/* out: TRUE if success or if not found */
206
purge_node_t* node, /* in: row purge node */
207
dict_index_t* index, /* in: index */
208
dtuple_t* entry, /* in: index entry */
209
ulint mode) /* in: latch mode BTR_MODIFY_LEAF or
215
ibool old_has = 0; /* remove warning */
224
found = row_search_index_entry(index, entry, mode, &pcur, &mtr);
229
/* fputs("PURGE:........sec entry not found\n", stderr); */
230
/* dtuple_print(stderr, entry); */
232
btr_pcur_close(&pcur);
238
btr_cur = btr_pcur_get_btr_cur(&pcur);
240
/* We should remove the index record if no later version of the row,
241
which cannot be purged yet, requires its existence. If some requires,
242
we should do nothing. */
244
mtr_vers = mem_alloc(sizeof(mtr_t));
248
success = row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, mtr_vers);
251
old_has = row_vers_old_has_index_entry(
252
TRUE, btr_pcur_get_rec(&(node->pcur)),
253
mtr_vers, index, entry);
256
btr_pcur_commit_specify_mtr(&(node->pcur), mtr_vers);
260
if (!success || !old_has) {
261
/* Remove the index record */
263
if (mode == BTR_MODIFY_LEAF) {
264
success = btr_cur_optimistic_delete(btr_cur, &mtr);
266
ut_ad(mode == BTR_MODIFY_TREE);
267
btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
269
success = err == DB_SUCCESS;
270
ut_a(success || err == DB_OUT_OF_FILE_SPACE);
274
btr_pcur_close(&pcur);
280
/***************************************************************
281
Removes a secondary index entry if possible. */
284
row_purge_remove_sec_if_poss(
285
/*=========================*/
286
purge_node_t* node, /* in: row purge node */
287
dict_index_t* index, /* in: index */
288
dtuple_t* entry) /* in: index entry */
293
/* fputs("Purge: Removing secondary record\n", stderr); */
295
success = row_purge_remove_sec_if_poss_low(node, index, entry,
302
success = row_purge_remove_sec_if_poss_low(node, index, entry,
304
/* The delete operation may fail if we have little
305
file space left: TODO: easiest to crash the database
306
and restart with more file space */
308
if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
312
os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
320
/***************************************************************
321
Purges a delete marking of a record. */
326
purge_node_t* node) /* in: row purge node */
334
heap = mem_heap_create(1024);
336
while (node->index != NULL) {
339
/* Build the index entry */
340
entry = row_build_index_entry(node->row, NULL, index, heap);
342
row_purge_remove_sec_if_poss(node, index, entry);
344
node->index = dict_table_get_next_index(node->index);
349
row_purge_remove_clust_if_poss(node);
352
/***************************************************************
353
Purges an update of an existing record. Also purges an update of a delete
354
marked record if that record contained an externally stored field. */
357
row_purge_upd_exist_or_extern(
358
/*==========================*/
359
purge_node_t* node) /* in: row purge node */
373
if (node->rec_type == TRX_UNDO_UPD_DEL_REC) {
375
goto skip_secondaries;
378
heap = mem_heap_create(1024);
380
while (node->index != NULL) {
383
if (row_upd_changes_ord_field_binary(NULL, node->index,
385
/* Build the older version of the index entry */
386
entry = row_build_index_entry(node->row, NULL,
389
row_purge_remove_sec_if_poss(node, index, entry);
392
node->index = dict_table_get_next_index(node->index);
398
/* Free possible externally stored fields */
399
for (i = 0; i < upd_get_n_fields(node->update); i++) {
401
const upd_field_t* ufield
402
= upd_get_nth_field(node->update, i);
404
if (dfield_is_ext(&ufield->new_val)) {
406
ulint internal_offset;
409
/* We use the fact that new_val points to
410
node->undo_rec and get thus the offset of
411
dfield data inside the undo record. Then we
412
can calculate from node->roll_ptr the file
413
address of the new_val data */
417
dfield_get_data(&ufield->new_val))
420
ut_a(internal_offset < UNIV_PAGE_SIZE);
422
trx_undo_decode_roll_ptr(node->roll_ptr,
423
&is_insert, &rseg_id,
427
/* We have to acquire an X-latch to the clustered
430
index = dict_table_get_first_index(node->table);
432
mtr_x_lock(dict_index_get_lock(index), &mtr);
434
/* NOTE: we must also acquire an X-latch to the
435
root page of the tree. We will need it when we
436
free pages from the tree. If the tree is of height 1,
437
the tree X-latch does NOT protect the root page,
438
because it is also a leaf page. Since we will have a
439
latch on an undo log page, we would break the
440
latching order if we would only later latch the
441
root page of such a tree! */
443
btr_root_get(index, &mtr);
445
/* We assume in purge of externally stored fields
446
that the space id of the undo log record is 0! */
448
block = buf_page_get(0, 0, page_no, RW_X_LATCH, &mtr);
449
#ifdef UNIV_SYNC_DEBUG
450
buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
451
#endif /* UNIV_SYNC_DEBUG */
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, FALSE, &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(ptr, clust_index,
563
&node->row, node->heap);
569
/***************************************************************
570
Fetches an undo log record and does the purge for the recorded operation.
571
If none left, or the current purge completed, returns the control to the
572
parent node, which is always a query thread node. */
577
/* out: DB_SUCCESS if operation successfully
578
completed, else error code */
579
purge_node_t* node, /* in: row purge node */
580
que_thr_t* thr) /* in: query thread */
584
ibool updated_extern;
589
trx = thr_get_trx(thr);
591
node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr,
592
&(node->reservation),
594
if (!node->undo_rec) {
595
/* Purge completed for this query thread */
597
thr->run_node = que_node_get_parent(node);
602
node->roll_ptr = roll_ptr;
604
if (node->undo_rec == &trx_purge_dummy_rec) {
605
purge_needed = FALSE;
607
purge_needed = row_purge_parse_undo_rec(node, &updated_extern,
609
/* If purge_needed == TRUE, we must also remember to unfreeze
614
node->found_clust = FALSE;
616
node->index = dict_table_get_next_index(
617
dict_table_get_first_index(node->table));
619
if (node->rec_type == TRX_UNDO_DEL_MARK_REC) {
620
row_purge_del_mark(node);
622
} else if (updated_extern
623
|| node->rec_type == TRX_UNDO_UPD_EXIST_REC) {
625
row_purge_upd_exist_or_extern(node);
628
if (node->found_clust) {
629
btr_pcur_close(&(node->pcur));
632
row_mysql_unfreeze_data_dictionary(trx);
635
/* Do some cleanup */
636
trx_purge_rec_release(node->reservation);
637
mem_heap_empty(node->heap);
639
thr->run_node = node;
644
/***************************************************************
645
Does the purge operation for a single undo log record. This is a high-level
646
function used in an SQL execution graph. */
651
/* out: query thread to run next or NULL */
652
que_thr_t* thr) /* in: query thread */
659
node = thr->run_node;
661
ut_ad(que_node_get_type(node) == QUE_NODE_PURGE);
663
err = row_purge(node, thr);
665
ut_ad(err == DB_SUCCESS);