Skip to content

Commit 1dec14d

Browse files
Bug #22204260 PURGE TRIES TO ACCESS FREED PAGE FOR VIRTUAL INDEX
Analysis: ======== 1) Purge is trying to build "current image" of virtual columns from the current cluster index, even it is deleted since the old version will depend on it. 2) The old version took a short cut, build tuple out of this delete marked rec for all columns (including non-virtual columns). 3) The problem with old one is that the BLOB page can be freed, so building the tuple out of it will trigger error. Fix: ==== 1) No need to build tuple on non-virtual columns at all. 2) Build only virtual column, and it exist in undo log. 3) So there is no need to fetch the whole blob, only prefix of it (that can be fetched from undo_log). Reviewed-by: Jimmy Yang <jimmy.yang@oracle.com> Reviewed-by: Marko Makela <marko.makela@oracle.com> RB: 11442
1 parent 2b72526 commit 1dec14d

File tree

3 files changed

+215
-11
lines changed

3 files changed

+215
-11
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
set global innodb_purge_stop_now = 1;
2+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
3+
vchar char(2) as (substr(f3,2,2)) virtual,
4+
primary key(f1, f3(5)), index(vchar))engine=innodb;
5+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
6+
update t1 set f1=5 where f1=1;
7+
delete from t1 where f1=5;
8+
set global innodb_purge_run_now=1;
9+
set global innodb_fast_shutdown=0;
10+
# restart
11+
set global innodb_purge_stop_now = 1;
12+
drop table t1;
13+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
14+
vchar char(2) as (substr(f3,2,2)) virtual,
15+
primary key(f1, f3(5)), index(vchar, f3(2)))engine=innodb;
16+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
17+
update t1 set f1=5 where f1=1;
18+
delete from t1 where f1=5;
19+
set global innodb_purge_run_now=1;
20+
set global innodb_fast_shutdown=0;
21+
# restart
22+
set global innodb_purge_stop_now = 1;
23+
drop table t1;
24+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
25+
vchar blob as (f3) virtual,
26+
primary key(f1, f3(5)), index(vchar(3)))engine=innodb;
27+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
28+
update t1 set f1=5 where f1=1;
29+
delete from t1 where f1=5;
30+
set global innodb_purge_run_now=1;
31+
set global innodb_fast_shutdown=0;
32+
# restart
33+
drop table t1;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--source include/have_debug.inc
2+
--source include/have_innodb.inc
3+
4+
set global innodb_purge_stop_now = 1;
5+
6+
# Index on virtual column
7+
8+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
9+
vchar char(2) as (substr(f3,2,2)) virtual,
10+
primary key(f1, f3(5)), index(vchar))engine=innodb;
11+
12+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
13+
14+
update t1 set f1=5 where f1=1;
15+
delete from t1 where f1=5;
16+
17+
set global innodb_purge_run_now=1;
18+
set global innodb_fast_shutdown=0;
19+
--source include/restart_mysqld.inc
20+
set global innodb_purge_stop_now = 1;
21+
drop table t1;
22+
23+
# Index on virtual column and blob
24+
25+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
26+
vchar char(2) as (substr(f3,2,2)) virtual,
27+
primary key(f1, f3(5)), index(vchar, f3(2)))engine=innodb;
28+
29+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
30+
31+
update t1 set f1=5 where f1=1;
32+
delete from t1 where f1=5;
33+
34+
set global innodb_purge_run_now=1;
35+
set global innodb_fast_shutdown=0;
36+
--source include/restart_mysqld.inc
37+
set global innodb_purge_stop_now = 1;
38+
drop table t1;
39+
40+
# Index on virtual column of blob type
41+
42+
create table t1(f1 int not null, f2 blob not null, f3 blob not null,
43+
vchar blob as (f3) virtual,
44+
primary key(f1, f3(5)), index(vchar(3)))engine=innodb;
45+
46+
insert into t1(f1,f2,f3) values(1, repeat('a',8000), repeat('b', 9000));
47+
48+
update t1 set f1=5 where f1=1;
49+
delete from t1 where f1=5;
50+
51+
set global innodb_purge_run_now=1;
52+
set global innodb_fast_shutdown=0;
53+
--source include/restart_mysqld.inc
54+
drop table t1;

