1
/******************************************************
6
Created 2/16/1997 Heikki Tuuri
7
*******************************************************/
12
#include "read0read.ic"
19
-------------------------------------------------------------------------------
20
FACT A: Cursor read view on a secondary index sees only committed versions
22
of the records in the secondary index or those versions of rows created
23
by transaction which created a cursor before cursor was created even
24
if transaction which created the cursor has changed that clustered index page.
26
PROOF: We must show that read goes always to the clustered index record
27
to see that record is visible in the cursor read view. Consider e.g.
28
following table and SQL-clauses:
30
create table t1(a int not null, b int, primary key(a), index(b));
31
insert into t1 values (1,1),(2,2);
34
Now consider that we have a cursor for a query
36
select b from t1 where b >= 1;
38
This query will use secondary key on the table t1. Now after the first fetch
39
on this cursor if we do a update:
41
update t1 set b = 5 where b = 2;
43
Now second fetch of the cursor should not see record (2,5) instead it should
46
We also should show that if we have delete t1 where b = 5; we still
49
When we access a secondary key record maximum transaction id is fetched
50
from this record and this trx_id is compared to up_limit_id in the view.
51
If trx_id in the record is greater or equal than up_limit_id in the view
52
cluster record is accessed. Because trx_id of the creating
53
transaction is stored when this view was created to the list of
54
trx_ids not seen by this read view previous version of the
55
record is requested to be built. This is build using clustered record.
56
If the secondary key record is delete marked it's corresponding
57
clustered record can be already be purged only if records
58
trx_id < low_limit_no. Purge can't remove any record deleted by a
59
transaction which was active when cursor was created. But, we still
60
may have a deleted secondary key record but no clustered record. But,
61
this is not a problem because this case is handled in
62
row_sel_get_clust_rec() function which is called
63
whenever we note that this read view does not see trx_id in the
64
record. Thus, we see correct version. Q. E. D.
66
-------------------------------------------------------------------------------
67
FACT B: Cursor read view on a clustered index sees only committed versions
69
of the records in the clustered index or those versions of rows created
70
by transaction which created a cursor before cursor was created even
71
if transaction which created the cursor has changed that clustered index page.
73
PROOF: Consider e.g.following table and SQL-clauses:
75
create table t1(a int not null, b int, primary key(a));
76
insert into t1 values (1),(2);
79
Now consider that we have a cursor for a query
81
select a from t1 where a >= 1;
83
This query will use clustered key on the table t1. Now after the first fetch
84
on this cursor if we do a update:
86
update t1 set a = 5 where a = 2;
88
Now second fetch of the cursor should not see record (5) instead it should
91
We also should show that if we have execute delete t1 where a = 5; after
92
the cursor is opened we still can see record (2).
94
When accessing clustered record we always check if this read view sees
95
trx_id stored to clustered record. By default we don't see any changes
96
if record trx_id >= low_limit_id i.e. change was made transaction
97
which started after transaction which created the cursor. If row
98
was changed by the future transaction a previous version of the
99
clustered record is created. Thus we see only committed version in
100
this case. We see all changes made by committed transactions i.e.
101
record trx_id < up_limit_id. In this case we don't need to do anything,
102
we already see correct version of the record. We don't see any changes
103
made by active transaction except creating transaction. We have stored
104
trx_id of creating transaction to list of trx_ids when this view was
105
created. Thus we can easily see if this record was changed by the
106
creating transaction. Because we already have clustered record we can
107
access roll_ptr. Using this roll_ptr we can fetch undo record.
108
We can now check that undo_no of the undo record is less than undo_no of the
109
trancaction which created a view when cursor was created. We see this
110
clustered record only in case when record undo_no is less than undo_no
111
in the view. If this is not true we build based on undo_rec previous
112
version of the record. This record is found because purge can't remove
113
records accessed by active transaction. Thus we see correct version. Q. E. D.
114
-------------------------------------------------------------------------------
115
FACT C: Purge does not remove any delete marked row that is visible
123
/*************************************************************************
124
Creates a read view object. */
127
read_view_create_low(
128
/*=================*/
129
/* out, own: read view struct */
130
ulint n, /* in: number of cells in the trx_ids array */
131
mem_heap_t* heap) /* in: memory heap from which allocated */
135
view = mem_heap_alloc(heap, sizeof(read_view_t));
138
view->trx_ids = mem_heap_alloc(heap, n * sizeof(dulint));
143
/*************************************************************************
144
Makes a copy of the oldest existing read view, with the exception that also
145
the creating trx of the oldest view is set as not visible in the 'copied'
146
view. Opens a new view if no views currently exist. The view must be closed
147
with ..._close. This is used in purge. */
150
read_view_oldest_copy_or_open_new(
151
/*==============================*/
152
/* out, own: read view struct */
153
dulint cr_trx_id, /* in: trx_id of creating
154
transaction, or (0, 0) used in purge*/
155
mem_heap_t* heap) /* in: memory heap from which
158
read_view_t* old_view;
159
read_view_t* view_copy;
160
ibool needs_insert = TRUE;
161
ulint insert_done = 0;
165
ut_ad(mutex_own(&kernel_mutex));
167
old_view = UT_LIST_GET_LAST(trx_sys->view_list);
169
if (old_view == NULL) {
171
return(read_view_open_now(cr_trx_id, heap));
174
n = old_view->n_trx_ids;
176
if (ut_dulint_cmp(old_view->creator_trx_id,
177
ut_dulint_create(0,0)) != 0) {
180
needs_insert = FALSE;
183
view_copy = read_view_create_low(n, heap);
185
/* Insert the id of the creator in the right place of the descending
186
array of ids, if needs_insert is TRUE: */
191
&& (i >= old_view->n_trx_ids
192
|| ut_dulint_cmp(old_view->creator_trx_id,
193
read_view_get_nth_trx_id(old_view, i))
196
read_view_set_nth_trx_id(view_copy, i,
197
old_view->creator_trx_id);
198
needs_insert = FALSE;
201
read_view_set_nth_trx_id(view_copy, i,
202
read_view_get_nth_trx_id(
210
view_copy->creator_trx_id = cr_trx_id;
212
view_copy->low_limit_no = old_view->low_limit_no;
213
view_copy->low_limit_id = old_view->low_limit_id;
215
view_copy->can_be_too_old = FALSE;
218
/* The last active transaction has the smallest id: */
219
view_copy->up_limit_id = read_view_get_nth_trx_id(
222
view_copy->up_limit_id = old_view->up_limit_id;
225
UT_LIST_ADD_LAST(view_list, trx_sys->view_list, view_copy);
230
/*************************************************************************
231
Opens a read view where exactly the transactions serialized before this
232
point in time are seen in the view. */
237
/* out, own: read view struct */
238
dulint cr_trx_id, /* in: trx_id of creating
239
transaction, or (0, 0) used in
241
mem_heap_t* heap) /* in: memory heap from which
248
ut_ad(mutex_own(&kernel_mutex));
250
view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);
252
view->creator_trx_id = cr_trx_id;
253
view->type = VIEW_NORMAL;
254
view->undo_no = ut_dulint_create(0, 0);
256
/* No future transactions should be visible in the view */
258
view->low_limit_no = trx_sys->max_trx_id;
259
view->low_limit_id = view->low_limit_no;
261
view->can_be_too_old = FALSE;
264
trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
266
/* No active transaction should be visible, except cr_trx */
269
if (ut_dulint_cmp(trx->id, cr_trx_id) != 0
270
&& (trx->conc_state == TRX_ACTIVE
271
|| trx->conc_state == TRX_PREPARED)) {
273
read_view_set_nth_trx_id(view, n, trx->id);
277
/* NOTE that a transaction whose trx number is <
278
trx_sys->max_trx_id can still be active, if it is
279
in the middle of its commit! Note that when a
280
transaction starts, we initialize trx->no to
283
if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) {
285
view->low_limit_no = trx->no;
289
trx = UT_LIST_GET_NEXT(trx_list, trx);
295
/* The last active transaction has the smallest id: */
296
view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
298
view->up_limit_id = view->low_limit_id;
302
UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
307
/*************************************************************************
308
Closes a read view. */
313
read_view_t* view) /* in: read view */
315
ut_ad(mutex_own(&kernel_mutex));
317
UT_LIST_REMOVE(view_list, trx_sys->view_list, view);
320
/*************************************************************************
321
Closes a consistent read view for MySQL. This function is called at an SQL
322
statement end if the trx isolation level is <= TRX_ISO_READ_COMMITTED. */
325
read_view_close_for_mysql(
326
/*======================*/
327
trx_t* trx) /* in: trx which has a read view */
329
ut_a(trx->global_read_view);
331
mutex_enter(&kernel_mutex);
333
read_view_close(trx->global_read_view);
335
mem_heap_empty(trx->global_read_view_heap);
337
trx->read_view = NULL;
338
trx->global_read_view = NULL;
340
mutex_exit(&kernel_mutex);
343
/*************************************************************************
344
Prints a read view to stderr. */
349
read_view_t* view) /* in: read view */
354
if (view->type == VIEW_HIGH_GRANULARITY) {
356
"High-granularity read view undo_n:o %lu %lu\n",
357
(ulong) ut_dulint_get_high(view->undo_no),
358
(ulong) ut_dulint_get_low(view->undo_no));
360
fprintf(stderr, "Normal read view\n");
363
fprintf(stderr, "Read view low limit trx n:o %lu %lu\n",
364
(ulong) ut_dulint_get_high(view->low_limit_no),
365
(ulong) ut_dulint_get_low(view->low_limit_no));
367
fprintf(stderr, "Read view up limit trx id %lu %lu\n",
368
(ulong) ut_dulint_get_high(view->up_limit_id),
369
(ulong) ut_dulint_get_low(view->up_limit_id));
371
fprintf(stderr, "Read view low limit trx id %lu %lu\n",
372
(ulong) ut_dulint_get_high(view->low_limit_id),
373
(ulong) ut_dulint_get_low(view->low_limit_id));
375
fprintf(stderr, "Read view individually stored trx ids:\n");
377
n_ids = view->n_trx_ids;
379
for (i = 0; i < n_ids; i++) {
380
fprintf(stderr, "Read view trx id %lu %lu\n",
381
(ulong) ut_dulint_get_high(
382
read_view_get_nth_trx_id(view, i)),
383
(ulong) ut_dulint_get_low(
384
read_view_get_nth_trx_id(view, i)));
388
/*************************************************************************
389
Create a high-granularity consistent cursor view for mysql to be used
390
in cursors. In this consistent read view modifications done by the
391
creating transaction after the cursor is created or future transactions
395
read_cursor_view_create_for_mysql(
396
/*==============================*/
397
trx_t* cr_trx) /* in: trx where cursor view is created */
399
cursor_view_t* curview;
407
/* Use larger heap than in trx_create when creating a read_view
408
because cursors are quite long. */
410
heap = mem_heap_create(512);
412
curview = (cursor_view_t*) mem_heap_alloc(heap, sizeof(cursor_view_t));
413
curview->heap = heap;
415
/* Drop cursor tables from consideration when evaluating the need of
417
curview->n_mysql_tables_in_use = cr_trx->n_mysql_tables_in_use;
418
cr_trx->n_mysql_tables_in_use = 0;
420
mutex_enter(&kernel_mutex);
422
curview->read_view = read_view_create_low(
423
UT_LIST_GET_LEN(trx_sys->trx_list), curview->heap);
425
view = curview->read_view;
426
view->creator_trx_id = cr_trx->id;
427
view->type = VIEW_HIGH_GRANULARITY;
428
view->undo_no = cr_trx->undo_no;
430
/* No future transactions should be visible in the view */
432
view->low_limit_no = trx_sys->max_trx_id;
433
view->low_limit_id = view->low_limit_no;
435
view->can_be_too_old = FALSE;
438
trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
440
/* No active transaction should be visible */
444
if (trx->conc_state == TRX_ACTIVE
445
|| trx->conc_state == TRX_PREPARED) {
447
read_view_set_nth_trx_id(view, n, trx->id);
451
/* NOTE that a transaction whose trx number is <
452
trx_sys->max_trx_id can still be active, if it is
453
in the middle of its commit! Note that when a
454
transaction starts, we initialize trx->no to
457
if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) {
459
view->low_limit_no = trx->no;
463
trx = UT_LIST_GET_NEXT(trx_list, trx);
469
/* The last active transaction has the smallest id: */
470
view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
472
view->up_limit_id = view->low_limit_id;
475
UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
477
mutex_exit(&kernel_mutex);
482
/*************************************************************************
483
Close a given consistent cursor view for mysql and restore global read view
484
back to a transaction read view. */
487
read_cursor_view_close_for_mysql(
488
/*=============================*/
489
trx_t* trx, /* in: trx */
490
cursor_view_t* curview)/* in: cursor view to be closed */
493
ut_a(curview->read_view);
496
/* Add cursor's tables to the global count of active tables that
497
belong to this transaction */
498
trx->n_mysql_tables_in_use += curview->n_mysql_tables_in_use;
500
mutex_enter(&kernel_mutex);
502
read_view_close(curview->read_view);
503
trx->read_view = trx->global_read_view;
505
mutex_exit(&kernel_mutex);
507
mem_heap_free(curview->heap);
510
/*************************************************************************
511
This function sets a given consistent cursor view to a transaction
512
read view if given consistent cursor view is not NULL. Otherwise, function
513
restores a global read view to a transaction read view. */
516
read_cursor_set_for_mysql(
517
/*======================*/
518
trx_t* trx, /* in: transaction where cursor is set */
519
cursor_view_t* curview)/* in: consistent cursor view to be set */
523
mutex_enter(&kernel_mutex);
525
if (UNIV_LIKELY(curview != NULL)) {
526
trx->read_view = curview->read_view;
528
trx->read_view = trx->global_read_view;
531
mutex_exit(&kernel_mutex);