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
*offsets_ = (sizeof offsets_) / sizeof *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(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
if (err == DB_SUCCESS) {
271
} else if (err == DB_OUT_OF_FILE_SPACE) {
279
btr_pcur_close(&pcur);
285
/***************************************************************
286
Removes a secondary index entry if possible. */
289
row_purge_remove_sec_if_poss(
290
/*=========================*/
291
purge_node_t* node, /* in: row purge node */
292
dict_index_t* index, /* in: index */
293
dtuple_t* entry) /* in: index entry */
298
/* fputs("Purge: Removing secondary record\n", stderr); */
300
success = row_purge_remove_sec_if_poss_low(node, index, entry,
307
success = row_purge_remove_sec_if_poss_low(node, index, entry,
309
/* The delete operation may fail if we have little
310
file space left: TODO: easiest to crash the database
311
and restart with more file space */
313
if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
317
os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
325
/***************************************************************
326
Purges a delete marking of a record. */
331
purge_node_t* node) /* in: row purge node */
339
heap = mem_heap_create(1024);
341
while (node->index != NULL) {
344
/* Build the index entry */
345
entry = row_build_index_entry(node->row, index, heap);
347
row_purge_remove_sec_if_poss(node, index, entry);
349
node->index = dict_table_get_next_index(node->index);
354
row_purge_remove_clust_if_poss(node);
357
/***************************************************************
358
Purges an update of an existing record. Also purges an update of a delete
359
marked record if that record contained an externally stored field. */
362
row_purge_upd_exist_or_extern(
363
/*==========================*/
364
purge_node_t* node) /* in: row purge node */
374
ulint internal_offset;
376
ulint data_field_len;
382
if (node->rec_type == TRX_UNDO_UPD_DEL_REC) {
384
goto skip_secondaries;
387
heap = mem_heap_create(1024);
389
while (node->index != NULL) {
392
if (row_upd_changes_ord_field_binary(NULL, node->index,
394
/* Build the older version of the index entry */
395
entry = row_build_index_entry(node->row, index, heap);
397
row_purge_remove_sec_if_poss(node, index, entry);
400
node->index = dict_table_get_next_index(node->index);
406
/* Free possible externally stored fields */
407
for (i = 0; i < upd_get_n_fields(node->update); i++) {
409
ufield = upd_get_nth_field(node->update, i);
411
if (ufield->extern_storage) {
412
/* We use the fact that new_val points to
413
node->undo_rec and get thus the offset of
414
dfield data inside the unod record. Then we
415
can calculate from node->roll_ptr the file
416
address of the new_val data */
418
internal_offset = ((byte*)ufield->new_val.data)
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
data_field = buf_page_get(0, page_no, RW_X_LATCH, &mtr)
450
+ offset + internal_offset;
452
#ifdef UNIV_SYNC_DEBUG
453
buf_page_dbg_add_level(buf_frame_align(data_field),
455
#endif /* UNIV_SYNC_DEBUG */
457
data_field_len = ufield->new_val.len;
459
btr_free_externally_stored_field(index, data_field,
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);
537
if (node->table->ibd_file_missing) {
538
/* We skip purge of missing .ibd files */
542
row_mysql_unfreeze_data_dictionary(trx);
547
clust_index = dict_table_get_first_index(node->table);
549
if (clust_index == NULL) {
550
/* The table was corrupt in the data dictionary */
552
row_mysql_unfreeze_data_dictionary(trx);
557
ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
560
ptr = trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id,
561
roll_ptr, info_bits, trx,
562
node->heap, &(node->update));
564
/* Read to the partial row the fields that occur in indexes */
566
if (!(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
567
ptr = trx_undo_rec_get_partial_row(ptr, clust_index,
568
&(node->row), node->heap);
574
/***************************************************************
575
Fetches an undo log record and does the purge for the recorded operation.
576
If none left, or the current purge completed, returns the control to the
577
parent node, which is always a query thread node. */
582
/* out: DB_SUCCESS if operation successfully
583
completed, else error code */
584
purge_node_t* node, /* in: row purge node */
585
que_thr_t* thr) /* in: query thread */
589
ibool updated_extern;
594
trx = thr_get_trx(thr);
596
node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr,
597
&(node->reservation),
599
if (!node->undo_rec) {
600
/* Purge completed for this query thread */
602
thr->run_node = que_node_get_parent(node);
607
node->roll_ptr = roll_ptr;
609
if (node->undo_rec == &trx_purge_dummy_rec) {
610
purge_needed = FALSE;
612
purge_needed = row_purge_parse_undo_rec(node, &updated_extern,
614
/* If purge_needed == TRUE, we must also remember to unfreeze
619
node->found_clust = FALSE;
621
node->index = dict_table_get_next_index(
622
dict_table_get_first_index(node->table));
624
if (node->rec_type == TRX_UNDO_DEL_MARK_REC) {
625
row_purge_del_mark(node);
627
} else if (updated_extern
628
|| node->rec_type == TRX_UNDO_UPD_EXIST_REC) {
630
row_purge_upd_exist_or_extern(node);
633
if (node->found_clust) {
634
btr_pcur_close(&(node->pcur));
637
row_mysql_unfreeze_data_dictionary(trx);
640
/* Do some cleanup */
641
trx_purge_rec_release(node->reservation);
642
mem_heap_empty(node->heap);
644
thr->run_node = node;
649
/***************************************************************
650
Does the purge operation for a single undo log record. This is a high-level
651
function used in an SQL execution graph. */
656
/* out: query thread to run next or NULL */
657
que_thr_t* thr) /* in: query thread */
664
node = thr->run_node;
666
ut_ad(que_node_get_type(node) == QUE_NODE_PURGE);
668
err = row_purge(node, thr);
670
ut_ad(err == DB_SUCCESS);