read0read.c 14.7 KB
Newer Older
osku's avatar
osku committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/******************************************************
Cursor read

(c) 1997 Innobase Oy

Created 2/16/1997 Heikki Tuuri
*******************************************************/

#include "read0read.h"

#ifdef UNIV_NONINL
#include "read0read.ic"
#endif

#include "srv0srv.h"
#include "trx0sys.h"

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
/*
-------------------------------------------------------------------------------
FACT A: Cursor read view on a secondary index sees only committed versions
-------
of the records in the secondary index or those versions of rows created
by transaction which created a cursor before cursor was created even
if transaction which created the cursor has changed that clustered index page.

PROOF: We must show that read goes always to the clustered index record
to see that record is visible in the cursor read view. Consider e.g.
following table and SQL-clauses:

create table t1(a int not null, b int, primary key(a), index(b));
insert into t1 values (1,1),(2,2);
commit;

Now consider that we have a cursor for a query

select b from t1 where b >= 1;

This query will use secondary key on the table t1. Now after the first fetch
on this cursor if we do a update:

update t1 set b = 5 where b = 2;

Now second fetch of the cursor should not see record (2,5) instead it should
see record (2,2).

We also should show that if we have delete t1 where b = 5; we still
can see record (2,2).

When we access a secondary key record maximum transaction id is fetched
from this record and this trx_id is compared to up_limit_id in the view.
If trx_id in the record is greater or equal than up_limit_id in the view
cluster record is accessed.  Because trx_id of the creating
transaction is stored when this view was created to the list of
trx_ids not seen by this read view previous version of the
record is requested to be built. This is build using clustered record.
If the secondary key record is delete  marked it's corresponding
clustered record can be already be purged only if records
trx_id < low_limit_no. Purge can't remove any record deleted by a
transaction which was active when cursor was created. But, we still
may have a deleted secondary key record but no clustered record. But,
this is not a problem because this case is handled in
row_sel_get_clust_rec() function which is called
whenever we note that this read view does not see trx_id in the
record. Thus, we see correct version. Q. E. D.

-------------------------------------------------------------------------------
FACT B: Cursor read view on a clustered index sees only committed versions
-------
of the records in the clustered index or those versions of rows created
by transaction which created a cursor before cursor was created even
if transaction which created the cursor has changed that clustered index page.

PROOF:  Consider e.g.following table and SQL-clauses:

create table t1(a int not null, b int, primary key(a));
insert into t1 values (1),(2);
commit;

Now consider that we have a cursor for a query

select a from t1 where a >= 1;

This query will use clustered key on the table t1. Now after the first fetch
on this cursor if we do a update:

update t1 set a = 5 where a = 2;

Now second fetch of the cursor should not see record (5) instead it should
see record (2).

We also should show that if we have execute delete t1 where a = 5; after
the cursor is opened we still can see record (2).

When accessing clustered record we always check if this read view sees
trx_id stored to clustered record. By default we don't see any changes
if record trx_id >= low_limit_id i.e. change was made transaction
which started after transaction which created the cursor. If row
was changed by the future transaction a previous version of the
clustered record is created. Thus we see only committed version in
this case. We see all changes made by committed transactions i.e.
record trx_id < up_limit_id. In this case we don't need to do anything,
we already see correct version of the record. We don't see any changes
made by active transaction except creating transaction. We have stored
trx_id of creating transaction to list of trx_ids when this view was
created. Thus we can easily see if this record was changed by the
creating transaction. Because we already have clustered record we can
access roll_ptr. Using this roll_ptr we can fetch undo record.
We can now check that undo_no of the undo record is less than undo_no of the
trancaction which created a view when cursor was created. We see this
clustered record only in case when record undo_no is less than undo_no
in the view. If this is not true we build based on undo_rec previous
version of the record. This record is found because purge can't remove
records accessed by active transaction. Thus we see correct version. Q. E. D.
-------------------------------------------------------------------------------
FACT C: Purge does not remove any delete marked row that is visible
-------
to cursor view.

TODO: proof this

*/

osku's avatar
osku committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
/*************************************************************************
Creates a read view object. */
UNIV_INLINE
read_view_t*
read_view_create_low(
/*=================*/
				/* out, own: read view struct */
	ulint		n,	/* in: number of cells in the trx_ids array */
	mem_heap_t*	heap)	/* in: memory heap from which allocated */
{
	read_view_t*	view;

	view = mem_heap_alloc(heap, sizeof(read_view_t));

	view->n_trx_ids = n;
	view->trx_ids = mem_heap_alloc(heap, n * sizeof(dulint));

	return(view);
}

