MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
trx0roll.cc
Go to the documentation of this file.
1 /*****************************************************************************
2 
3 Copyright (c) 1996, 2013, Oracle and/or its affiliates. All Rights Reserved.
4 
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.
8 
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.
12 
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.,
15 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16 
17 *****************************************************************************/
18 
19 /**************************************************/
26 #include "trx0roll.h"
27 
28 #ifdef UNIV_NONINL
29 #include "trx0roll.ic"
30 #endif
31 
32 #include "fsp0fsp.h"
33 #include "mach0data.h"
34 #include "trx0rseg.h"
35 #include "trx0trx.h"
36 #include "trx0undo.h"
37 #include "trx0rec.h"
38 #include "que0que.h"
39 #include "usr0sess.h"
40 #include "srv0start.h"
41 #include "read0read.h"
42 #include "row0undo.h"
43 #include "row0mysql.h"
44 #include "lock0lock.h"
45 #include "pars0pars.h"
46 #include "srv0mon.h"
47 #include "trx0sys.h"
48 
51 #define TRX_ROLL_TRUNC_THRESHOLD 1
52 
54 static const trx_t* trx_roll_crash_recv_trx = NULL;
55 
58 static undo_no_t trx_roll_max_undo_no;
59 
61 static ulint trx_roll_progress_printed_pct;
62 
63 /****************************************************************/
65 static
66 void
67 trx_rollback_finish(
68 /*================*/
69  trx_t* trx);
71 /*******************************************************************/
73 static
74 void
75 trx_rollback_to_savepoint_low(
76 /*==========================*/
77  trx_t* trx,
78  trx_savept_t* savept)
81 {
82  que_thr_t* thr;
84  roll_node_t* roll_node;
85 
86  heap = mem_heap_create(512);
87 
88  roll_node = roll_node_create(heap);
89 
90  if (savept != NULL) {
91  roll_node->partial = TRUE;
92  roll_node->savept = *savept;
93  assert_trx_in_list(trx);
94  } else {
96  }
97 
98  trx->error_state = DB_SUCCESS;
99 
100  if (trx->insert_undo || trx->update_undo) {
101  thr = pars_complete_graph_for_exec(roll_node, trx, heap);
102 
104  static_cast<que_fork_t*>(que_node_get_parent(thr))));
105 
106  que_run_threads(thr);
107 
108  ut_a(roll_node->undo_thr != NULL);
109  que_run_threads(roll_node->undo_thr);
110 
111  /* Free the memory reserved by the undo graph. */
112  que_graph_free(static_cast<que_t*>(
113  roll_node->undo_thr->common.parent));
114  }
115 
116  if (savept == NULL) {
117  trx_rollback_finish(trx);
118  MONITOR_INC(MONITOR_TRX_ROLLBACK);
119  } else {
121  MONITOR_INC(MONITOR_TRX_ROLLBACK_SAVEPOINT);
122  }
123 
124  ut_a(trx->error_state == DB_SUCCESS);
126 
127  mem_heap_free(heap);
128 
129  MONITOR_DEC(MONITOR_TRX_ACTIVE);
130 }
131 
132 /*******************************************************************/
135 UNIV_INTERN
136 dberr_t
138 /*======================*/
139  trx_t* trx,
140  trx_savept_t* savept)
143 {
144  ut_ad(!trx_mutex_own(trx));
145 
146  /* Tell Innobase server that there might be work for
147  utility threads: */
148 
150 
151  trx_start_if_not_started_xa(trx);
152 
153  trx_rollback_to_savepoint_low(trx, savept);
154 
155  /* Tell Innobase server that there might be work for
156  utility threads: */
157 
159 
160  return(trx->error_state);
161 }
162 
163 /*******************************************************************/
166 static
167 dberr_t
168 trx_rollback_for_mysql_low(
169 /*=======================*/
170  trx_t* trx)
171 {
173 
174  trx->op_info = "rollback";
175 
176  /* If we are doing the XA recovery of prepared transactions,
177  then the transaction object does not have an InnoDB session
178  object, and we set a dummy session that we use for all MySQL
179  transactions. */
180 
181  trx_rollback_to_savepoint_low(trx, NULL);
182 
183  trx->op_info = "";
184 
185  ut_a(trx->error_state == DB_SUCCESS);
186 
188 
189  return(trx->error_state);
190 }
191 
192 /*******************************************************************/
195 UNIV_INTERN
196 dberr_t
198 /*===================*/
199  trx_t* trx)
200 {
201  /* We are reading trx->state without holding trx_sys->mutex
202  here, because the rollback should be invoked for a running
203  active MySQL transaction (or recovered prepared transaction)
204  that is associated with the current thread. */
205 
206  switch (trx->state) {
207  case TRX_STATE_NOT_STARTED:
208  ut_ad(trx->in_mysql_trx_list);
209  return(DB_SUCCESS);
210 
211  case TRX_STATE_ACTIVE:
212  ut_ad(trx->in_mysql_trx_list);
214  return(trx_rollback_for_mysql_low(trx));
215 
216  case TRX_STATE_PREPARED:
218  return(trx_rollback_for_mysql_low(trx));
219 
220  case TRX_STATE_COMMITTED_IN_MEMORY:
221  assert_trx_in_list(trx);
222  break;
223  }
224 
225  ut_error;
226  return(DB_CORRUPTION);
227 }
228 
229 /*******************************************************************/
232 UNIV_INTERN
233 dberr_t
235 /*=================================*/
236  trx_t* trx)
237 {
238  dberr_t err;
239 
240  /* We are reading trx->state without holding trx_sys->mutex
241  here, because the statement rollback should be invoked for a
242  running active MySQL transaction that is associated with the
243  current thread. */
244  ut_ad(trx->in_mysql_trx_list);
245 
246  switch (trx->state) {
247  case TRX_STATE_NOT_STARTED:
248  return(DB_SUCCESS);
249  case TRX_STATE_ACTIVE:
251 
252  trx->op_info = "rollback of SQL statement";
253 
255  trx, &trx->last_sql_stat_start);
256 
257  if (trx->fts_trx) {
259  }
260 
261  /* The following call should not be needed,
262  but we play it safe: */
264 
265  trx->op_info = "";
266 
267  return(err);
268  case TRX_STATE_PREPARED:
269  case TRX_STATE_COMMITTED_IN_MEMORY:
270  /* The statement rollback is only allowed on an ACTIVE
271  transaction, not a PREPARED or COMMITTED one. */
272  break;
273  }
274 
275  ut_error;
276  return(DB_CORRUPTION);
277 }
278 
279 /*******************************************************************/
282 static
284 trx_savepoint_find(
285 /*===============*/
286  trx_t* trx,
287  const char* name)
288 {
290 
291  for (savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
292  savep != NULL;
293  savep = UT_LIST_GET_NEXT(trx_savepoints, savep)) {
294 
295  if (0 == ut_strcmp(savep->name, name)) {
296  return(savep);
297  }
298  }
299 
300  return(NULL);
301 }
302 
303 /*******************************************************************/
305 static
306 void
307 trx_roll_savepoint_free(
308 /*=====================*/
309  trx_t* trx,
310  trx_named_savept_t* savep)
311 {
312  UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
313  mem_free(savep->name);
314  mem_free(savep);
315 }
316 
317 /*******************************************************************/
319 UNIV_INTERN
320 void
322 /*=====================*/
323  trx_t* trx,
324  trx_named_savept_t* savep)
326 {
327  while (savep != NULL) {
328  trx_named_savept_t* next_savep;
329 
330  next_savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
331 
332  trx_roll_savepoint_free(trx, savep);
333 
334  savep = next_savep;
335  }
336 }
337 
338 /*******************************************************************/
347 static __attribute__((nonnull, warn_unused_result))
348 dberr_t
349 trx_rollback_to_savepoint_for_mysql_low(
350 /*====================================*/
351  trx_t* trx,
353  ib_int64_t* mysql_binlog_cache_pos)
360 {
361  dberr_t err;
362 
363  ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
364  ut_ad(trx->in_mysql_trx_list);
365 
366  /* Free all savepoints strictly later than savep. */
367 
369  trx, UT_LIST_GET_NEXT(trx_savepoints, savep));
370 
371  *mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos;
372 
373  trx->op_info = "rollback to a savepoint";
374 
375  err = trx_rollback_to_savepoint(trx, &savep->savept);
376 
377  /* Store the current undo_no of the transaction so that
378  we know where to roll back if we have to roll back the
379  next SQL statement: */
380 
382 
383  trx->op_info = "";
384 
385  return(err);
386 }
387 
388 /*******************************************************************/
397 UNIV_INTERN
398 dberr_t
400 /*================================*/
401  trx_t* trx,
402  const char* savepoint_name,
403  ib_int64_t* mysql_binlog_cache_pos)
409 {
411 
412  /* We are reading trx->state without holding trx_sys->mutex
413  here, because the savepoint rollback should be invoked for a
414  running active MySQL transaction that is associated with the
415  current thread. */
416  ut_ad(trx->in_mysql_trx_list);
417 
418  savep = trx_savepoint_find(trx, savepoint_name);
419 
420  if (savep == NULL) {
421  return(DB_NO_SAVEPOINT);
422  }
423 
424  switch (trx->state) {
425  case TRX_STATE_NOT_STARTED:
426  ut_print_timestamp(stderr);
427  fputs(" InnoDB: Error: transaction has a savepoint ", stderr);
428  ut_print_name(stderr, trx, FALSE, savep->name);
429  fputs(" though it is not started\n", stderr);
430  return(DB_ERROR);
431  case TRX_STATE_ACTIVE:
432  return(trx_rollback_to_savepoint_for_mysql_low(
433  trx, savep, mysql_binlog_cache_pos));
434  case TRX_STATE_PREPARED:
435  case TRX_STATE_COMMITTED_IN_MEMORY:
436  /* The savepoint rollback is only allowed on an ACTIVE
437  transaction, not a PREPARED or COMMITTED one. */
438  break;
439  }
440 
441  ut_error;
442  return(DB_CORRUPTION);
443 }
444 
445 /*******************************************************************/
451 UNIV_INTERN
452 dberr_t
454 /*====================*/
455  trx_t* trx,
456  const char* savepoint_name,
457  ib_int64_t binlog_cache_pos)
461 {
463 
464  trx_start_if_not_started_xa(trx);
465 
466  savep = trx_savepoint_find(trx, savepoint_name);
467 
468  if (savep) {
469  /* There is a savepoint with the same name: free that */
470 
471  UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
472 
473  mem_free(savep->name);
474  mem_free(savep);
475  }
476 
477  /* Create a new savepoint and add it as the last in the list */
478 
479  savep = static_cast<trx_named_savept_t*>(mem_alloc(sizeof(*savep)));
480 
481  savep->name = mem_strdup(savepoint_name);
482 
483  savep->savept = trx_savept_take(trx);
484 
485  savep->mysql_binlog_cache_pos = binlog_cache_pos;
486 
487  UT_LIST_ADD_LAST(trx_savepoints, trx->trx_savepoints, savep);
488 
489  return(DB_SUCCESS);
490 }
491 
492 /*******************************************************************/
497 UNIV_INTERN
498 dberr_t
500 /*============================*/
501  trx_t* trx,
502  const char* savepoint_name)
503 {
505 
506  ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
507  ut_ad(trx->in_mysql_trx_list);
508 
509  savep = trx_savepoint_find(trx, savepoint_name);
510 
511  if (savep != NULL) {
512  trx_roll_savepoint_free(trx, savep);
513  }
514 
515  return(savep != NULL ? DB_SUCCESS : DB_NO_SAVEPOINT);
516 }
517 
518 /*******************************************************************/
523 UNIV_INTERN
524 ibool
526 /*========*/
527  const trx_t* trx)
528 {
529  return(trx == trx_roll_crash_recv_trx);
530 }
531 
532 /*******************************************************************/
535 UNIV_INTERN
538 /*============*/
539  trx_t* trx)
540 {
541  trx_savept_t savept;
542 
543  savept.least_undo_no = trx->undo_no;
544 
545  return(savept);
546 }
547 
548 /*******************************************************************/
550 static
551 void
552 trx_rollback_active(
553 /*================*/
554  trx_t* trx)
555 {
556  mem_heap_t* heap;
557  que_fork_t* fork;
558  que_thr_t* thr;
559  roll_node_t* roll_node;
561  ib_int64_t rows_to_undo;
562  const char* unit = "";
563  ibool dictionary_locked = FALSE;
564 
565  heap = mem_heap_create(512);
566 
567  fork = que_fork_create(NULL, NULL, QUE_FORK_RECOVERY, heap);
568  fork->trx = trx;
569 
570  thr = que_thr_create(fork, heap);
571 
572  roll_node = roll_node_create(heap);
573 
574  thr->child = roll_node;
575  roll_node->common.parent = thr;
576 
577  trx->graph = fork;
578 
579  ut_a(thr == que_fork_start_command(fork));
580 
581  mutex_enter(&trx_sys->mutex);
582 
583  trx_roll_crash_recv_trx = trx;
584 
585  trx_roll_max_undo_no = trx->undo_no;
586 
587  trx_roll_progress_printed_pct = 0;
588 
589  rows_to_undo = trx_roll_max_undo_no;
590 
591  mutex_exit(&trx_sys->mutex);
592 
593  if (rows_to_undo > 1000000000) {
594  rows_to_undo = rows_to_undo / 1000000;
595  unit = "M";
596  }
597 
598  ut_print_timestamp(stderr);
599  fprintf(stderr,
600  " InnoDB: Rolling back trx with id " TRX_ID_FMT ", %lu%s"
601  " rows to undo\n",
602  trx->id,
603  (ulong) rows_to_undo, unit);
604 
606  row_mysql_lock_data_dictionary(trx);
607  dictionary_locked = TRUE;
608  }
609 
610  que_run_threads(thr);
611  ut_a(roll_node->undo_thr != NULL);
612 
613  que_run_threads(roll_node->undo_thr);
614 
615  trx_rollback_finish(thr_get_trx(roll_node->undo_thr));
616 
617  /* Free the memory reserved by the undo graph */
618  que_graph_free(static_cast<que_t*>(
619  roll_node->undo_thr->common.parent));
620 
622 
624  && trx->table_id != 0) {
625 
626  /* If the transaction was for a dictionary operation,
627  we drop the relevant table only if it is not flagged
628  as DISCARDED. If it still exists. */
629 
630  table = dict_table_open_on_id(
631  trx->table_id, dictionary_locked,
633 
634  if (table && !dict_table_is_discarded(table)) {
635 
636  dberr_t err;
637 
638  /* Ensure that the table doesn't get evicted from the
639  cache, keeps things simple for drop. */
640 
641  if (table->can_be_evicted) {
643  }
644 
645  dict_table_close(table, dictionary_locked, FALSE);
646 
647  ib_logf(IB_LOG_LEVEL_WARN,
648  "Dropping table '%s', with id " UINT64PF " "
649  "in recovery",
650  table->name, trx->table_id);
651 
652  err = row_drop_table_for_mysql(table->name, trx, TRUE);
654 
655  ut_a(err == DB_SUCCESS);
656  }
657  }
658 
659  if (dictionary_locked) {
661  }
662 
663  ib_logf(IB_LOG_LEVEL_INFO,
664  "Rollback of trx with id " TRX_ID_FMT " completed", trx->id);
665 
666  mem_heap_free(heap);
667 
668  trx_roll_crash_recv_trx = NULL;
669 }
670 
671 /*******************************************************************/
677 static
678 ibool
679 trx_rollback_resurrected(
680 /*=====================*/
681  trx_t* trx,
682  ibool all)
684 {
685  ut_ad(mutex_own(&trx_sys->mutex));
686 
687  /* The trx->is_recovered flag and trx->state are set
688  atomically under the protection of the trx->mutex (and
689  lock_sys->mutex) in lock_trx_release_locks(). We do not want
690  to accidentally clean up a non-recovered transaction here. */
691 
692  trx_mutex_enter(trx);
693  bool is_recovered = trx->is_recovered;
694  trx_state_t state = trx->state;
695  trx_mutex_exit(trx);
696 
697  if (!is_recovered) {
698  return(FALSE);
699  }
700 
701  switch (state) {
702  case TRX_STATE_COMMITTED_IN_MEMORY:
703  mutex_exit(&trx_sys->mutex);
704  fprintf(stderr,
705  "InnoDB: Cleaning up trx with id " TRX_ID_FMT "\n",
706  trx->id);
709  return(TRUE);
710  case TRX_STATE_ACTIVE:
711  if (all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
712  mutex_exit(&trx_sys->mutex);
713  trx_rollback_active(trx);
715  return(TRUE);
716  }
717  return(FALSE);
718  case TRX_STATE_PREPARED:
719  return(FALSE);
720  case TRX_STATE_NOT_STARTED:
721  break;
722  }
723 
724  ut_error;
725  return(FALSE);
726 }
727 
728 /*******************************************************************/
733 UNIV_INTERN
734 void
736 /*============================*/
737  ibool all)
739 {
740  trx_t* trx;
741 
743 
744  if (trx_sys_get_n_rw_trx() == 0) {
745 
746  return;
747  }
748 
749  if (all) {
750  fprintf(stderr,
751  "InnoDB: Starting in background the rollback"
752  " of uncommitted transactions\n");
753  }
754 
755  /* Note: For XA recovered transactions, we rely on MySQL to
756  do rollback. They will be in TRX_STATE_PREPARED state. If the server
757  is shutdown and they are still lingering in trx_sys_t::trx_list
758  then the shutdown will hang. */
759 
760  /* Loop over the transaction list as long as there are
761  recovered transactions to clean up or recover. */
762 
763  do {
764  mutex_enter(&trx_sys->mutex);
765 
767  trx != NULL;
768  trx = UT_LIST_GET_NEXT(trx_list, trx)) {
769 
771 
772  /* If this function does a cleanup or rollback
773  then it will release the trx_sys->mutex, therefore
774  we need to reacquire it before retrying the loop. */
775 
776  if (trx_rollback_resurrected(trx, all)) {
777 
778  mutex_enter(&trx_sys->mutex);
779 
780  break;
781  }
782  }
783 
784  mutex_exit(&trx_sys->mutex);
785 
786  } while (trx != NULL);
787 
788  if (all) {
789  ut_print_timestamp(stderr);
790  fprintf(stderr,
791  " InnoDB: Rollback of non-prepared"
792  " transactions completed\n");
793  }
794 }
795 
796 /*******************************************************************/
803 extern "C" UNIV_INTERN
804 os_thread_ret_t
806 /*================================================*/
807  void* arg __attribute__((unused)))
810 {
812 
813 #ifdef UNIV_PFS_THREAD
814  pfs_register_thread(trx_rollback_clean_thread_key);
815 #endif /* UNIV_PFS_THREAD */
816 
818 
819  /* We count the number of threads in os_thread_exit(). A created
820  thread should always use that to exit and not use return() to exit. */
821 
822  os_thread_exit(NULL);
823 
824  OS_THREAD_DUMMY_RETURN;
825 }
826 
827 /*******************************************************************/
830 static
832 trx_undo_arr_create(
833 /*================*/
834  ulint n_cells)
835 {
836  trx_undo_arr_t* arr;
837  mem_heap_t* heap;
838  ulint sz = sizeof(*arr) + sizeof(*arr->infos) * n_cells;
839 
840  heap = mem_heap_create(sz);
841 
842  arr = static_cast<trx_undo_arr_t*>(mem_heap_zalloc(heap, sz));
843 
844  arr->n_cells = n_cells;
845 
846  arr->infos = (trx_undo_inf_t*) (arr + 1);
847 
848  arr->heap = heap;
849 
850  return(arr);
851 }
852 
853 /*******************************************************************/
855 UNIV_INTERN
856 void
858 /*==============*/
859  trx_undo_arr_t* arr)
860 {
861  mem_heap_free(arr->heap);
862 }
863 
864 /*******************************************************************/
867 static
868 ibool
869 trx_undo_arr_store_info(
870 /*====================*/
871  trx_t* trx,
872  undo_no_t undo_no)
873 {
874  ulint i;
875  trx_undo_arr_t* arr;
876  ulint n = 0;
877  ulint n_used;
878  trx_undo_inf_t* stored_here = NULL;
879 
880  arr = trx->undo_no_arr;
881  n_used = arr->n_used;
882 
883  for (i = 0; i < arr->n_cells; i++) {
884  trx_undo_inf_t* cell;
885 
886  cell = trx_undo_arr_get_nth_info(arr, i);
887 
888  if (!cell->in_use) {
889  if (!stored_here) {
890  /* Not in use, we may store here */
891  cell->undo_no = undo_no;
892  cell->in_use = TRUE;
893 
894  arr->n_used++;
895 
896  stored_here = cell;
897  }
898  } else {
899  n++;
900 
901  if (cell->undo_no == undo_no) {
902 
903  if (stored_here) {
904  stored_here->in_use = FALSE;
905  ut_ad(arr->n_used > 0);
906  arr->n_used--;
907  }
908 
909  ut_ad(arr->n_used == n_used);
910 
911  return(FALSE);
912  }
913  }
914 
915  if (n == n_used && stored_here) {
916 
917  ut_ad(arr->n_used == 1 + n_used);
918 
919  return(TRUE);
920  }
921  }
922 
923  ut_error;
924 
925  return(FALSE);
926 }
927 
928 /*******************************************************************/
930 static
931 void
932 trx_undo_arr_remove_info(
933 /*=====================*/
934  trx_undo_arr_t* arr,
935  undo_no_t undo_no)
936 {
937  ulint i;
938 
939  for (i = 0; i < arr->n_cells; i++) {
940 
941  trx_undo_inf_t* cell;
942 
943  cell = trx_undo_arr_get_nth_info(arr, i);
944 
945  if (cell->in_use && cell->undo_no == undo_no) {
946  cell->in_use = FALSE;
947  ut_ad(arr->n_used > 0);
948  --arr->n_used;
949  break;
950  }
951  }
952 }
953 
954 /*******************************************************************/
957 static
958 undo_no_t
959 trx_undo_arr_get_biggest(
960 /*=====================*/
961  const trx_undo_arr_t* arr)
962 {
963  ulint i;
964  undo_no_t biggest = 0;
965  ulint n_checked = 0;
966 
967  for (i = 0; i < arr->n_cells && n_checked < arr->n_used; ++i) {
968 
969  const trx_undo_inf_t* cell = &arr->infos[i];
970 
971  if (cell->in_use) {
972 
973  ++n_checked;
974 
975  if (cell->undo_no > biggest) {
976 
977  biggest = cell->undo_no;
978  }
979  }
980  }
981 
982  return(biggest);
983 }
984 
985 /***********************************************************************/
987 static
988 void
989 trx_roll_try_truncate(
990 /*==================*/
991  trx_t* trx)
992 {
994  const trx_undo_arr_t* arr;
995 
996  ut_ad(mutex_own(&(trx->undo_mutex)));
997  ut_ad(mutex_own(&((trx->rseg)->mutex)));
998 
999  trx->pages_undone = 0;
1000 
1001  arr = trx->undo_no_arr;
1002 
1003  limit = trx->undo_no;
1004 
1005  if (arr->n_used > 0) {
1006  undo_no_t biggest;
1007 
1008  biggest = trx_undo_arr_get_biggest(arr);
1009 
1010  if (biggest >= limit) {
1011 
1012  limit = biggest + 1;
1013  }
1014  }
1015 
1016  if (trx->insert_undo) {
1017  trx_undo_truncate_end(trx, trx->insert_undo, limit);
1018  }
1019 
1020  if (trx->update_undo) {
1021  trx_undo_truncate_end(trx, trx->update_undo, limit);
1022  }
1023 }
1024 
1025 /***********************************************************************/
1029 static
1031 trx_roll_pop_top_rec(
1032 /*=================*/
1033  trx_t* trx,
1034  trx_undo_t* undo,
1035  mtr_t* mtr)
1036 {
1037  page_t* undo_page;
1038  ulint offset;
1039  trx_undo_rec_t* prev_rec;
1040  page_t* prev_rec_page;
1041 
1042  ut_ad(mutex_own(&trx->undo_mutex));
1043 
1044  undo_page = trx_undo_page_get_s_latched(
1045  undo->space, undo->zip_size, undo->top_page_no, mtr);
1046 
1047  offset = undo->top_offset;
1048 
1049  /* fprintf(stderr, "Thread %lu undoing trx " TRX_ID_FMT
1050  " undo record " TRX_ID_FMT "\n",
1051  os_thread_get_curr_id(), trx->id, undo->top_undo_no); */
1052 
1053  prev_rec = trx_undo_get_prev_rec(
1054  undo_page + offset, undo->hdr_page_no, undo->hdr_offset,
1055  true, mtr);
1056 
1057  if (prev_rec == NULL) {
1058 
1059  undo->empty = TRUE;
1060  } else {
1061  prev_rec_page = page_align(prev_rec);
1062 
1063  if (prev_rec_page != undo_page) {
1064 
1065  trx->pages_undone++;
1066  }
1067 
1068  undo->top_page_no = page_get_page_no(prev_rec_page);
1069  undo->top_offset = prev_rec - prev_rec_page;
1070  undo->top_undo_no = trx_undo_rec_get_undo_no(prev_rec);
1071  }
1072 
1073  return(undo_page + offset);
1074 }
1075 
1076 /********************************************************************/
1084 UNIV_INTERN
1087 /*========================*/
1088  trx_t* trx,
1089  undo_no_t limit,
1090  roll_ptr_t* roll_ptr,
1091  mem_heap_t* heap)
1092 {
1093  trx_undo_t* undo;
1094  trx_undo_t* ins_undo;
1095  trx_undo_t* upd_undo;
1096  trx_undo_rec_t* undo_rec;
1097  trx_undo_rec_t* undo_rec_copy;
1098  undo_no_t undo_no;
1099  ibool is_insert;
1100  trx_rseg_t* rseg;
1101  ulint progress_pct;
1102  mtr_t mtr;
1103 
1104  rseg = trx->rseg;
1105 try_again:
1106  mutex_enter(&(trx->undo_mutex));
1107 
1108  if (trx->pages_undone >= TRX_ROLL_TRUNC_THRESHOLD) {
1109  mutex_enter(&rseg->mutex);
1110 
1111  trx_roll_try_truncate(trx);
1112 
1113  mutex_exit(&rseg->mutex);
1114  }
1115 
1116  ins_undo = trx->insert_undo;
1117  upd_undo = trx->update_undo;
1118 
1119  if (!ins_undo || ins_undo->empty) {
1120  undo = upd_undo;
1121  } else if (!upd_undo || upd_undo->empty) {
1122  undo = ins_undo;
1123  } else if (upd_undo->top_undo_no > ins_undo->top_undo_no) {
1124  undo = upd_undo;
1125  } else {
1126  undo = ins_undo;
1127  }
1128 
1129  if (!undo || undo->empty || limit > undo->top_undo_no) {
1130 
1131  if ((trx->undo_no_arr)->n_used == 0) {
1132  /* Rollback is ending */
1133 
1134  mutex_enter(&(rseg->mutex));
1135 
1136  trx_roll_try_truncate(trx);
1137 
1138  mutex_exit(&(rseg->mutex));
1139  }
1140 
1141  mutex_exit(&(trx->undo_mutex));
1142 
1143  return(NULL);
1144  }
1145 
1146  is_insert = (undo == ins_undo);
1147 
1148  *roll_ptr = trx_undo_build_roll_ptr(
1149  is_insert, undo->rseg->id, undo->top_page_no, undo->top_offset);
1150 
1151  mtr_start(&mtr);
1152 
1153  undo_rec = trx_roll_pop_top_rec(trx, undo, &mtr);
1154 
1155  undo_no = trx_undo_rec_get_undo_no(undo_rec);
1156 
1157  ut_ad(undo_no + 1 == trx->undo_no);
1158 
1159  /* We print rollback progress info if we are in a crash recovery
1160  and the transaction has at least 1000 row operations to undo. */
1161 
1162  if (trx == trx_roll_crash_recv_trx && trx_roll_max_undo_no > 1000) {
1163 
1164  progress_pct = 100 - (ulint)
1165  ((undo_no * 100) / trx_roll_max_undo_no);
1166  if (progress_pct != trx_roll_progress_printed_pct) {
1167  if (trx_roll_progress_printed_pct == 0) {
1168  fprintf(stderr,
1169  "\nInnoDB: Progress in percents:"
1170  " %lu", (ulong) progress_pct);
1171  } else {
1172  fprintf(stderr,
1173  " %lu", (ulong) progress_pct);
1174  }
1175  fflush(stderr);
1176  trx_roll_progress_printed_pct = progress_pct;
1177  }
1178  }
1179 
1180  trx->undo_no = undo_no;
1181 
1182  if (!trx_undo_arr_store_info(trx, undo_no)) {
1183  /* A query thread is already processing this undo log record */
1184 
1185  mutex_exit(&(trx->undo_mutex));
1186 
1187  mtr_commit(&mtr);
1188 
1189  goto try_again;
1190  }
1191 
1192  undo_rec_copy = trx_undo_rec_copy(undo_rec, heap);
1193 
1194  mutex_exit(&(trx->undo_mutex));
1195 
1196  mtr_commit(&mtr);
1197 
1198  return(undo_rec_copy);
1199 }
1200 
1201 /********************************************************************/
1206 UNIV_INTERN
1207 ibool
1209 /*=================*/
1210  trx_t* trx,
1211  undo_no_t undo_no)
1212 {
1213  ibool ret;
1214 
1215  mutex_enter(&(trx->undo_mutex));
1216 
1217  ret = trx_undo_arr_store_info(trx, undo_no);
1218 
1219  mutex_exit(&(trx->undo_mutex));
1220 
1221  return(ret);
1222 }
1223 
1224 /*******************************************************************/
1226 UNIV_INTERN
1227 void
1229 /*=================*/
1230  trx_t* trx,
1231  undo_no_t undo_no)
1232 {
1233  trx_undo_arr_t* arr;
1234 
1235  mutex_enter(&(trx->undo_mutex));
1236 
1237  arr = trx->undo_no_arr;
1238 
1239  trx_undo_arr_remove_info(arr, undo_no);
1240 
1241  mutex_exit(&(trx->undo_mutex));
1242 }
1243 
1244 /****************************************************************/
1250 static
1251 que_t*
1252 trx_roll_graph_build(
1253 /*=================*/
1254  trx_t* trx)
1255 {
1256  mem_heap_t* heap;
1257  que_fork_t* fork;
1258  que_thr_t* thr;
1259 
1260  ut_ad(trx_mutex_own(trx));
1261 
1262  heap = mem_heap_create(512);
1263  fork = que_fork_create(NULL, NULL, QUE_FORK_ROLLBACK, heap);
1264  fork->trx = trx;
1265 
1266  thr = que_thr_create(fork, heap);
1267 
1268  thr->child = row_undo_node_create(trx, thr, heap);
1269 
1270  return(fork);
1271 }
1272 
1273 /*********************************************************************/
1277 static
1278 que_thr_t*
1279 trx_rollback_start(
1280 /*===============*/
1281  trx_t* trx,
1282  ib_id_t roll_limit)
1285 {
1286  que_t* roll_graph;
1287 
1288  ut_ad(trx_mutex_own(trx));
1289 
1290  ut_ad(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0);
1291 
1292  /* Initialize the rollback field in the transaction */
1293 
1294  trx->roll_limit = roll_limit;
1295 
1296  ut_a(trx->roll_limit <= trx->undo_no);
1297 
1298  trx->pages_undone = 0;
1299 
1300  if (trx->undo_no_arr == NULL) {
1301  /* Single query thread -> 1 */
1302  trx->undo_no_arr = trx_undo_arr_create(1);
1303  }
1304 
1305  /* Build a 'query' graph which will perform the undo operations */
1306 
1307  roll_graph = trx_roll_graph_build(trx);
1308 
1309  trx->graph = roll_graph;
1310 
1312 
1313  return(que_fork_start_command(roll_graph));
1314 }
1315 
1316 /****************************************************************/
1318 static
1319 void
1320 trx_rollback_finish(
1321 /*================*/
1322  trx_t* trx)
1323 {
1324  ut_a(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0);
1325 
1326  trx_commit(trx);
1327 
1329 }
1330 
1331 /*********************************************************************/
1334 UNIV_INTERN
1335 roll_node_t*
1337 /*=============*/
1338  mem_heap_t* heap)
1339 {
1340  roll_node_t* node;
1341 
1342  node = static_cast<roll_node_t*>(mem_heap_zalloc(heap, sizeof(*node)));
1343 
1344  node->state = ROLL_NODE_SEND;
1345 
1346  node->common.type = QUE_NODE_ROLLBACK;
1347 
1348  return(node);
1349 }
1350 
1351 /***********************************************************/
1354 UNIV_INTERN
1355 que_thr_t*
1357 /*==============*/
1358  que_thr_t* thr)
1359 {
1360  roll_node_t* node;
1361 
1362  node = static_cast<roll_node_t*>(thr->run_node);
1363 
1364  ut_ad(que_node_get_type(node) == QUE_NODE_ROLLBACK);
1365 
1366  if (thr->prev_node == que_node_get_parent(node)) {
1367  node->state = ROLL_NODE_SEND;
1368  }
1369 
1370  if (node->state == ROLL_NODE_SEND) {
1371  trx_t* trx;
1372  ib_id_t roll_limit = 0;
1373 
1374  trx = thr_get_trx(thr);
1375 
1376  trx_mutex_enter(trx);
1377 
1378  node->state = ROLL_NODE_WAIT;
1379 
1380  ut_a(node->undo_thr == NULL);
1381 
1382  roll_limit = node->partial ? node->savept.least_undo_no : 0;
1383 
1385 
1386  node->undo_thr = trx_rollback_start(trx, roll_limit);
1387 
1388  trx_mutex_exit(trx);
1389 
1390  } else {
1391  ut_ad(node->state == ROLL_NODE_WAIT);
1392 
1393  thr->run_node = que_node_get_parent(node);
1394  }
1395 
1396  return(thr);
1397 }