1 /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
2 Copyright (c) 2009, 2021, MariaDB Corporation.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
16
17
18 #ifdef USE_PRAGMA_IMPLEMENTATION
19 #pragma implementation // gcc: Class implementation
20 #endif
21
22 #include "mariadb.h"
23 #include "sql_priv.h"
24 #include "transaction.h"
25 #include "debug_sync.h" // DEBUG_SYNC
26 #include "sql_acl.h"
27 #include "semisync_master.h"
28 #ifdef WITH_WSREP
29 #include "wsrep_trans_observer.h"
30 #endif /* WITH_WSREP */
31
32 /**
33 Helper: Tell tracker (if any) that transaction ended.
34 */
trans_track_end_trx(THD * thd)35 void trans_track_end_trx(THD *thd)
36 {
37 #ifndef EMBEDDED_LIBRARY
38 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
39 thd->session_tracker.transaction_info.end_trx(thd);
40 #endif //EMBEDDED_LIBRARY
41 }
42
43
44 /**
45 Helper: transaction ended, SET TRANSACTION one-shot variables
46 revert to session values. Let the transaction state tracker know.
47 */
trans_reset_one_shot_chistics(THD * thd)48 void trans_reset_one_shot_chistics(THD *thd)
49 {
50 #ifndef EMBEDDED_LIBRARY
51 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
52 {
53 thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_INHERIT);
54 thd->session_tracker.transaction_info.set_isol_level(thd, TX_ISOL_INHERIT);
55 }
56 #endif //EMBEDDED_LIBRARY
57 thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
58 thd->tx_read_only= thd->variables.tx_read_only;
59 }
60
61 /* Conditions under which the transaction state must not change. */
trans_check(THD * thd)62 static bool trans_check(THD *thd)
63 {
64 DBUG_ENTER("trans_check");
65
66 /*
67 Always commit statement transaction before manipulating with
68 the normal one.
69 */
70 DBUG_ASSERT(thd->transaction.stmt.is_empty());
71
72 if (unlikely(thd->in_sub_stmt))
73 my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
74 if (thd->transaction.xid_state.is_explicit_XA())
75 thd->transaction.xid_state.er_xaer_rmfail();
76 else
77 DBUG_RETURN(FALSE);
78
79 DBUG_RETURN(TRUE);
80 }
81
82
83 /**
84 Begin a new transaction.
85
86 @note Beginning a transaction implicitly commits any current
87 transaction and releases existing locks.
88
89 @param thd Current thread
90 @param flags Transaction flags
91
92 @retval FALSE Success
93 @retval TRUE Failure
94 */
95
trans_begin(THD * thd,uint flags)96 bool trans_begin(THD *thd, uint flags)
97 {
98 int res= FALSE;
99 DBUG_ENTER("trans_begin");
100
101 if (trans_check(thd))
102 DBUG_RETURN(TRUE);
103
104 thd->locked_tables_list.unlock_locked_tables(thd);
105
106 DBUG_ASSERT(!thd->locked_tables_mode);
107
108 if (thd->in_multi_stmt_transaction_mode() ||
109 (thd->variables.option_bits & OPTION_TABLE_LOCK))
110 {
111 thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
112 thd->server_status&=
113 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
114 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
115 res= MY_TEST(ha_commit_trans(thd, TRUE));
116 #ifdef WITH_WSREP
117 if (wsrep_thd_is_local(thd))
118 {
119 res= res || wsrep_after_statement(thd);
120 }
121 #endif /* WITH_WSREP */
122 }
123
124 thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
125
126 /*
127 The following set should not be needed as transaction state should
128 already be reset. We should at some point change this to an assert.
129 */
130 thd->transaction.all.reset();
131 thd->has_waiter= false;
132 thd->waiting_on_group_commit= false;
133 thd->transaction.start_time.reset(thd);
134
135 if (res)
136 DBUG_RETURN(TRUE);
137
138 /*
139 Release transactional metadata locks only after the
140 transaction has been committed.
141 */
142 thd->release_transactional_locks();
143
144 // The RO/RW options are mutually exclusive.
145 DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) &&
146 (flags & MYSQL_START_TRANS_OPT_READ_WRITE)));
147 if (flags & MYSQL_START_TRANS_OPT_READ_ONLY)
148 {
149 thd->tx_read_only= true;
150 #ifndef EMBEDDED_LIBRARY
151 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
152 thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_ONLY);
153 #endif //EMBEDDED_LIBRARY
154 }
155 else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE)
156 {
157 /*
158 Explicitly starting a RW transaction when the server is in
159 read-only mode, is not allowed unless the user has SUPER priv.
160 Implicitly starting a RW transaction is allowed for backward
161 compatibility.
162 */
163 const bool user_is_super=
164 MY_TEST(thd->security_ctx->master_access & SUPER_ACL);
165 if (opt_readonly && !user_is_super)
166 {
167 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
168 DBUG_RETURN(true);
169 }
170 thd->tx_read_only= false;
171 /*
172 This flags that tx_read_only was set explicitly, rather than
173 just from the session's default.
174 */
175 #ifndef EMBEDDED_LIBRARY
176 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
177 thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_WRITE);
178 #endif //EMBEDDED_LIBRARY
179 }
180
181 #ifdef WITH_WSREP
182 if (wsrep_thd_is_local(thd))
183 {
184 if (wsrep_sync_wait(thd))
185 DBUG_RETURN(TRUE);
186 if (!thd->tx_read_only &&
187 wsrep_start_transaction(thd, thd->wsrep_next_trx_id()))
188 DBUG_RETURN(TRUE);
189 }
190 #endif /* WITH_WSREP */
191
192 thd->variables.option_bits|= OPTION_BEGIN;
193 thd->server_status|= SERVER_STATUS_IN_TRANS;
194 if (thd->tx_read_only)
195 thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
196 DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
197
198 #ifndef EMBEDDED_LIBRARY
199 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
200 thd->session_tracker.transaction_info.add_trx_state(thd, TX_EXPLICIT);
201 #endif //EMBEDDED_LIBRARY
202
203 /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */
204 if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
205 {
206 #ifndef EMBEDDED_LIBRARY
207 if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
208 thd->session_tracker.transaction_info.add_trx_state(thd, TX_WITH_SNAPSHOT);
209 #endif //EMBEDDED_LIBRARY
210 res= ha_start_consistent_snapshot(thd);
211 }
212
213 DBUG_RETURN(MY_TEST(res));
214 }
215
216
217 /**
218 Commit the current transaction, making its changes permanent.
219
220 @param thd Current thread
221
222 @retval FALSE Success
223 @retval TRUE Failure
224 */
225
trans_commit(THD * thd)226 bool trans_commit(THD *thd)
227 {
228 int res;
229 DBUG_ENTER("trans_commit");
230
231 if (trans_check(thd))
232 DBUG_RETURN(TRUE);
233
234 thd->server_status&=
235 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
236 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
237 res= ha_commit_trans(thd, TRUE);
238
239 mysql_mutex_assert_not_owner(&LOCK_prepare_ordered);
240 mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock());
241 mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync);
242 mysql_mutex_assert_not_owner(&LOCK_commit_ordered);
243
244 /*
245 if res is non-zero, then ha_commit_trans has rolled back the
246 transaction, so the hooks for rollback will be called.
247 */
248 if (res)
249 {
250 #ifdef HAVE_REPLICATION
251 repl_semisync_master.wait_after_rollback(thd, FALSE);
252 #endif
253 }
254 else
255 {
256 #ifdef HAVE_REPLICATION
257 repl_semisync_master.wait_after_commit(thd, FALSE);
258 #endif
259 }
260 thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
261 thd->transaction.all.reset();
262 thd->lex->start_transaction_opt= 0;
263
264 trans_track_end_trx(thd);
265
266 DBUG_RETURN(MY_TEST(res));
267 }
268
269
270 /**
271 Implicitly commit the current transaction.
272
273 @note A implicit commit does not releases existing table locks.
274
275 @param thd Current thread
276
277 @retval FALSE Success
278 @retval TRUE Failure
279 */
280
trans_commit_implicit(THD * thd)281 bool trans_commit_implicit(THD *thd)
282 {
283 bool res= FALSE;
284 DBUG_ENTER("trans_commit_implicit");
285
286 if (trans_check(thd))
287 DBUG_RETURN(TRUE);
288
289 if (thd->variables.option_bits & OPTION_GTID_BEGIN)
290 DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. "
291 "Master and slave will have different GTID values"));
292
293 if (thd->in_multi_stmt_transaction_mode() ||
294 (thd->variables.option_bits & OPTION_TABLE_LOCK))
295 {
296 /* Safety if one did "drop table" on locked tables */
297 if (!thd->locked_tables_mode)
298 thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
299 thd->server_status&=
300 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
301 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
302 res= MY_TEST(ha_commit_trans(thd, TRUE));
303 }
304
305 thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
306 thd->transaction.all.reset();
307
308 /*
309 Upon implicit commit, reset the current transaction
310 isolation level and access mode. We do not care about
311 @@session.completion_type since it's documented
312 to not have any effect on implicit commit.
313 */
314 trans_reset_one_shot_chistics(thd);
315
316 trans_track_end_trx(thd);
317
318 DBUG_RETURN(res);
319 }
320
321
322 /**
323 Rollback the current transaction, canceling its changes.
324
325 @param thd Current thread
326
327 @retval FALSE Success
328 @retval TRUE Failure
329 */
330
trans_rollback(THD * thd)331 bool trans_rollback(THD *thd)
332 {
333 int res;
334 DBUG_ENTER("trans_rollback");
335
336 if (trans_check(thd))
337 DBUG_RETURN(TRUE);
338
339 thd->server_status&=
340 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
341 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
342 res= ha_rollback_trans(thd, TRUE);
343 #ifdef HAVE_REPLICATION
344 repl_semisync_master.wait_after_rollback(thd, FALSE);
345 #endif
346 thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
347 /* Reset the binlog transaction marker */
348 thd->variables.option_bits&= ~OPTION_GTID_BEGIN;
349 thd->transaction.all.reset();
350 thd->lex->start_transaction_opt= 0;
351
352 trans_track_end_trx(thd);
353
354 DBUG_RETURN(MY_TEST(res));
355 }
356
357
358 /**
359 Implicitly rollback the current transaction, typically
360 after deadlock was discovered.
361
362 @param thd Current thread
363
364 @retval False Success
365 @retval True Failure
366
367 @note ha_rollback_low() which is indirectly called by this
368 function will mark XA transaction for rollback by
369 setting appropriate RM error status if there was
370 transaction rollback request.
371 */
372
trans_rollback_implicit(THD * thd)373 bool trans_rollback_implicit(THD *thd)
374 {
375 int res;
376 DBUG_ENTER("trans_rollback_implict");
377
378 /*
379 Always commit/rollback statement transaction before manipulating
380 with the normal one.
381 Don't perform rollback in the middle of sub-statement, wait till
382 its end.
383 */
384 DBUG_ASSERT(thd->transaction.stmt.is_empty() && !thd->in_sub_stmt);
385
386 thd->server_status&= ~SERVER_STATUS_IN_TRANS;
387 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
388 res= ha_rollback_trans(thd, true);
389 /*
390 We don't reset OPTION_BEGIN flag below to simulate implicit start
391 of new transacton in @@autocommit=1 mode. This is necessary to
392 preserve backward compatibility.
393 */
394 thd->variables.option_bits&= ~(OPTION_KEEP_LOG);
395 thd->transaction.all.reset();
396
397 /* Rollback should clear transaction_rollback_request flag. */
398 DBUG_ASSERT(! thd->transaction_rollback_request);
399
400 trans_track_end_trx(thd);
401
402 DBUG_RETURN(MY_TEST(res));
403 }
404
405
406 /**
407 Commit the single statement transaction.
408
409 @note Note that if the autocommit is on, then the following call
410 inside InnoDB will commit or rollback the whole transaction
411 (= the statement). The autocommit mechanism built into InnoDB
412 is based on counting locks, but if the user has used LOCK
413 TABLES then that mechanism does not know to do the commit.
414
415 @param thd Current thread
416
417 @retval FALSE Success
418 @retval TRUE Failure
419 */
420
trans_commit_stmt(THD * thd)421 bool trans_commit_stmt(THD *thd)
422 {
423 DBUG_ENTER("trans_commit_stmt");
424 int res= FALSE;
425 /*
426 We currently don't invoke commit/rollback at end of
427 a sub-statement. In future, we perhaps should take
428 a savepoint for each nested statement, and release the
429 savepoint when statement has succeeded.
430 */
431 DBUG_ASSERT(! thd->in_sub_stmt);
432
433 thd->merge_unsafe_rollback_flags();
434
435 if (thd->transaction.stmt.ha_list)
436 {
437 res= ha_commit_trans(thd, FALSE);
438 if (! thd->in_active_multi_stmt_transaction())
439 {
440 trans_reset_one_shot_chistics(thd);
441 }
442 }
443
444 mysql_mutex_assert_not_owner(&LOCK_prepare_ordered);
445 mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock());
446 mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync);
447 mysql_mutex_assert_not_owner(&LOCK_commit_ordered);
448
449 /*
450 if res is non-zero, then ha_commit_trans has rolled back the
451 transaction, so the hooks for rollback will be called.
452 */
453 if (res)
454 {
455 #ifdef HAVE_REPLICATION
456 repl_semisync_master.wait_after_rollback(thd, FALSE);
457 #endif
458 }
459 else
460 {
461 #ifdef HAVE_REPLICATION
462 repl_semisync_master.wait_after_commit(thd, FALSE);
463 #endif
464 }
465
466 thd->transaction.stmt.reset();
467
468 DBUG_RETURN(MY_TEST(res));
469 }
470
471
472 /**
473 Rollback the single statement transaction.
474
475 @param thd Current thread
476
477 @retval FALSE Success
478 @retval TRUE Failure
479 */
trans_rollback_stmt(THD * thd)480 bool trans_rollback_stmt(THD *thd)
481 {
482 DBUG_ENTER("trans_rollback_stmt");
483
484 /*
485 We currently don't invoke commit/rollback at end of
486 a sub-statement. In future, we perhaps should take
487 a savepoint for each nested statement, and release the
488 savepoint when statement has succeeded.
489 */
490 DBUG_ASSERT(! thd->in_sub_stmt);
491
492 thd->merge_unsafe_rollback_flags();
493
494 if (thd->transaction.stmt.ha_list)
495 {
496 ha_rollback_trans(thd, FALSE);
497 if (! thd->in_active_multi_stmt_transaction())
498 trans_reset_one_shot_chistics(thd);
499 }
500
501 #ifdef HAVE_REPLICATION
502 repl_semisync_master.wait_after_rollback(thd, FALSE);
503 #endif
504
505 thd->transaction.stmt.reset();
506
507 DBUG_RETURN(FALSE);
508 }
509
510 /* Find a named savepoint in the current transaction. */
511 static SAVEPOINT **
find_savepoint(THD * thd,LEX_CSTRING name)512 find_savepoint(THD *thd, LEX_CSTRING name)
513 {
514 SAVEPOINT **sv= &thd->transaction.savepoints;
515
516 while (*sv)
517 {
518 if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length,
519 (uchar *) (*sv)->name, (*sv)->length) == 0)
520 break;
521 sv= &(*sv)->prev;
522 }
523
524 return sv;
525 }
526
527
528 /**
529 Set a named transaction savepoint.
530
531 @param thd Current thread
532 @param name Savepoint name
533
534 @retval FALSE Success
535 @retval TRUE Failure
536 */
537
trans_savepoint(THD * thd,LEX_CSTRING name)538 bool trans_savepoint(THD *thd, LEX_CSTRING name)
539 {
540 SAVEPOINT **sv, *newsv;
541 DBUG_ENTER("trans_savepoint");
542
543 if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) ||
544 !opt_using_transactions)
545 DBUG_RETURN(FALSE);
546
547 if (thd->transaction.xid_state.check_has_uncommitted_xa())
548 DBUG_RETURN(TRUE);
549
550 sv= find_savepoint(thd, name);
551
552 if (*sv) /* old savepoint of the same name exists */
553 {
554 newsv= *sv;
555 ha_release_savepoint(thd, *sv);
556 *sv= (*sv)->prev;
557 }
558 else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root,
559 savepoint_alloc_size)) == NULL)
560 {
561 my_error(ER_OUT_OF_RESOURCES, MYF(0));
562 DBUG_RETURN(TRUE);
563 }
564
565 newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length);
566 newsv->length= (uint)name.length;
567
568 /*
569 if we'll get an error here, don't add new savepoint to the list.
570 we'll lose a little bit of memory in transaction mem_root, but it'll
571 be free'd when transaction ends anyway
572 */
573 if (unlikely(ha_savepoint(thd, newsv)))
574 DBUG_RETURN(TRUE);
575
576 newsv->prev= thd->transaction.savepoints;
577 thd->transaction.savepoints= newsv;
578
579 /*
580 Remember locks acquired before the savepoint was set.
581 They are used as a marker to only release locks acquired after
582 the setting of this savepoint.
583 Note: this works just fine if we're under LOCK TABLES,
584 since mdl_savepoint() is guaranteed to be beyond
585 the last locked table. This allows to release some
586 locks acquired during LOCK TABLES.
587 */
588 newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint();
589
590 DBUG_RETURN(FALSE);
591 }
592
593
594 /**
595 Rollback a transaction to the named savepoint.
596
597 @note Modifications that the current transaction made to
598 rows after the savepoint was set are undone in the
599 rollback.
600
601 @note Savepoints that were set at a later time than the
602 named savepoint are deleted.
603
604 @param thd Current thread
605 @param name Savepoint name
606
607 @retval FALSE Success
608 @retval TRUE Failure
609 */
610
trans_rollback_to_savepoint(THD * thd,LEX_CSTRING name)611 bool trans_rollback_to_savepoint(THD *thd, LEX_CSTRING name)
612 {
613 int res= FALSE;
614 SAVEPOINT *sv= *find_savepoint(thd, name);
615 DBUG_ENTER("trans_rollback_to_savepoint");
616
617 if (sv == NULL)
618 {
619 my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
620 DBUG_RETURN(TRUE);
621 }
622
623 if (thd->transaction.xid_state.check_has_uncommitted_xa())
624 DBUG_RETURN(TRUE);
625
626 if (ha_rollback_to_savepoint(thd, sv))
627 res= TRUE;
628 else if (((thd->variables.option_bits & OPTION_KEEP_LOG) ||
629 thd->transaction.all.modified_non_trans_table) &&
630 !thd->slave_thread)
631 push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
632 ER_WARNING_NOT_COMPLETE_ROLLBACK,
633 ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK));
634
635 thd->transaction.savepoints= sv;
636
637 if (res)
638 /* An error occurred during rollback; we cannot release any MDL */;
639 else if (thd->variables.sql_log_bin &&
640 (WSREP_EMULATE_BINLOG_NNULL(thd) || mysql_bin_log.is_open()))
641 /* In some cases (such as with non-transactional tables) we may
642 choose to preserve events that were added after the SAVEPOINT,
643 delimiting them by SAVEPOINT and ROLLBACK TO SAVEPOINT statements.
644 Prematurely releasing MDL on such objects would break replication. */;
645 else if (ha_rollback_to_savepoint_can_release_mdl(thd))
646 thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
647
648 DBUG_RETURN(MY_TEST(res));
649 }
650
651
652 /**
653 Remove the named savepoint from the set of savepoints of
654 the current transaction.
655
656 @note No commit or rollback occurs. It is an error if the
657 savepoint does not exist.
658
659 @param thd Current thread
660 @param name Savepoint name
661
662 @retval FALSE Success
663 @retval TRUE Failure
664 */
665
trans_release_savepoint(THD * thd,LEX_CSTRING name)666 bool trans_release_savepoint(THD *thd, LEX_CSTRING name)
667 {
668 int res= FALSE;
669 SAVEPOINT *sv= *find_savepoint(thd, name);
670 DBUG_ENTER("trans_release_savepoint");
671
672 if (sv == NULL)
673 {
674 my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str);
675 DBUG_RETURN(TRUE);
676 }
677
678 if (ha_release_savepoint(thd, sv))
679 res= TRUE;
680
681 thd->transaction.savepoints= sv->prev;
682
683 DBUG_RETURN(MY_TEST(res));
684 }
685