/*************************************************************************
Makes a copy of the oldest existing read view, with the exception that also
the creating trx of the oldest view is set as not visible in the 'copied'
view. Opens a new view if no views currently exist. The view must be closed
with ..._close. This is used in purge. */

read_view_t*
read_view_oldest_copy_or_open_new(
/*==============================*/
152 153 154 155 156
					/* out, own: read view struct */
	dulint		cr_trx_id,	/* in: trx_id of creating
					transaction, or (0, 0) used in purge*/
	mem_heap_t*	heap)		/* in: memory heap from which
					allocated */
osku's avatar
osku committed
157 158 159 160 161 162 163 164 165
{
	read_view_t*	old_view;
	read_view_t*	view_copy;
	ibool		needs_insert	= TRUE;
	ulint		insert_done	= 0;
	ulint		n;
	ulint		i;

	ut_ad(mutex_own(&kernel_mutex));
166

osku's avatar
osku committed
167 168 169 170
	old_view = UT_LIST_GET_LAST(trx_sys->view_list);

	if (old_view == NULL) {

171
		return(read_view_open_now(cr_trx_id, heap));
osku's avatar
osku committed
172 173 174 175
	}

	n = old_view->n_trx_ids;

176
	if (!ut_dulint_is_zero(old_view->creator_trx_id)) {
osku's avatar
osku committed
177 178 179 180 181 182
		n++;
	} else {
		needs_insert = FALSE;
	}

	view_copy = read_view_create_low(n, heap);
183

osku's avatar
osku committed
184 185 186 187 188 189
	/* Insert the id of the creator in the right place of the descending
	array of ids, if needs_insert is TRUE: */

	i = 0;
	while (i < n) {
		if (needs_insert
190 191 192 193
		    && (i >= old_view->n_trx_ids
			|| ut_dulint_cmp(old_view->creator_trx_id,
					 read_view_get_nth_trx_id(old_view, i))
			> 0)) {
osku's avatar
osku committed
194 195

			read_view_set_nth_trx_id(view_copy, i,
196
						 old_view->creator_trx_id);
osku's avatar
osku committed
197 198 199 200
			needs_insert = FALSE;
			insert_done = 1;
		} else {
			read_view_set_nth_trx_id(view_copy, i,
201 202 203
						 read_view_get_nth_trx_id(
							 old_view,
							 i - insert_done));
osku's avatar
osku committed
204 205 206 207 208
		}

		i++;
	}

209
	view_copy->creator_trx_id = cr_trx_id;
210 211

	view_copy->low_limit_no = old_view->low_limit_no;
osku's avatar
osku committed
212 213 214 215 216
	view_copy->low_limit_id = old_view->low_limit_id;


	if (n > 0) {
		/* The last active transaction has the smallest id: */
217 218
		view_copy->up_limit_id = read_view_get_nth_trx_id(
			view_copy, n - 1);
osku's avatar
osku committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
	} else {
		view_copy->up_limit_id = old_view->up_limit_id;
	}

	UT_LIST_ADD_LAST(view_list, trx_sys->view_list, view_copy);

	return(view_copy);
}

/*************************************************************************
Opens a read view where exactly the transactions serialized before this
point in time are seen in the view. */

read_view_t*
read_view_open_now(
/*===============*/
235 236 237 238 239 240
					/* out, own: read view struct */
	dulint		cr_trx_id,	/* in: trx_id of creating
					transaction, or (0, 0) used in
					purge */
	mem_heap_t*	heap)		/* in: memory heap from which
					allocated */
osku's avatar
osku committed
241 242 243 244
{
	read_view_t*	view;
	trx_t*		trx;
	ulint		n;
245

osku's avatar
osku committed
246
	ut_ad(mutex_own(&kernel_mutex));
247

osku's avatar
osku committed
248 249
	view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);

250 251
	view->creator_trx_id = cr_trx_id;
	view->type = VIEW_NORMAL;
252
	view->undo_no = ut_dulint_zero;
osku's avatar
osku committed
253 254 255

	/* No future transactions should be visible in the view */

256
	view->low_limit_no = trx_sys->max_trx_id;