storage/innobase/row/row0vers.cc

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*****************************************************************************
22
3-
Copyright (c) 1997, 2015, Oracle and/or its affiliates. All Rights Reserved.
3+
Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved.
44
55
This program is free software; you can redistribute it and/or modify it under
66
the terms of the GNU General Public License as published by the Free Software
@@ -469,7 +469,7 @@ row_vers_non_vc_match(
469469

470470
/** build virtual column value from current cluster index record data
471471
@param[in,out] row the cluster index row in dtuple form
472-
@param[in] clust_indexcluster index
472+
@param[in] clust_indexclustered index
473473
@param[in] index the secondary index
474474
@param[in] heap heap used to build virtual dtuple */
475475
static
@@ -502,6 +502,115 @@ row_vers_build_clust_v_col(
502502
}
503503
}
504504

505+
/** Build latest virtual column data from undo log
506+
@param[in] in_purge whether this is the purge thread
507+
@param[in] rec clustered index record
508+
@param[in] clust_index clustered index
509+
@param[in,out] clust_offsets offsets on the clustered index record
510+
@param[in] index the secondary index
511+
@param[in] roll_ptr the rollback pointer for the purging record
512+
@param[in] trx_id trx id for the purging record
513+
@param[in,out] v_heap heap used to build vrow
514+
@param[out] v_row dtuple holding the virtual rows
515+
@param[in,out] mtr mtr holding the latch on rec */
516+
static
517+
void
518+
row_vers_build_cur_vrow_low(
519+
boolin_purge,
520+
const rec_t* rec,
521+
dict_index_t* clust_index,
522+
ulint* clust_offsets,
523+
dict_index_t* index,
524+
roll_ptr_troll_ptr,
525+
trx_id_ttrx_id,
526+
mem_heap_t* v_heap,
527+
const dtuple_t**vrow,
528+
mtr_t* mtr)
529+
{
530+
const rec_t* version;
531+
rec_t* prev_version;
532+
mem_heap_t* heap = NULL;
533+
ulint num_v = dict_table_get_n_v_cols(index->table);
534+
const dfield_t* field;
535+
ulint i;
536+
boolall_filled = false;
537+
538+
*vrow = dtuple_create_with_vcol(v_heap, 0, num_v);
539+
dtuple_init_v_fld(*vrow);
540+
541+
for (i = 0; i < num_v; i++) {
542+
dfield_get_type(dtuple_get_nth_v_field(*vrow, i))->mtype
543+
= DATA_MISSING;
544+
}
545+
546+
version = rec;
547+
548+
/* If this is called by purge thread, set TRX_UNDO_PREV_IN_PURGE
549+
bit to search the undo log until we hit the current undo log with
550+
roll_ptr */
551+
const ulint status = in_purge
552+
? TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE
553+
: TRX_UNDO_GET_OLD_V_VALUE;
554+
555+
while (!all_filled) {
556+
mem_heap_t* heap2 = heap;
557+
heap = mem_heap_create(1024);
558+
roll_ptr_tcur_roll_ptr = row_get_rec_roll_ptr(
559+
version, clust_index, clust_offsets);
560+
561+
trx_undo_prev_version_build(
562+
rec, mtr, version, clust_index, clust_offsets,
563+
heap, &prev_version, NULL, vrow, status);
564+
565+
if (heap2) {
566+
mem_heap_free(heap2);
567+
}
568+
569+
if (!prev_version) {
570+
/* Versions end here */
571+
break;
572+
}
573+
574+
clust_offsets = rec_get_offsets(prev_version, clust_index,
575+
NULL, ULINT_UNDEFINED, &heap);
576+
577+
ulint entry_len = dict_index_get_n_fields(index);
578+
579+
all_filled = true;
580+
581+
for (i = 0; i < entry_len; i++) {
582+
const dict_field_t* ind_field
583+
= dict_index_get_nth_field(index, i);
584+
const dict_col_t* col = ind_field->col;
585+
586+
if (!dict_col_is_virtual(col)) {
587+
continue;
588+
}
589+
590+
const dict_v_col_t* v_col
591+
= reinterpret_cast<const dict_v_col_t*>(col);
592+
field = dtuple_get_nth_v_field(*vrow, v_col->v_pos);
593+
594+
if (dfield_get_type(field)->mtype == DATA_MISSING) {
595+
all_filled = false;
596+
break;
597+
}
598+
599+
}
600+
601+
trx_id_trec_trx_id = row_get_rec_trx_id(
602+
prev_version, clust_index, clust_offsets);
603+
604+
if (rec_trx_id < trx_id || roll_ptr == cur_roll_ptr) {
605+
break;
606+
}
607+
608+
version = prev_version;
609+
}
610+
611+
mem_heap_free(heap);
612+
}
613+
505614
/** Check a virtual column value index secondary virtual index matches
506615
that of current cluster index record, which is recreated from information
507616
stored in undo log
@@ -584,6 +693,9 @@ row_vers_vc_matches_cluster(
584693
roll_ptr_tcur_roll_ptr = row_get_rec_roll_ptr(
585694
version, clust_index, clust_offsets);
586695

696+
ut_ad(cur_roll_ptr != 0);
697+
ut_ad(in_purge == (roll_ptr != 0));
698+
587699
trx_undo_prev_version_build(
588700
rec, mtr, version, clust_index, clust_offsets,
589701
heap, &prev_version, NULL, vrow, status);
@@ -696,27 +808,32 @@ row_vers_build_cur_vrow(
696808
mtr_t* mtr)
697809
{
698810
const dtuple_t* cur_vrow = NULL;
699-
row_ext_t* ext;
700-
701-
dtuple_t* row = row_build(ROW_COPY_POINTERS, clust_index,
702-
rec, *clust_offsets,
703-
NULL, NULL, NULL, &ext, heap);
704811

705812
roll_ptr_t t_roll_ptr = row_get_rec_roll_ptr(
706813
rec, clust_index, *clust_offsets);
707814

708815
/* if the row is newly inserted, then the virtual
709816
columns need to be computed */
710817
if (trx_undo_roll_ptr_is_insert(t_roll_ptr)) {
818+
819+
ut_ad(!rec_get_deleted_flag(rec, page_rec_is_comp(rec)));
820+
821+
/* This is a newly inserted record and cannot
822+
be deleted, So the externally stored field
823+
cannot be freed yet. */
824+
dtuple_t* row = row_build(ROW_COPY_POINTERS, clust_index,
825+
rec, *clust_offsets,
826+
NULL, NULL, NULL, NULL, heap);
827+
711828
row_vers_build_clust_v_col(
712829
row, clust_index, index, heap);
713830
cur_vrow = dtuple_copy(row, v_heap);
714831
dtuple_dup_v_fld(cur_vrow, v_heap);
715832
} else {
716-
row_vers_vc_matches_cluster(
717-
in_purge, rec, row, ext, clust_index, *clust_offsets,
718-
index, ientry, roll_ptr,
719-
trx_id, v_heap, &cur_vrow, mtr);
833+
/* Try to fetch virtual column data from undo log */
834+
row_vers_build_cur_vrow_low(
835+
in_purge, rec, clust_index, *clust_offsets,
836+
index, roll_ptr, trx_id, v_heap, &cur_vrow, mtr);
720837
}
721838

722839
*clust_offsets = rec_get_offsets(rec, clust_index, NULL,

0 commit comments

Comments
 (0)