1 /*****************************************************************************
2
3 Copyright (c) 1996, 2021, Oracle and/or its affiliates.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation. The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License, version 2.0, for more details.
20
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
24
25 *****************************************************************************/
26
27 /**************************************************//**
28 @file read/read0read.cc
29 Cursor read
30
31 Created 2/16/1997 Heikki Tuuri
32 *******************************************************/
33
34 #include "read0read.h"
35 #include "read0i_s.h"
36
37 #include "srv0srv.h"
38 #include "trx0sys.h"
39
40 /*
41 -------------------------------------------------------------------------------
42 FACT A: Cursor read view on a secondary index sees only committed versions
43 -------
44 of the records in the secondary index or those versions of rows created
45 by transaction which created a cursor before cursor was created even
46 if transaction which created the cursor has changed that clustered index page.
47
48 PROOF: We must show that read goes always to the clustered index record
49 to see that record is visible in the cursor read view. Consider e.g.
50 following table and SQL-clauses:
51
52 create table t1(a int not null, b int, primary key(a), index(b));
53 insert into t1 values (1,1),(2,2);
54 commit;
55
56 Now consider that we have a cursor for a query
57
58 select b from t1 where b >= 1;
59
60 This query will use secondary key on the table t1. Now after the first fetch
61 on this cursor if we do a update:
62
63 update t1 set b = 5 where b = 2;
64
65 Now second fetch of the cursor should not see record (2,5) instead it should
66 see record (2,2).
67
68 We also should show that if we have delete t1 where b = 5; we still
69 can see record (2,2).
70
71 When we access a secondary key record maximum transaction id is fetched
72 from this record and this trx_id is compared to up_limit_id in the view.
73 If trx_id in the record is greater or equal than up_limit_id in the view
74 cluster record is accessed. Because trx_id of the creating
75 transaction is stored when this view was created to the list of
76 trx_ids not seen by this read view previous version of the
77 record is requested to be built. This is build using clustered record.
78 If the secondary key record is delete-marked, its corresponding
79 clustered record can be already be purged only if records
80 trx_id < low_limit_no. Purge can't remove any record deleted by a
81 transaction which was active when cursor was created. But, we still
82 may have a deleted secondary key record but no clustered record. But,
83 this is not a problem because this case is handled in
84 row_sel_get_clust_rec() function which is called
85 whenever we note that this read view does not see trx_id in the
86 record. Thus, we see correct version. Q. E. D.
87
88 -------------------------------------------------------------------------------
89 FACT B: Cursor read view on a clustered index sees only committed versions
90 -------
91 of the records in the clustered index or those versions of rows created
92 by transaction which created a cursor before cursor was created even
93 if transaction which created the cursor has changed that clustered index page.
94
95 PROOF: Consider e.g.following table and SQL-clauses:
96
97 create table t1(a int not null, b int, primary key(a));
98 insert into t1 values (1),(2);
99 commit;
100
101 Now consider that we have a cursor for a query
102
103 select a from t1 where a >= 1;
104
105 This query will use clustered key on the table t1. Now after the first fetch
106 on this cursor if we do a update:
107
108 update t1 set a = 5 where a = 2;
109
110 Now second fetch of the cursor should not see record (5) instead it should
111 see record (2).
112
113 We also should show that if we have execute delete t1 where a = 5; after
114 the cursor is opened we still can see record (2).
115
116 When accessing clustered record we always check if this read view sees
117 trx_id stored to clustered record. By default we don't see any changes
118 if record trx_id >= low_limit_id i.e. change was made transaction
119 which started after transaction which created the cursor. If row
120 was changed by the future transaction a previous version of the
121 clustered record is created. Thus we see only committed version in
122 this case. We see all changes made by committed transactions i.e.
123 record trx_id < up_limit_id. In this case we don't need to do anything,
124 we already see correct version of the record. We don't see any changes
125 made by active transaction except creating transaction. We have stored
126 trx_id of creating transaction to list of trx_ids when this view was
127 created. Thus we can easily see if this record was changed by the
128 creating transaction. Because we already have clustered record we can
129 access roll_ptr. Using this roll_ptr we can fetch undo record.
130 We can now check that undo_no of the undo record is less than undo_no of the
131 trancaction which created a view when cursor was created. We see this
132 clustered record only in case when record undo_no is less than undo_no
133 in the view. If this is not true we build based on undo_rec previous
134 version of the record. This record is found because purge can't remove
135 records accessed by active transaction. Thus we see correct version. Q. E. D.
136 -------------------------------------------------------------------------------
137 FACT C: Purge does not remove any delete-marked row that is visible
138 -------
139 in any cursor read view.
140
141 PROOF: We know that:
142 1: Currently active read views in trx_sys_t::view_list are ordered by
143 ReadView::low_limit_no in descending order, that is,
144 newest read view first.
145
146 2: Purge clones the oldest read view and uses that to determine whether there
147 are any active transactions that can see the to be purged records.
148
149 Therefore any joining or active transaction will not have a view older
150 than the purge view, according to 1.
151
152 When purge needs to remove a delete-marked row from a secondary index,
153 it will first check that the DB_TRX_ID value of the corresponding
154 record in the clustered index is older than the purge view. It will
155 also check if there is a newer version of the row (clustered index
156 record) that is not delete-marked in the secondary index. If such a
157 row exists and is collation-equal to the delete-marked secondary index
158 record then purge will not remove the secondary index record.
159
160 Delete-marked clustered index records will be removed by
161 row_purge_remove_clust_if_poss(), unless the clustered index record
162 (and its DB_ROLL_PTR) has been updated. Every new version of the
163 clustered index record will update DB_ROLL_PTR, pointing to a new UNDO
164 log entry that allows the old version to be reconstructed. The
165 DB_ROLL_PTR in the oldest remaining version in the old-version chain
166 may be pointing to garbage (an undo log record discarded by purge),
167 but it will never be dereferenced, because the purge view is older
168 than any active transaction.
169
170 For details see: row_vers_old_has_index_entry() and row_purge_poss_sec()
171
172 Some additional issues:
173
174 What if trx_sys->view_list == NULL and some transaction T1 and Purge both
175 try to open read_view at same time. Only one can acquire trx_sys->mutex.
176 In which order will the views be opened? Should it matter? If no, why?
177
178 The order does not matter. No new transactions can be created and no running
179 RW transaction can commit or rollback (or free views). AC-NL-RO transactions
180 will mark their views as closed but not actually free their views.
181 */
182
183 /** Minimum number of elements to reserve in ReadView::ids_t */
184 static const ulint MIN_TRX_IDS = 32;
185
186 #ifdef UNIV_DEBUG
187 /** Functor to validate the view list. */
188 struct ViewCheck {
189
ViewCheckViewCheck190 ViewCheck() : m_prev_view() { }
191
operator ()ViewCheck192 void operator()(const ReadView* view)
193 {
194 ut_a(m_prev_view == NULL
195 || view->is_closed()
196 || view->le(m_prev_view));
197
198 m_prev_view = view;
199 }
200
201 const ReadView* m_prev_view;
202 };
203
204 /**
205 Validates a read view list. */
206
207 bool
validate() const208 MVCC::validate() const
209 {
210 ViewCheck check;
211
212 ut_ad(mutex_own(&trx_sys->mutex));
213
214 ut_list_map(m_views, check);
215
216 return(true);
217 }
218 #endif /* UNIV_DEBUG */
219
220 /**
221 Try and increase the size of the array. Old elements are
222 copied across.
223 @param n Make space for n elements */
224
225 void
reserve(ulint n)226 ReadView::ids_t::reserve(ulint n)
227 {
228 if (n <= capacity()) {
229 return;
230 }
231
232 /** Keep a minimum threshold */
233 if (n < MIN_TRX_IDS) {
234 n = MIN_TRX_IDS;
235 }
236
237 value_type* p = m_ptr;
238
239 m_ptr = UT_NEW_ARRAY_NOKEY(value_type, n);
240
241 m_reserved = n;
242
243 ut_ad(size() < capacity());
244
245 if (p != NULL) {
246
247 ::memmove(m_ptr, p, size() * sizeof(value_type));
248
249 UT_DELETE_ARRAY(p);
250 }
251 }
252
253 /**
254 Copy and overwrite this array contents
255 @param start Source array
256 @param end Pointer to end of array */
257
258 void
assign(const value_type * start,const value_type * end)259 ReadView::ids_t::assign(const value_type* start, const value_type* end)
260 {
261 ut_ad(end >= start);
262
263 ulint n = end - start;
264
265 /* No need to copy the old contents across during reserve(). */
266 clear();
267
268 /* Create extra space if required. */
269 reserve(n);
270
271 resize(n);
272
273 ut_ad(size() == n);
274
275 ::memmove(m_ptr, start, size() * sizeof(value_type));
276 }
277
278 /**
279 Append a value to the array.
280 @param value the value to append */
281
282 void
push_back(value_type value)283 ReadView::ids_t::push_back(value_type value)
284 {
285 if (capacity() <= size()) {
286 reserve(size() * 2);
287 }
288
289 m_ptr[m_size++] = value;
290 ut_ad(size() <= capacity());
291 }
292
293 /**
294 Insert the value in the correct slot, preserving the order. Doesn't
295 check for duplicates. */
296
297 void
insert(value_type value)298 ReadView::ids_t::insert(value_type value)
299 {
300 ut_ad(value > 0);
301
302 reserve(size() + 1);
303
304 if (empty() || back() < value) {
305 push_back(value);
306 return;
307 }
308
309 value_type* end = data() + size();
310 value_type* ub = std::upper_bound(data(), end, value);
311
312 if (ub == end) {
313 push_back(value);
314 } else {
315 ut_ad(ub < end);
316
317 ulint n_elems = std::distance(ub, end);
318 ulint n = n_elems * sizeof(value_type);
319
320 /* Note: Copying overlapped memory locations. */
321 ::memmove(ub + 1, ub, n);
322
323 *ub = value;
324
325 resize(size() + 1);
326 }
327 }
328
329 /**
330 ReadView constructor */
ReadView()331 ReadView::ReadView()
332 :
333 m_low_limit_id(),
334 m_up_limit_id(),
335 m_creator_trx_id(),
336 m_ids(),
337 m_low_limit_no(),
338 m_cloned(false)
339 {
340 ut_d(::memset(&m_view_list, 0x0, sizeof(m_view_list)));
341 }
342
343 /**
344 ReadView destructor */
~ReadView()345 ReadView::~ReadView()
346 {
347 // Do nothing
348 }
349
350 /** Constructor
351 @param size Number of views to pre-allocate */
MVCC(ulint size)352 MVCC::MVCC(ulint size)
353 {
354 UT_LIST_INIT(m_free, &ReadView::m_view_list);
355 UT_LIST_INIT(m_views, &ReadView::m_view_list);
356
357 for (ulint i = 0; i < size; ++i) {
358 ReadView* view = UT_NEW_NOKEY(ReadView());
359
360 UT_LIST_ADD_FIRST(m_free, view);
361 }
362 }
363
~MVCC()364 MVCC::~MVCC()
365 {
366 for (ReadView* view = UT_LIST_GET_FIRST(m_free);
367 view != NULL;
368 view = UT_LIST_GET_FIRST(m_free)) {
369
370 UT_LIST_REMOVE(m_free, view);
371
372 UT_DELETE(view);
373 }
374
375 ut_a(UT_LIST_GET_LEN(m_views) == 0);
376 }
377
378 /** Insert the view in the proper order into the view list.
379 @param view view to add */
380 void
view_add(const ReadView * view)381 MVCC::view_add(const ReadView* view)
382 {
383 ut_ad(trx_sys_mutex_own());
384
385 UT_LIST_ADD_FIRST(m_views, const_cast<ReadView *>(view));
386
387 ut_ad(!view->is_closed());
388
389 ut_ad(validate());
390 }
391
392 /**
393 Copy the transaction ids from the source vector */
394
395 void
copy_trx_ids(const trx_ids_t & trx_ids)396 ReadView::copy_trx_ids(const trx_ids_t& trx_ids)
397 {
398 ut_ad(!m_cloned);
399 ulint size = trx_ids.size();
400
401 if (m_creator_trx_id > 0) {
402 ut_ad(size > 0);
403 --size;
404 }
405
406 if (size == 0) {
407 m_ids.clear();
408 return;
409 }
410
411 m_ids.reserve(size);
412 m_ids.resize(size);
413
414 ids_t::value_type* p = m_ids.data();
415
416 /* Copy all the trx_ids except the creator trx id */
417
418 if (m_creator_trx_id > 0) {
419
420 /* Note: We go through all this trouble because it is
421 unclear whether std::vector::resize() will cause an
422 overhead or not. We should test this extensively and
423 if the vector to vector copy is fast enough then get
424 rid of this code and replace it with more readable
425 and obvious code. The code below does exactly one copy,
426 and filters out the creator's trx id. */
427
428 trx_ids_t::const_iterator it = std::lower_bound(
429 trx_ids.begin(), trx_ids.end(), m_creator_trx_id);
430
431 ut_ad(it != trx_ids.end() && *it == m_creator_trx_id);
432
433 ulint i = std::distance(trx_ids.begin(), it);
434 ulint n = i * sizeof(trx_ids_t::value_type);
435
436 ::memmove(p, &trx_ids[0], n);
437
438 n = (trx_ids.size() - i - 1) * sizeof(trx_ids_t::value_type);
439
440 ut_ad(i + (n / sizeof(trx_ids_t::value_type)) == m_ids.size());
441
442 if (n > 0) {
443 ::memmove(p + i, &trx_ids[i + 1], n);
444 }
445 } else {
446 ulint n = size * sizeof(trx_ids_t::value_type);
447
448 ::memmove(p, &trx_ids[0], n);
449 }
450
451 #ifdef UNIV_DEBUG
452 /* Assert that all transaction ids in list are active. */
453 for (trx_ids_t::const_iterator it = trx_ids.begin();
454 it != trx_ids.end(); ++it) {
455
456 trx_t* trx = trx_get_rw_trx_by_id(*it);
457 ut_ad(trx != NULL);
458 ut_ad(trx->state == TRX_STATE_ACTIVE
459 || trx->state == TRX_STATE_PREPARED);
460 }
461 #endif /* UNIV_DEBUG */
462 }
463
464 /**
465 Opens a read view where exactly the transactions serialized before this
466 point in time are seen in the view.
467 @param id Creator transaction id */
468
469 void
prepare(trx_id_t id)470 ReadView::prepare(trx_id_t id)
471 {
472 ut_ad(!m_cloned);
473 ut_ad(mutex_own(&trx_sys->mutex));
474
475 m_creator_trx_id = id;
476
477 m_low_limit_no = m_low_limit_id = trx_sys->max_trx_id;
478
479 if (!trx_sys->rw_trx_ids.empty()) {
480 copy_trx_ids(trx_sys->rw_trx_ids);
481 } else {
482 m_ids.clear();
483 }
484
485 if (UT_LIST_GET_LEN(trx_sys->serialisation_list) > 0) {
486 const trx_t* trx;
487
488 trx = UT_LIST_GET_FIRST(trx_sys->serialisation_list);
489
490 if (trx->no < m_low_limit_no) {
491 m_low_limit_no = trx->no;
492 }
493 }
494 }
495
496 /**
497 Complete the read view creation */
498
499 void
complete()500 ReadView::complete()
501 {
502 ut_ad(!m_cloned);
503 /* The first active transaction has the smallest id. */
504 m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id;
505
506 ut_ad(m_up_limit_id <= m_low_limit_id);
507
508 m_closed = false;
509 }
510
511 /**
512 Find a free view from the active list, if none found then allocate
513 a new view.
514 @return a view to use */
515
516 ReadView*
get_view()517 MVCC::get_view()
518 {
519 ut_ad(mutex_own(&trx_sys->mutex));
520
521 ReadView* view;
522
523 if (UT_LIST_GET_LEN(m_free) > 0) {
524 view = UT_LIST_GET_FIRST(m_free);
525 UT_LIST_REMOVE(m_free, view);
526 } else {
527 view = UT_NEW_NOKEY(ReadView());
528
529 if (view == NULL) {
530 ib::error() << "Failed to allocate MVCC view";
531 }
532 }
533
534 return(view);
535 }
536
537 /**
538 Release a view that is inactive but not closed. Caller must own
539 the trx_sys_t::mutex.
540 @param view View to release */
541 void
view_release(ReadView * & view)542 MVCC::view_release(ReadView*& view)
543 {
544 ut_ad(!srv_read_only_mode);
545 ut_ad(trx_sys_mutex_own());
546
547 uintptr_t p = reinterpret_cast<uintptr_t>(view);
548
549 ut_a(p & 0x1);
550
551 view = reinterpret_cast<ReadView*>(p & ~1);
552
553 ut_ad(view->m_closed);
554 ut_ad(!view->m_cloned);
555
556 /** RW transactions should not free their views here. Their views
557 should freed using view_close_view() */
558
559 ut_ad(view->m_creator_trx_id == 0);
560
561 UT_LIST_REMOVE(m_views, view);
562
563 UT_LIST_ADD_LAST(m_free, view);
564
565 view = NULL;
566 }
567
568 /**
569 Allocate and create a view.
570 @param view view owned by this class created for the
571 caller. Must be freed by calling view_close()
572 @param trx transaction instance of caller */
573 void
view_open(ReadView * & view,trx_t * trx)574 MVCC::view_open(ReadView*& view, trx_t* trx)
575 {
576 ut_ad(!srv_read_only_mode);
577
578 /** If no new RW transaction has been started since the last view
579 was created then reuse the the existing view. */
580 if (view != NULL) {
581
582 uintptr_t p = reinterpret_cast<uintptr_t>(view);
583
584 view = reinterpret_cast<ReadView*>(p & ~1);
585
586 ut_ad(view->m_closed);
587
588 /* NOTE: This can be optimised further, for now we only
589 resuse the view iff there are no active RW transactions.
590
591 There is an inherent race here between purge and this
592 thread. Purge will skip views that are marked as closed.
593 Therefore we must set the low limit id after we reset the
594 closed status after the check. */
595
596 if (trx_is_autocommit_non_locking(trx) && view->empty()) {
597
598 view->m_closed = false;
599
600 if (view->m_low_limit_id == trx_sys_get_max_trx_id()) {
601 return;
602 } else {
603 view->m_closed = true;
604 }
605 }
606
607 mutex_enter(&trx_sys->mutex);
608
609 UT_LIST_REMOVE(m_views, view);
610
611 } else {
612 mutex_enter(&trx_sys->mutex);
613
614 view = get_view();
615 }
616
617 if (view != NULL) {
618
619 view->prepare(trx->id);
620
621 view->complete();
622
623 view_add(view);
624
625 }
626
627 trx_sys_mutex_exit();
628 }
629
630 /**
631 Get the oldest (active) view in the system.
632 @return oldest view if found or NULL */
633
634 ReadView*
get_oldest_view() const635 MVCC::get_oldest_view() const
636 {
637 ReadView* view;
638
639 ut_ad(mutex_own(&trx_sys->mutex));
640
641 for (view = UT_LIST_GET_LAST(m_views);
642 view != NULL;
643 view = UT_LIST_GET_PREV(m_view_list, view)) {
644
645 if (!view->is_closed()) {
646 break;
647 }
648 }
649
650 return(view);
651 }
652
653 /**
654 Copy state from another view. Must call copy_complete() to finish.
655 @param other view to copy from */
656
657 void
copy_prepare(const ReadView & other)658 ReadView::copy_prepare(const ReadView& other)
659 {
660 ut_ad(&other != this);
661
662 if (!other.m_ids.empty()) {
663 const ids_t::value_type* p = other.m_ids.data();
664
665 m_ids.assign(p, p + other.m_ids.size());
666 } else {
667 m_ids.clear();
668 }
669
670 m_up_limit_id = other.m_up_limit_id;
671
672 m_low_limit_no = other.m_low_limit_no;
673
674 m_low_limit_id = other.m_low_limit_id;
675
676 m_creator_trx_id = other.m_creator_trx_id;
677 }
678
679 /**
680 Complete the copy, insert the creator transaction id into the
681 m_ids too and adjust the m_up_limit_id, if required */
682
683 void
copy_complete()684 ReadView::copy_complete()
685 {
686 ut_ad(!trx_sys_mutex_own());
687
688 if (m_creator_trx_id > 0) {
689 m_ids.insert(m_creator_trx_id);
690 }
691
692 if (!m_ids.empty()) {
693 /* The last active transaction has the smallest id. */
694 m_up_limit_id = std::min(m_ids.front(), m_up_limit_id);
695 }
696
697 ut_ad(m_up_limit_id <= m_low_limit_id);
698
699 /* We added the creator transaction ID to the m_ids. */
700 m_creator_trx_id = 0;
701 }
702
703 /**
704 Clones a read view object. The resulting read view has identical change
705 visibility as the donor read view
706 @param result pointer to resulting read view. If NULL, a view will be
707 allocated. If non-NULL, a view will overwrite a previously-existing
708 in-use or released view.
709 @param from_trx transation owning the donor read view. */
710
711 void
clone(ReadView * & result,trx_t * from_trx) const712 ReadView::clone(ReadView*& result, trx_t* from_trx) const
713 {
714 ut_ad(from_trx->read_view == this);
715 ut_ad(trx_sys_mutex_own());
716
717 if (!result)
718 result = trx_sys->mvcc->get_view();
719 else {
720 result = reinterpret_cast<ReadView *>
721 (reinterpret_cast<uintptr_t>(result) & ~1);
722 }
723
724 // Set the creating trx id of the clone to that of donor.
725 trx_id_t from_trx_id;
726 if (from_trx->read_view->m_creator_trx_id != 0) {
727 // The donor transaction is RO, and a clone itself
728 from_trx_id = from_trx->read_view->m_creator_trx_id;
729 } else if (from_trx->id == 0) {
730 // The donor transaction is RO, thus does not have a trx ID
731 // yet which the cloned view must see, if it assigned later
732 if (!from_trx->preallocated_id) {
733 // Preallocate a transaction id for the donor
734 from_trx_id = from_trx->preallocated_id
735 = trx_sys_get_new_trx_id();
736 } else {
737 // This transaction has already been cloned
738 from_trx_id = from_trx->preallocated_id;
739 }
740 } else {
741 // The donor transaction is RW
742 from_trx_id = from_trx->id;
743 }
744
745 result->copy_prepare(*this);
746 // Calling copy_complete would be redundant for us and would force
747 // a too early trx sys mutex release.
748 result->m_creator_trx_id = from_trx_id;
749 // If the clone transaction is RO and is later promoted to RW, make
750 // sure not to add its own id to its view
751 result->m_cloned = true;
752 result->m_closed = false;
753 }
754
755 /** Clones the oldest view and stores it in view. No need to
756 call view_close(). The caller owns the view that is passed in.
757 This function is called by Purge to determine whether it should
758 purge the delete marked record or not.
759 @param view Preallocated view, owned by the caller */
760
761 void
clone_oldest_view(ReadView * view)762 MVCC::clone_oldest_view(ReadView* view)
763 {
764 mutex_enter(&trx_sys->mutex);
765
766 ReadView* oldest_view = get_oldest_view();
767
768 if (oldest_view == NULL) {
769
770 view->prepare(0);
771
772 trx_sys_mutex_exit();
773
774 view->complete();
775
776 } else {
777 view->copy_prepare(*oldest_view);
778
779 trx_sys_mutex_exit();
780
781 view->copy_complete();
782 }
783 }
784
785 /**
786 @return the number of active views */
787
788 ulint
size() const789 MVCC::size() const
790 {
791 trx_sys_mutex_enter();
792
793 ulint size = 0;
794
795 for (const ReadView* view = UT_LIST_GET_FIRST(m_views);
796 view != NULL;
797 view = UT_LIST_GET_NEXT(m_view_list, view)) {
798
799 if (!view->is_closed()) {
800 ++size;
801 }
802 }
803
804 trx_sys_mutex_exit();
805
806 return(size);
807 }
808
809 /**
810 Close a view created by the above function.
811 @para view view allocated by trx_open.
812 @param own_mutex true if caller owns trx_sys_t::mutex */
813
814 void
view_close(ReadView * & view,bool own_mutex)815 MVCC::view_close(ReadView*& view, bool own_mutex)
816 {
817 uintptr_t p = reinterpret_cast<uintptr_t>(view);
818
819 /* Note: The assumption here is that AC-NL-RO transactions will
820 call this function with own_mutex == false. */
821 if (!own_mutex) {
822 /* Sanitise the pointer first. */
823 ReadView* ptr = reinterpret_cast<ReadView*>(p & ~1);
824
825 /* Note this can be called for a read view that
826 was already closed. */
827 ptr->m_closed = true;
828 ptr->m_cloned = false;
829
830 /* Set the view as closed. */
831 view = reinterpret_cast<ReadView*>(p | 0x1);
832 } else {
833 view = reinterpret_cast<ReadView*>(p & ~1);
834
835 view->close();
836
837 UT_LIST_REMOVE(m_views, view);
838 UT_LIST_ADD_LAST(m_free, view);
839
840 ut_ad(validate());
841
842 view = NULL;
843 }
844 }
845
846 /**
847 Set the view creator transaction id. Note: This shouldbe set only
848 for views created by RW transactions.
849 @param view Set the creator trx id for this view
850 @param id Transaction id to set */
851
852 void
set_view_creator_trx_id(ReadView * view,trx_id_t id)853 MVCC::set_view_creator_trx_id(ReadView* view, trx_id_t id)
854 {
855 ut_ad(!view->is_cloned());
856 ut_ad(id > 0);
857 ut_ad(mutex_own(&trx_sys->mutex));
858
859 view->creator_trx_id(id);
860 }
861
862 i_s_xtradb_read_view_t*
read_fill_i_s_xtradb_read_view(i_s_xtradb_read_view_t * rv)863 read_fill_i_s_xtradb_read_view(i_s_xtradb_read_view_t* rv)
864 {
865 ReadView* view;
866
867 mutex_enter(&trx_sys->mutex);
868
869 view = trx_sys->mvcc->get_oldest_view();
870 if (!view) {
871 mutex_exit(&trx_sys->mutex);
872 return NULL;
873 }
874
875 rv->low_limit_no = view->low_limit_no();
876 rv->up_limit_id = view->up_limit_id();
877 rv->low_limit_id = view->low_limit_id();
878
879 mutex_exit(&trx_sys->mutex);
880
881 return rv;
882 }
883