osku's avatar
osku committed
257 258 259 260 261 262 263 264
	view->low_limit_id = view->low_limit_no;

	n = 0;
	trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

	/* No active transaction should be visible, except cr_trx */

	while (trx) {
265
		if (ut_dulint_cmp(trx->id, cr_trx_id) != 0
266 267
		    && (trx->conc_state == TRX_ACTIVE
			|| trx->conc_state == TRX_PREPARED)) {
osku's avatar
osku committed
268 269 270 271 272 273 274 275 276 277 278 279 280 281

			read_view_set_nth_trx_id(view, n, trx->id);

			n++;

			/* NOTE that a transaction whose trx number is <
			trx_sys->max_trx_id can still be active, if it is
			in the middle of its commit! Note that when a
			transaction starts, we initialize trx->no to
			ut_dulint_max. */

			if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) {

				view->low_limit_no = trx->no;
282
			}
osku's avatar
osku committed
283 284 285 286 287
		}

		trx = UT_LIST_GET_NEXT(trx_list, trx);
	}

288
	view->n_trx_ids = n;
osku's avatar
osku committed
289 290 291 292 293 294 295 296

	if (n > 0) {
		/* The last active transaction has the smallest id: */
		view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
	} else {
		view->up_limit_id = view->low_limit_id;
	}

297

osku's avatar
osku committed
298
	UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
299

osku's avatar
osku committed
300 301 302 303 304 305 306 307 308 309 310 311
	return(view);
}

/*************************************************************************
Closes a read view. */

void
read_view_close(
/*============*/
	read_view_t*	view)	/* in: read view */
{
	ut_ad(mutex_own(&kernel_mutex));
312

osku's avatar
osku committed
313
	UT_LIST_REMOVE(view_list, trx_sys->view_list, view);
314
}
osku's avatar
osku committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337

/*************************************************************************
Closes a consistent read view for MySQL. This function is called at an SQL
statement end if the trx isolation level is <= TRX_ISO_READ_COMMITTED. */

void
read_view_close_for_mysql(
/*======================*/
	trx_t*	trx)	/* in: trx which has a read view */
{
	ut_a(trx->global_read_view);

	mutex_enter(&kernel_mutex);

	read_view_close(trx->global_read_view);

	mem_heap_empty(trx->global_read_view_heap);

	trx->read_view = NULL;
	trx->global_read_view = NULL;

	mutex_exit(&kernel_mutex);
}
338

osku's avatar
osku committed
339 340 341 342 343 344 345 346 347 348
/*************************************************************************
Prints a read view to stderr. */

void
read_view_print(
/*============*/
	read_view_t*	view)	/* in: read view */
{
	ulint	n_ids;
	ulint	i;
349

350 351 352 353 354 355 356 357 358
	if (view->type == VIEW_HIGH_GRANULARITY) {
		fprintf(stderr,
			"High-granularity read view undo_n:o %lu %lu\n",
			(ulong) ut_dulint_get_high(view->undo_no),
			(ulong) ut_dulint_get_low(view->undo_no));
	} else {
		fprintf(stderr, "Normal read view\n");
	}

osku's avatar
osku committed
359
	fprintf(stderr, "Read view low limit trx n:o %lu %lu\n",
360 361
		(ulong) ut_dulint_get_high(view->low_limit_no),
		(ulong) ut_dulint_get_low(view->low_limit_no));
osku's avatar
osku committed
362

vasil's avatar
vasil committed
363 364
	fprintf(stderr, "Read view up limit trx id " TRX_ID_FMT "\n",
		TRX_ID_PREP_PRINTF(view->up_limit_id));
osku's avatar
osku committed
365

vasil's avatar
vasil committed
366 367
	fprintf(stderr, "Read view low limit trx id " TRX_ID_FMT "\n",
		TRX_ID_PREP_PRINTF(view->low_limit_id));
osku's avatar
osku committed
368 369 370 371 372 373

	fprintf(stderr, "Read view individually stored trx ids:\n");

	n_ids = view->n_trx_ids;

	for (i = 0; i < n_ids; i++) {
vasil's avatar
vasil committed
374 375
		fprintf(stderr, "Read view trx id " TRX_ID_FMT "\n",
			TRX_ID_PREP_PRINTF(
376
				read_view_get_nth_trx_id(view, i)));
osku's avatar
osku committed
377 378 379 380
	}
}

/*************************************************************************
381 382 383 384
Create a high-granularity consistent cursor view for mysql to be used
in cursors. In this consistent read view modifications done by the
creating transaction after the cursor is created or future transactions
are not visible. */
osku's avatar
osku committed
385 386 387 388 389 390 391 392 393 394 395 396 397 398

