1
/*****************************************************************************
3
Copyright (C) 1996, 2009, Innobase Oy. All Rights Reserved.
5
This program is free software; you can redistribute it and/or modify it under
6
the terms of the GNU General Public License as published by the Free Software
7
Foundation; version 2 of the License.
9
This program is distributed in the hope that it will be useful, but WITHOUT
10
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
You should have received a copy of the GNU General Public License along with
14
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
15
St, Fifth Floor, Boston, MA 02110-1301 USA
17
*****************************************************************************/
19
/**************************************************//**
23
Created 3/26/1996 Heikki Tuuri
24
*******************************************************/
29
#include "trx0roll.ic"
33
#include "mach0data.h"
40
#include "srv0start.h"
42
#include "row0mysql.h"
43
#include "lock0lock.h"
44
#include "pars0pars.h"
46
/** This many pages must be undone before a truncate is tried within
48
#define TRX_ROLL_TRUNC_THRESHOLD 1
50
/** In crash recovery, the current trx to be rolled back */
51
static trx_t* trx_roll_crash_recv_trx = NULL;
53
/** In crash recovery we set this to the undo n:o of the current trx to be
54
rolled back. Then we can print how many % the rollback has progressed. */
55
static undo_no_t trx_roll_max_undo_no;
57
/** Auxiliary variable which tells the previous progress % we printed */
58
static ulint trx_roll_progress_printed_pct;
60
/*******************************************************************//**
61
Rollback a transaction used in MySQL.
62
@return error code or DB_SUCCESS */
65
trx_general_rollback_for_mysql(
66
/*===========================*/
67
trx_t* trx, /*!< in: transaction handle */
68
trx_savept_t* savept) /*!< in: pointer to savepoint undo number, if
69
partial rollback requested, or NULL for
74
roll_node_t* roll_node;
76
/* Tell Innobase server that there might be work for
79
srv_active_wake_master_thread();
81
trx_start_if_not_started(trx);
83
heap = mem_heap_create(512);
85
roll_node = roll_node_create(heap);
88
roll_node->partial = TRUE;
89
roll_node->savept = *savept;
92
trx->error_state = DB_SUCCESS;
94
thr = pars_complete_graph_for_exec(roll_node, trx, heap);
96
ut_a(thr == que_fork_start_command(static_cast<que_fork_t *>(que_node_get_parent(thr))));
99
mutex_enter(&kernel_mutex);
101
while (trx->que_state != TRX_QUE_RUNNING) {
103
mutex_exit(&kernel_mutex);
105
os_thread_sleep(100000);
107
mutex_enter(&kernel_mutex);
110
mutex_exit(&kernel_mutex);
114
ut_a(trx->error_state == DB_SUCCESS);
116
/* Tell Innobase server that there might be work for
119
srv_active_wake_master_thread();
121
return((int) trx->error_state);
124
/*******************************************************************//**
125
Rollback a transaction used in MySQL.
126
@return error code or DB_SUCCESS */
129
trx_rollback_for_mysql(
130
/*===================*/
131
trx_t* trx) /*!< in: transaction handle */
135
if (trx->conc_state == TRX_NOT_STARTED) {
140
trx->op_info = "rollback";
142
/* If we are doing the XA recovery of prepared transactions, then
143
the transaction object does not have an InnoDB session object, and we
144
set a dummy session that we use for all MySQL transactions. */
146
err = trx_general_rollback_for_mysql(trx, NULL);
153
/*******************************************************************//**
154
Rollback the latest SQL statement for MySQL.
155
@return error code or DB_SUCCESS */
158
trx_rollback_last_sql_stat_for_mysql(
159
/*=================================*/
160
trx_t* trx) /*!< in: transaction handle */
164
if (trx->conc_state == TRX_NOT_STARTED) {
169
trx->op_info = "rollback of SQL statement";
171
err = trx_general_rollback_for_mysql(trx, &trx->last_sql_stat_start);
172
/* The following call should not be needed, but we play safe: */
173
trx_mark_sql_stat_end(trx);
180
/*******************************************************************//**
181
Frees a single savepoint struct. */
184
trx_roll_savepoint_free(
185
/*=====================*/
186
trx_t* trx, /*!< in: transaction handle */
187
trx_named_savept_t* savep) /*!< in: savepoint to free */
190
ut_a(UT_LIST_GET_LEN(trx->trx_savepoints) > 0);
192
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
193
mem_free(savep->name);
197
/*******************************************************************//**
198
Frees savepoint structs starting from savep, if savep == NULL then
199
free all savepoints. */
202
trx_roll_savepoints_free(
203
/*=====================*/
204
trx_t* trx, /*!< in: transaction handle */
205
trx_named_savept_t* savep) /*!< in: free all savepoints > this one;
206
if this is NULL, free all savepoints
209
trx_named_savept_t* next_savep;
212
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
214
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
217
while (savep != NULL) {
218
next_savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
220
trx_roll_savepoint_free(trx, savep);
226
/*******************************************************************//**
227
Rolls back a transaction back to a named savepoint. Modifications after the
228
savepoint are undone but InnoDB does NOT release the corresponding locks
229
which are stored in memory. If a lock is 'implicit', that is, a new inserted
230
row holds a lock where the lock information is carried by the trx id stored in
231
the row, these locks are naturally released in the rollback. Savepoints which
232
were set after this savepoint are deleted.
233
@return if no savepoint of the name found then DB_NO_SAVEPOINT,
234
otherwise DB_SUCCESS */
237
trx_rollback_to_savepoint_for_mysql(
238
/*================================*/
239
trx_t* trx, /*!< in: transaction handle */
240
const char* savepoint_name, /*!< in: savepoint name */
241
ib_int64_t* mysql_binlog_cache_pos) /*!< out: the MySQL binlog cache
242
position corresponding to this
243
savepoint; MySQL needs this
244
information to remove the
245
binlog entries of the queries
246
executed after the savepoint */
248
trx_named_savept_t* savep;
251
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
253
while (savep != NULL) {
254
if (0 == ut_strcmp(savep->name, savepoint_name)) {
258
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
263
return(DB_NO_SAVEPOINT);
266
if (trx->conc_state == TRX_NOT_STARTED) {
267
ut_print_timestamp(stderr);
268
fputs(" InnoDB: Error: transaction has a savepoint ", stderr);
269
ut_print_name(stderr, trx, FALSE, savep->name);
270
fputs(" though it is not started\n", stderr);
274
/* We can now free all savepoints strictly later than this one */
276
trx_roll_savepoints_free(trx, savep);
278
*mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos;
280
trx->op_info = "rollback to a savepoint";
282
err = trx_general_rollback_for_mysql(trx, &savep->savept);
284
/* Store the current undo_no of the transaction so that we know where
285
to roll back if we have to roll back the next SQL statement: */
287
trx_mark_sql_stat_end(trx);
294
/*******************************************************************//**
295
Creates a named savepoint. If the transaction is not yet started, starts it.
296
If there is already a savepoint of the same name, this call erases that old
297
savepoint and replaces it with a new. Savepoints are deleted in a transaction
299
@return always DB_SUCCESS */
302
trx_savepoint_for_mysql(
303
/*====================*/
304
trx_t* trx, /*!< in: transaction handle */
305
const char* savepoint_name, /*!< in: savepoint name */
306
ib_int64_t binlog_cache_pos) /*!< in: MySQL binlog cache
307
position corresponding to this
308
connection at the time of the
311
trx_named_savept_t* savep;
314
ut_a(savepoint_name);
316
trx_start_if_not_started(trx);
318
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
320
while (savep != NULL) {
321
if (0 == ut_strcmp(savep->name, savepoint_name)) {
325
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
329
/* There is a savepoint with the same name: free that */
331
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
333
mem_free(savep->name);
337
/* Create a new savepoint and add it as the last in the list */
339
savep = static_cast<trx_named_savept_t *>(mem_alloc(sizeof(trx_named_savept_t)));
341
savep->name = mem_strdup(savepoint_name);
343
savep->savept = trx_savept_take(trx);
345
savep->mysql_binlog_cache_pos = binlog_cache_pos;
347
UT_LIST_ADD_LAST(trx_savepoints, trx->trx_savepoints, savep);
352
/*******************************************************************//**
353
Releases only the named savepoint. Savepoints which were set after this
354
savepoint are left as is.
355
@return if no savepoint of the name found then DB_NO_SAVEPOINT,
356
otherwise DB_SUCCESS */
359
trx_release_savepoint_for_mysql(
360
/*============================*/
361
trx_t* trx, /*!< in: transaction handle */
362
const char* savepoint_name) /*!< in: savepoint name */
364
trx_named_savept_t* savep;
366
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
368
/* Search for the savepoint by name and free if found. */
369
while (savep != NULL) {
370
if (0 == ut_strcmp(savep->name, savepoint_name)) {
371
trx_roll_savepoint_free(trx, savep);
374
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
377
return(DB_NO_SAVEPOINT);
380
/*******************************************************************//**
381
Determines if this transaction is rolling back an incomplete transaction
383
@return TRUE if trx is an incomplete transaction that is being rolled
384
back in crash recovery */
389
const trx_t* trx) /*!< in: transaction */
391
return(trx == trx_roll_crash_recv_trx);
394
/*******************************************************************//**
395
Returns a transaction savepoint taken at this point in time.
401
trx_t* trx) /*!< in: transaction */
405
savept.least_undo_no = trx->undo_no;
410
/*******************************************************************//**
411
Roll back an active transaction. */
416
trx_t* trx) /*!< in/out: transaction */
421
roll_node_t* roll_node;
423
ib_int64_t rows_to_undo;
424
const char* unit = "";
425
ibool dictionary_locked = FALSE;
427
heap = mem_heap_create(512);
429
fork = que_fork_create(NULL, NULL, QUE_FORK_RECOVERY, heap);
432
thr = que_thr_create(fork, heap);
434
roll_node = roll_node_create(heap);
436
thr->child = roll_node;
437
roll_node->common.parent = thr;
439
mutex_enter(&kernel_mutex);
443
ut_a(thr == que_fork_start_command(fork));
445
trx_roll_crash_recv_trx = trx;
446
trx_roll_max_undo_no = trx->undo_no;
447
trx_roll_progress_printed_pct = 0;
448
rows_to_undo = trx_roll_max_undo_no;
450
if (rows_to_undo > 1000000000) {
451
rows_to_undo = rows_to_undo / 1000000;
455
ut_print_timestamp(stderr);
457
" InnoDB: Rolling back trx with id " TRX_ID_FMT ", %lu%s"
460
(ulong) rows_to_undo, unit);
461
mutex_exit(&kernel_mutex);
463
trx->mysql_thread_id = os_thread_get_curr_id();
465
trx->mysql_process_no = os_proc_get_number();
467
if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
468
row_mysql_lock_data_dictionary(trx);
469
dictionary_locked = TRUE;
472
que_run_threads(thr);
474
mutex_enter(&kernel_mutex);
476
while (trx->que_state != TRX_QUE_RUNNING) {
478
mutex_exit(&kernel_mutex);
481
"InnoDB: Waiting for rollback of trx id "
482
TRX_ID_FMT " to end\n",
484
os_thread_sleep(100000);
486
mutex_enter(&kernel_mutex);
489
mutex_exit(&kernel_mutex);
491
if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE
492
&& trx->table_id != 0) {
494
/* If the transaction was for a dictionary operation, we
495
drop the relevant table, if it still exists */
498
"InnoDB: Dropping table with id %llu"
499
" in recovery if it exists\n",
500
(ullint) trx->table_id);
502
table = dict_table_get_on_id_low(trx->table_id);
507
fputs("InnoDB: Table found: dropping table ", stderr);
508
ut_print_name(stderr, trx, TRUE, table->name);
509
fputs(" in recovery\n", stderr);
511
err = row_drop_table_for_mysql(table->name, trx, TRUE);
512
trx_commit_for_mysql(trx);
514
ut_a(err == (int) DB_SUCCESS);
518
if (dictionary_locked) {
519
row_mysql_unlock_data_dictionary(trx);
522
fprintf(stderr, "\nInnoDB: Rolling back of trx id " TRX_ID_FMT
527
trx_roll_crash_recv_trx = NULL;
530
/*******************************************************************//**
531
Rollback or clean up any incomplete transactions which were
532
encountered in crash recovery. If the transaction already was
533
committed, then we clean up a possible insert undo log. If the
534
transaction was not yet committed, then we roll it back. */
537
trx_rollback_or_clean_recovered(
538
/*============================*/
539
ibool all) /*!< in: FALSE=roll back dictionary transactions;
540
TRUE=roll back all non-PREPARED transactions */
544
mutex_enter(&kernel_mutex);
546
if (!UT_LIST_GET_FIRST(trx_sys->trx_list)) {
552
"InnoDB: Starting in background the rollback"
553
" of uncommitted transactions\n");
556
mutex_exit(&kernel_mutex);
559
mutex_enter(&kernel_mutex);
561
for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
562
trx = UT_LIST_GET_NEXT(trx_list, trx)) {
563
if (!trx->is_recovered) {
567
switch (trx->conc_state) {
568
case TRX_NOT_STARTED:
572
case TRX_COMMITTED_IN_MEMORY:
573
mutex_exit(&kernel_mutex);
575
"InnoDB: Cleaning up trx with id "
578
trx_cleanup_at_db_startup(trx);
582
if (all || trx_get_dict_operation(trx)
583
!= TRX_DICT_OP_NONE) {
584
mutex_exit(&kernel_mutex);
585
trx_rollback_active(trx);
592
ut_print_timestamp(stderr);
594
" InnoDB: Rollback of non-prepared"
595
" transactions completed\n");
599
mutex_exit(&kernel_mutex);
602
/*******************************************************************//**
603
Rollback or clean up any incomplete transactions which were
604
encountered in crash recovery. If the transaction already was
605
committed, then we clean up a possible insert undo log. If the
606
transaction was not yet committed, then we roll it back.
607
Note: this is done in a background thread.
608
@return a dummy parameter */
611
trx_rollback_or_clean_all_recovered(
612
/*================================*/
614
/*!< in: a dummy parameter required by
617
#ifdef UNIV_PFS_THREAD
618
pfs_register_thread(trx_rollback_clean_thread_key);
619
#endif /* UNIV_PFS_THREAD */
621
trx_rollback_or_clean_recovered(TRUE);
623
/* We count the number of threads in os_thread_exit(). A created
624
thread should always use that to exit and not use return() to exit. */
626
os_thread_exit(NULL);
628
OS_THREAD_DUMMY_RETURN;
631
/*******************************************************************//**
632
Creates an undo number array.
633
@return own: undo number array */
636
trx_undo_arr_create(void)
637
/*=====================*/
643
heap = mem_heap_create(1024);
645
arr = static_cast<trx_undo_arr_t *>(mem_heap_alloc(heap, sizeof(trx_undo_arr_t)));
647
arr->infos = static_cast<trx_undo_inf_t *>(mem_heap_alloc(heap, sizeof(trx_undo_inf_t)
648
* UNIV_MAX_PARALLELISM));
649
arr->n_cells = UNIV_MAX_PARALLELISM;
654
for (i = 0; i < UNIV_MAX_PARALLELISM; i++) {
656
(trx_undo_arr_get_nth_info(arr, i))->in_use = FALSE;
662
/*******************************************************************//**
663
Frees an undo number array. */
668
trx_undo_arr_t* arr) /*!< in: undo number array */
670
ut_ad(arr->n_used == 0);
672
mem_heap_free(arr->heap);
675
/*******************************************************************//**
676
Stores info of an undo log record to the array if it is not stored yet.
677
@return FALSE if the record already existed in the array */
680
trx_undo_arr_store_info(
681
/*====================*/
682
trx_t* trx, /*!< in: transaction */
683
undo_no_t undo_no)/*!< in: undo number */
685
trx_undo_inf_t* cell;
686
trx_undo_inf_t* stored_here;
693
arr = trx->undo_no_arr;
694
n_used = arr->n_used;
698
cell = trx_undo_arr_get_nth_info(arr, i);
702
/* Not in use, we may store here */
703
cell->undo_no = undo_no;
713
if (cell->undo_no == undo_no) {
716
stored_here->in_use = FALSE;
717
ut_ad(arr->n_used > 0);
721
ut_ad(arr->n_used == n_used);
727
if (n == n_used && stored_here) {
729
ut_ad(arr->n_used == 1 + n_used);
736
/*******************************************************************//**
737
Removes an undo number from the array. */
740
trx_undo_arr_remove_info(
741
/*=====================*/
742
trx_undo_arr_t* arr, /*!< in: undo number array */
743
undo_no_t undo_no)/*!< in: undo number */
745
trx_undo_inf_t* cell;
749
cell = trx_undo_arr_get_nth_info(arr, i);
752
&& cell->undo_no == undo_no) {
754
cell->in_use = FALSE;
756
ut_ad(arr->n_used > 0);
765
/*******************************************************************//**
766
Gets the biggest undo number in an array.
767
@return biggest value, 0 if the array is empty */
770
trx_undo_arr_get_biggest(
771
/*=====================*/
772
trx_undo_arr_t* arr) /*!< in: undo number array */
774
trx_undo_inf_t* cell;
781
n_used = arr->n_used;
785
cell = trx_undo_arr_get_nth_info(arr, i);
789
if (cell->undo_no > biggest) {
791
biggest = cell->undo_no;
801
/***********************************************************************//**
802
Tries truncate the undo logs. */
805
trx_roll_try_truncate(
806
/*==================*/
807
trx_t* trx) /*!< in/out: transaction */
813
ut_ad(mutex_own(&(trx->undo_mutex)));
814
ut_ad(mutex_own(&((trx->rseg)->mutex)));
816
trx->pages_undone = 0;
818
arr = trx->undo_no_arr;
820
limit = trx->undo_no;
822
if (arr->n_used > 0) {
823
biggest = trx_undo_arr_get_biggest(arr);
825
if (biggest >= limit) {
831
if (trx->insert_undo) {
832
trx_undo_truncate_end(trx, trx->insert_undo, limit);
835
if (trx->update_undo) {
836
trx_undo_truncate_end(trx, trx->update_undo, limit);
840
/***********************************************************************//**
841
Pops the topmost undo log record in a single undo log and updates the info
842
about the topmost record in the undo log memory struct.
843
@return undo log record, the page s-latched */
846
trx_roll_pop_top_rec(
847
/*=================*/
848
trx_t* trx, /*!< in: transaction */
849
trx_undo_t* undo, /*!< in: undo log */
850
mtr_t* mtr) /*!< in: mtr */
854
trx_undo_rec_t* prev_rec;
855
page_t* prev_rec_page;
857
ut_ad(mutex_own(&(trx->undo_mutex)));
859
undo_page = trx_undo_page_get_s_latched(undo->space, undo->zip_size,
860
undo->top_page_no, mtr);
861
offset = undo->top_offset;
863
/* fprintf(stderr, "Thread %lu undoing trx " TRX_ID_FMT
864
" undo record " TRX_ID_FMT "\n",
865
os_thread_get_curr_id(), trx->id, undo->top_undo_no); */
867
prev_rec = trx_undo_get_prev_rec(undo_page + offset,
868
undo->hdr_page_no, undo->hdr_offset,
870
if (prev_rec == NULL) {
874
prev_rec_page = page_align(prev_rec);
876
if (prev_rec_page != undo_page) {
881
undo->top_page_no = page_get_page_no(prev_rec_page);
882
undo->top_offset = prev_rec - prev_rec_page;
883
undo->top_undo_no = trx_undo_rec_get_undo_no(prev_rec);
886
return(undo_page + offset);
889
/********************************************************************//**
890
Pops the topmost record when the two undo logs of a transaction are seen
891
as a single stack of records ordered by their undo numbers. Inserts the
892
undo number of the popped undo record to the array of currently processed
893
undo numbers in the transaction. When the query thread finishes processing
894
of this undo record, it must be released with trx_undo_rec_release.
895
@return undo log record copied to heap, NULL if none left, or if the
896
undo number of the top record would be less than the limit */
899
trx_roll_pop_top_rec_of_trx(
900
/*========================*/
901
trx_t* trx, /*!< in: transaction */
902
undo_no_t limit, /*!< in: least undo number we need */
903
roll_ptr_t* roll_ptr,/*!< out: roll pointer to undo record */
904
mem_heap_t* heap) /*!< in: memory heap where copied */
907
trx_undo_t* ins_undo;
908
trx_undo_t* upd_undo;
909
trx_undo_rec_t* undo_rec;
910
trx_undo_rec_t* undo_rec_copy;
919
mutex_enter(&(trx->undo_mutex));
921
if (trx->pages_undone >= TRX_ROLL_TRUNC_THRESHOLD) {
922
mutex_enter(&(rseg->mutex));
924
trx_roll_try_truncate(trx);
926
mutex_exit(&(rseg->mutex));
929
ins_undo = trx->insert_undo;
930
upd_undo = trx->update_undo;
932
if (!ins_undo || ins_undo->empty) {
934
} else if (!upd_undo || upd_undo->empty) {
936
} else if (upd_undo->top_undo_no > ins_undo->top_undo_no) {
942
if (!undo || undo->empty
943
|| limit > undo->top_undo_no) {
945
if ((trx->undo_no_arr)->n_used == 0) {
946
/* Rollback is ending */
948
mutex_enter(&(rseg->mutex));
950
trx_roll_try_truncate(trx);
952
mutex_exit(&(rseg->mutex));
955
mutex_exit(&(trx->undo_mutex));
960
if (undo == ins_undo) {
966
*roll_ptr = trx_undo_build_roll_ptr(is_insert, (undo->rseg)->id,
971
undo_rec = trx_roll_pop_top_rec(trx, undo, &mtr);
973
undo_no = trx_undo_rec_get_undo_no(undo_rec);
975
ut_ad(undo_no + 1 == trx->undo_no);
977
/* We print rollback progress info if we are in a crash recovery
978
and the transaction has at least 1000 row operations to undo. */
980
if (trx == trx_roll_crash_recv_trx && trx_roll_max_undo_no > 1000) {
982
progress_pct = 100 - (ulint)
983
((undo_no * 100) / trx_roll_max_undo_no);
984
if (progress_pct != trx_roll_progress_printed_pct) {
985
if (trx_roll_progress_printed_pct == 0) {
987
"\nInnoDB: Progress in percents:"
988
" %lu", (ulong) progress_pct);
991
" %lu", (ulong) progress_pct);
994
trx_roll_progress_printed_pct = progress_pct;
998
trx->undo_no = undo_no;
1000
if (!trx_undo_arr_store_info(trx, undo_no)) {
1001
/* A query thread is already processing this undo log record */
1003
mutex_exit(&(trx->undo_mutex));
1010
undo_rec_copy = trx_undo_rec_copy(undo_rec, heap);
1012
mutex_exit(&(trx->undo_mutex));
1016
return(undo_rec_copy);
1019
/********************************************************************//**
1020
Reserves an undo log record for a query thread to undo. This should be
1021
called if the query thread gets the undo log record not using the pop
1023
@return TRUE if succeeded */
1026
trx_undo_rec_reserve(
1027
/*=================*/
1028
trx_t* trx, /*!< in/out: transaction */
1029
undo_no_t undo_no)/*!< in: undo number of the record */
1033
mutex_enter(&(trx->undo_mutex));
1035
ret = trx_undo_arr_store_info(trx, undo_no);
1037
mutex_exit(&(trx->undo_mutex));
1042
/*******************************************************************//**
1043
Releases a reserved undo record. */
1046
trx_undo_rec_release(
1047
/*=================*/
1048
trx_t* trx, /*!< in/out: transaction */
1049
undo_no_t undo_no)/*!< in: undo number */
1051
trx_undo_arr_t* arr;
1053
mutex_enter(&(trx->undo_mutex));
1055
arr = trx->undo_no_arr;
1057
trx_undo_arr_remove_info(arr, undo_no);
1059
mutex_exit(&(trx->undo_mutex));
1062
/*********************************************************************//**
1063
Starts a rollback operation. */
1068
trx_t* trx, /*!< in: transaction */
1069
trx_sig_t* sig, /*!< in: signal starting the rollback */
1070
que_thr_t** next_thr)/*!< in/out: next query thread to run;
1071
if the value which is passed in is
1072
a pointer to a NULL pointer, then the
1073
calling function can start running
1074
a new query thread; if the passed value is
1075
NULL, the parameter is ignored */
1079
/* que_thr_t* thr2; */
1081
ut_ad(mutex_own(&kernel_mutex));
1082
ut_ad((trx->undo_no_arr == NULL) || ((trx->undo_no_arr)->n_used == 0));
1084
/* Initialize the rollback field in the transaction */
1086
switch (sig->type) {
1087
case TRX_SIG_TOTAL_ROLLBACK:
1088
trx->roll_limit = 0;
1090
case TRX_SIG_ROLLBACK_TO_SAVEPT:
1091
trx->roll_limit = (sig->savept).least_undo_no;
1093
case TRX_SIG_ERROR_OCCURRED:
1094
trx->roll_limit = trx->last_sql_stat_start.least_undo_no;
1100
ut_a(trx->roll_limit <= trx->undo_no);
1102
trx->pages_undone = 0;
1104
if (trx->undo_no_arr == NULL) {
1105
trx->undo_no_arr = trx_undo_arr_create();
1108
/* Build a 'query' graph which will perform the undo operations */
1110
roll_graph = trx_roll_graph_build(trx);
1112
trx->graph = roll_graph;
1113
trx->que_state = TRX_QUE_ROLLING_BACK;
1115
thr = que_fork_start_command(roll_graph);
1119
/* thr2 = que_fork_start_command(roll_graph);
1123
if (next_thr && (*next_thr == NULL)) {
1125
/* srv_que_task_enqueue_low(thr2); */
1127
srv_que_task_enqueue_low(thr);
1128
/* srv_que_task_enqueue_low(thr2); */
1132
/****************************************************************//**
1133
Builds an undo 'query' graph for a transaction. The actual rollback is
1134
performed by executing this query graph like a query subprocedure call.
1135
The reply about the completion of the rollback will be sent by this
1137
@return own: the query graph */
1140
trx_roll_graph_build(
1141
/*=================*/
1142
trx_t* trx) /*!< in: trx handle */
1147
/* que_thr_t* thr2; */
1149
ut_ad(mutex_own(&kernel_mutex));
1151
heap = mem_heap_create(512);
1152
fork = que_fork_create(NULL, NULL, QUE_FORK_ROLLBACK, heap);
1155
thr = que_thr_create(fork, heap);
1156
/* thr2 = que_thr_create(fork, heap); */
1158
thr->child = row_undo_node_create(trx, thr, heap);
1159
/* thr2->child = row_undo_node_create(trx, thr2, heap); */
1164
/*********************************************************************//**
1165
Finishes error processing after the necessary partial rollback has been
1169
trx_finish_error_processing(
1170
/*========================*/
1171
trx_t* trx) /*!< in: transaction */
1174
trx_sig_t* next_sig;
1176
ut_ad(mutex_own(&kernel_mutex));
1178
sig = UT_LIST_GET_FIRST(trx->signals);
1180
while (sig != NULL) {
1181
next_sig = UT_LIST_GET_NEXT(signals, sig);
1183
if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1185
trx_sig_remove(trx, sig);
1191
trx->que_state = TRX_QUE_RUNNING;
1194
/*********************************************************************//**
1195
Finishes a partial rollback operation. */
1198
trx_finish_partial_rollback_off_kernel(
1199
/*===================================*/
1200
trx_t* trx, /*!< in: transaction */
1201
que_thr_t** next_thr)/*!< in/out: next query thread to run;
1202
if the value which is passed in is a pointer
1203
to a NULL pointer, then the calling function
1204
can start running a new query thread; if this
1205
parameter is NULL, it is ignored */
1209
ut_ad(mutex_own(&kernel_mutex));
1211
sig = UT_LIST_GET_FIRST(trx->signals);
1213
/* Remove the signal from the signal queue and send reply message
1216
trx_sig_reply(sig, next_thr);
1217
trx_sig_remove(trx, sig);
1219
trx->que_state = TRX_QUE_RUNNING;
1222
/****************************************************************//**
1223
Finishes a transaction rollback. */
1226
trx_finish_rollback_off_kernel(
1227
/*===========================*/
1228
que_t* graph, /*!< in: undo graph which can now be freed */
1229
trx_t* trx, /*!< in: transaction */
1230
que_thr_t** next_thr)/*!< in/out: next query thread to run;
1231
if the value which is passed in is
1232
a pointer to a NULL pointer, then the
1233
calling function can start running
1234
a new query thread; if this parameter is
1235
NULL, it is ignored */
1238
trx_sig_t* next_sig;
1240
ut_ad(mutex_own(&kernel_mutex));
1242
ut_a(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0);
1244
/* Free the memory reserved by the undo graph */
1245
que_graph_free(graph);
1247
sig = UT_LIST_GET_FIRST(trx->signals);
1249
if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) {
1251
trx_finish_partial_rollback_off_kernel(trx, next_thr);
1255
} else if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1257
trx_finish_error_processing(trx);
1263
if (lock_print_waits) {
1264
fprintf(stderr, "Trx " TRX_ID_FMT " rollback finished\n",
1267
#endif /* UNIV_DEBUG */
1269
trx_commit_off_kernel(trx);
1271
/* Remove all TRX_SIG_TOTAL_ROLLBACK signals from the signal queue and
1272
send reply messages to them */
1274
trx->que_state = TRX_QUE_RUNNING;
1276
while (sig != NULL) {
1277
next_sig = UT_LIST_GET_NEXT(signals, sig);
1279
if (sig->type == TRX_SIG_TOTAL_ROLLBACK) {
1281
trx_sig_reply(sig, next_thr);
1283
trx_sig_remove(trx, sig);
1290
/*********************************************************************//**
1291
Creates a rollback command node struct.
1292
@return own: rollback node struct */
1297
mem_heap_t* heap) /*!< in: mem heap where created */
1302
node = static_cast<roll_node_t *>(mem_heap_alloc(heap, sizeof(roll_node_t)));
1303
node->common.type = QUE_NODE_ROLLBACK;
1304
node->state = ROLL_NODE_SEND;
1306
node->partial = FALSE;
1311
/***********************************************************//**
1312
Performs an execution step for a rollback command node in a query graph.
1313
@return query thread to run next, or NULL */
1318
que_thr_t* thr) /*!< in: query thread */
1322
trx_savept_t* savept;
1324
node = static_cast<roll_node_t *>(thr->run_node);
1326
ut_ad(que_node_get_type(node) == QUE_NODE_ROLLBACK);
1328
if (thr->prev_node == que_node_get_parent(node)) {
1329
node->state = ROLL_NODE_SEND;
1332
if (node->state == ROLL_NODE_SEND) {
1333
mutex_enter(&kernel_mutex);
1335
node->state = ROLL_NODE_WAIT;
1337
if (node->partial) {
1338
sig_no = TRX_SIG_ROLLBACK_TO_SAVEPT;
1339
savept = &(node->savept);
1341
sig_no = TRX_SIG_TOTAL_ROLLBACK;
1345
/* Send a rollback signal to the transaction */
1347
trx_sig_send(thr_get_trx(thr), sig_no, TRX_SIG_SELF, thr,
1350
thr->state = QUE_THR_SIG_REPLY_WAIT;
1352
mutex_exit(&kernel_mutex);
1357
ut_ad(node->state == ROLL_NODE_WAIT);
1359
thr->run_node = que_node_get_parent(node);