cursor_view_t*
read_cursor_view_create_for_mysql(
/*==============================*/
	trx_t*	cr_trx)	/* in: trx where cursor view is created */
{
	cursor_view_t*	curview;
	read_view_t*	view;
	mem_heap_t*	heap;
	trx_t*		trx;
	ulint		n;

	ut_a(cr_trx);

399
	/* Use larger heap than in trx_create when creating a read_view
osku's avatar
osku committed
400 401 402 403 404 405 406
	because cursors are quite long. */

	heap = mem_heap_create(512);

	curview = (cursor_view_t*) mem_heap_alloc(heap, sizeof(cursor_view_t));
	curview->heap = heap;

407
	/* Drop cursor tables from consideration when evaluating the need of
408
	auto-commit */
osku's avatar
osku committed
409 410 411 412 413
	curview->n_mysql_tables_in_use = cr_trx->n_mysql_tables_in_use;
	cr_trx->n_mysql_tables_in_use = 0;

	mutex_enter(&kernel_mutex);

414 415
	curview->read_view = read_view_create_low(
		UT_LIST_GET_LEN(trx_sys->trx_list), curview->heap);
osku's avatar
osku committed
416 417

	view = curview->read_view;
418 419 420
	view->creator_trx_id = cr_trx->id;
	view->type = VIEW_HIGH_GRANULARITY;
	view->undo_no = cr_trx->undo_no;
osku's avatar
osku committed
421 422 423

	/* No future transactions should be visible in the view */

424
	view->low_limit_no = trx_sys->max_trx_id;
osku's avatar
osku committed
425 426 427 428 429
	view->low_limit_id = view->low_limit_no;

	n = 0;
	trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

430
	/* No active transaction should be visible */
osku's avatar
osku committed
431 432

	while (trx) {
433 434

		if (trx->conc_state == TRX_ACTIVE
435
		    || trx->conc_state == TRX_PREPARED) {
osku's avatar
osku committed
436 437 438 439 440 441 442 443 444 445 446 447 448 449

			read_view_set_nth_trx_id(view, n, trx->id);

			n++;

			/* NOTE that a transaction whose trx number is <
			trx_sys->max_trx_id can still be active, if it is
			in the middle of its commit! Note that when a
			transaction starts, we initialize trx->no to
			ut_dulint_max. */

			if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) {

				view->low_limit_no = trx->no;
450
			}
osku's avatar
osku committed
451 452 453 454 455
		}

		trx = UT_LIST_GET_NEXT(trx_list, trx);
	}

456
	view->n_trx_ids = n;
osku's avatar
osku committed
457 458 459 460 461 462 463 464 465

	if (n > 0) {
		/* The last active transaction has the smallest id: */
		view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
	} else {
		view->up_limit_id = view->low_limit_id;
	}

	UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
466

osku's avatar
osku committed
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
	mutex_exit(&kernel_mutex);

	return(curview);
}

/*************************************************************************
Close a given consistent cursor view for mysql and restore global read view
back to a transaction read view. */

void
read_cursor_view_close_for_mysql(
/*=============================*/
	trx_t*		trx,	/* in: trx */
	cursor_view_t*	curview)/* in: cursor view to be closed */
{
	ut_a(curview);
483
	ut_a(curview->read_view);
osku's avatar
osku committed
484 485
	ut_a(curview->heap);

486
	/* Add cursor's tables to the global count of active tables that
487
	belong to this transaction */
osku's avatar
osku committed
488 489 490 491 492 493 494 495 496 497 498
	trx->n_mysql_tables_in_use += curview->n_mysql_tables_in_use;

	mutex_enter(&kernel_mutex);

	read_view_close(curview->read_view);
	trx->read_view = trx->global_read_view;

	mutex_exit(&kernel_mutex);

	mem_heap_free(curview->heap);
}
499

osku's avatar
osku committed
500 501 502 503 504
/*************************************************************************
This function sets a given consistent cursor view to a transaction
read view if given consistent cursor view is not NULL. Otherwise, function
restores a global read view to a transaction read view. */

505
void
osku's avatar
osku committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
read_cursor_set_for_mysql(
/*======================*/
	trx_t*		trx,	/* in: transaction where cursor is set */
	cursor_view_t*	curview)/* in: consistent cursor view to be set */
{
	ut_a(trx);

	mutex_enter(&kernel_mutex);

	if (UNIV_LIKELY(curview != NULL)) {
		trx->read_view = curview->read_view;
	} else {
		trx->read_view = trx->global_read_view;
	}

	mutex_exit(&kernel_mutex);
}