1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-17598: Store DB_TRX_ID in secondary index leaf records

Secondary index records carry no transaction information,
so implicit lock checks, consistent reads and purge must look up
the clustered index and the undo log, even for a covering read.

Add an optional index format (DICT_SEC_TRX_ID, SYS_INDEXES.TYPE bit 4)
whose leaf records end with a 6-byte DB_TRX_ID, so that these checks
can be answered from the record.

dict_index_build_internal_non_clust(): Appends DB_TRX_ID after
n_uniq is computed;

dict_load_indexes(): Accepts the new bit;

dict_index_get_n_unique_in_tree() and cmp_rec_rec_simple(): Exclude the
field from comparison and node pointers.

Add dict_index_t::has_trx_id(), sec_db_trx_id(), rec_get_sec_trx_id()
and rec_set_sec_trx_id().

row_build_index_entry_low(): adds a zeroed field for DB_TRX_ID sec index
entry

btr_cur_optimistic_insert(), btr_cur_pessimistic_insert() : stamp it
via btr_cur_write_sec_trx_id();

Delete-marking uses the new btr_rec_set_deleted_sec(), which restamps
in the same mini-transaction.

row_ins_sec_index_entry_by_modify() and row_upd_add_sec_trx_id() restamp
when reviving a delete-marked record, and row0umod.cc restamps on
rollback.

row_upd_build_sec_rec_difference_binary() can skip the field.

lock_sec_rec_some_has_impl() uses lock_sec_rec_some_has_impl_trx_id()

lock_rec_unlock_unmodified() compares the record's id.

row_search_mvcc() and Row_sel_get_clust_rec_for_mysql skip the clustered
lookup when the stamp is visible in the read view;

row_purge_poss_sec() removes a delete-marked record whose stamp is purgeable.
This commit is contained in:
Thirunarayanan Balathandayuthapani
2026-10-19 10:40:18 +05:30
parent 327437ade1
commit b302a2ecf6
17 changed files with 500 additions and 35 deletions

View File

@@ -2262,6 +2262,26 @@ btr_cur_ins_lock_and_undo(
return DB_SUCCESS;
}
/** Add the DB_TRX_ID of a secondary index leaf entry that is
about to be inserted
@param flags undo logging and locking flags
@param index secondary index
@param leaf Whether the entry goes to leaf page
@param entry entry to insert
@param mtr mini-transaction */
static void btr_cur_insert_sec_entry_trx(ulint flags, const dict_index_t *index,
bool leaf, dtuple_t *entry,
const mtr_t *mtr)
{
if (!leaf || !index->has_trx_id() ||
(flags & (BTR_KEEP_SYS_FLAG | BTR_CREATE_FLAG)))
return;
ut_ad(mtr->trx);
ut_ad(mtr->trx->id);
btr_cur_write_sec_trx_id(entry, index, mtr->trx->id);
}
/**
Prefetch siblings of the leaf for the pessimistic operation.
@param block leaf page
@@ -2469,6 +2489,7 @@ fail_err:
goto fail_err;
}
btr_cur_insert_sec_entry_trx(flags, index, leaf, entry, mtr);
#ifdef UNIV_DEBUG
if (!(flags & BTR_CREATE_FLAG)
&& leaf && index->is_primary()) {
@@ -2609,6 +2630,10 @@ btr_cur_pessimistic_insert(
return(err);
}
btr_cur_insert_sec_entry_trx(flags, index,
page_is_leaf(btr_cur_get_page(cursor)),
entry, mtr);
/* First reserve enough free space for the file segments of
the index tree, so that the insert will not fail because of
lack of space */
@@ -2805,6 +2830,18 @@ static void btr_cur_write_sys(
trx_write_roll_ptr(static_cast<byte*>(r->data), roll_ptr);
}
void btr_cur_write_sec_trx_id(dtuple_t *entry, const dict_index_t *index,
trx_id_t trx_id)
{
ut_ad(index->has_trx_id());
ut_ad(dtuple_get_n_fields(entry) == index->n_fields);
dfield_t *f= dtuple_get_nth_field(entry, index->sec_db_trx_id());
ut_ad(f->len == DATA_TRX_ID_LEN);
ut_ad(f->data);
trx_write_trx_id(static_cast<byte*>(f->data), trx_id);
}
MY_ATTRIBUTE((warn_unused_result))
/** Update DB_TRX_ID, DB_ROLL_PTR in a clustered index record.
@param[in,out] block clustered index leaf page
@@ -4259,6 +4296,46 @@ void btr_rec_set_deleted(buf_block_t *block, rec_t *rec, mtr_t *mtr)
template void btr_rec_set_deleted<false>(buf_block_t *, rec_t *, mtr_t *);
template void btr_rec_set_deleted<true>(buf_block_t *, rec_t *, mtr_t *);
template<bool flag>
void btr_rec_set_deleted_sec(buf_block_t *block, rec_t *rec,
const dict_index_t *index, mtr_t *mtr)
{
if (!index->has_trx_id())
{
btr_rec_set_deleted<flag>(block, rec, mtr);
return;
}
ut_ad(page_is_comp(block->page.frame));
ut_ad(!block->page.zip.data);
ut_ad(page_rec_is_leaf(rec));
ut_ad(mtr->trx);
ut_ad(mtr->trx->id);
mem_heap_t *heap= nullptr;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs_init(offsets_);
const rec_offs *offsets=
rec_get_offsets(rec, index, offsets_, index->n_core_fields,
index->sec_db_trx_id() + 1, &heap);
ulint len;
byte *p=
const_cast<byte*>(rec_get_nth_field(rec, offsets,
index->sec_db_trx_id(), &len));
ut_ad(len == DATA_TRX_ID_LEN);
byte id[DATA_TRX_ID_LEN];
trx_write_trx_id(id, mtr->trx->id);
mtr->memcpy<mtr_t::MAYBE_NOP>(*block, p, id, DATA_TRX_ID_LEN);
btr_rec_set_deleted<flag>(block, rec, mtr);
if (UNIV_LIKELY_NULL(heap))
mem_heap_free(heap);
}
template void btr_rec_set_deleted_sec<false>(buf_block_t *, rec_t *,
const dict_index_t *, mtr_t *);
template void btr_rec_set_deleted_sec<true>(buf_block_t *, rec_t *,
const dict_index_t *, mtr_t *);
/***********************************************************//**
Marks a clustered index record deleted. Writes an undo log record to
undo log on this delete marking. Writes in the trx id field the id

View File

@@ -2445,7 +2445,8 @@ dict_index_build_internal_non_clust(
/* Create a new index */
new_index = dict_mem_index_create(
index->table, index->name, index->type,
ulint(index->n_fields + 1 + clust_index->n_uniq));
ulint(index->n_fields + 1 + clust_index->n_uniq
+ !!(index->has_trx_id())));
/* Copy other relevant data from the old index
struct to the new struct: it inherits the values */
@@ -2500,6 +2501,14 @@ dict_index_build_internal_non_clust(
new_index->n_uniq = new_index->n_def;
}
if (new_index->type & DICT_SEC_TRX_ID) {
ut_ad(dict_table_is_comp(table));
dict_index_add_col(
new_index, table,
dict_table_get_sys_col(table, DATA_TRX_ID), 0,
false);
}
/* Set the n_fields value in new_index to the actual defined
number of fields */

View File

@@ -2048,7 +2048,8 @@ dberr_t dict_load_indexes(mtr_t *mtr, dict_table_t *table, bool uncommitted,
subsequent checks are relevant for the supported types. */
if (index->type & ~(DICT_CLUSTERED | DICT_UNIQUE
| DICT_CORRUPT | DICT_FTS
| DICT_SPATIAL | DICT_VIRTUAL)) {
| DICT_SPATIAL | DICT_VIRTUAL
| DICT_SEC_TRX_ID)) {
ib::error() << "Unknown type " << index->type
<< " of index " << index->name

View File

@@ -794,6 +794,31 @@ template<bool flag>
void btr_rec_set_deleted(buf_block_t *block, rec_t *rec, mtr_t *mtr)
MY_ATTRIBUTE((nonnull));
/** Modify the delete-mark flag of a secondary index leaf record.
If the index has_trx_id(), also overwrite the DB_TRX_ID of the
record with the identifier of the transaction. For other indexes
this is btr_rec_set_deleted().
@tparam flag the value of the delete-mark flag
@param block buffer block
@param rec leaf page record
@param index secondary index
@param mtr mini-transaction */
template<bool flag>
void btr_rec_set_deleted_sec(buf_block_t *block, rec_t *rec,
const dict_index_t *index, mtr_t *mtr)
MY_ATTRIBUTE((nonnull));
/** Store DB_TRX_ID into the trailing field of a secondary index
entry that is about to be inserted or that is used to build a record.
@param entry leaf entry
@param index secondary index
@param trx_id transaction identifier */
void btr_cur_write_sec_trx_id(dtuple_t *entry, const dict_index_t *index,
trx_id_t trx_id)
MY_ATTRIBUTE((nonnull));
/** If pessimistic delete fails because of lack of file space, there
is still a good change of success a little later. Try this many
times. */

View File

@@ -707,7 +707,8 @@ dict_index_get_n_unique_in_tree(
return(dict_index_get_n_unique(index));
}
return(dict_index_get_n_fields(index));
return uint16_t(dict_index_get_n_fields(index)
- uint16_t(index->has_trx_id()));
}
/**

View File

@@ -66,6 +66,7 @@ combination of types */
auto-generated clustered indexes,
also DICT_UNIQUE will be set */
#define DICT_UNIQUE 2 /*!< unique index */
#define DICT_CORRUPT 16 /*!< bit to store the corrupted flag
in SYS_INDEXES.TYPE */
#define DICT_FTS 32 /* FTS index; can't be combined with the
@@ -74,6 +75,16 @@ combination of types */
other flags */
#define DICT_VIRTUAL 128 /* Index on Virtual column */
/* Secondary index whose leaf-page record end with 6 byte DB_TRX_ID
field. The field is an ordinary trailing field of
dict_index_t::fields[], it is not part of the ordering fields, and
it is never present in node pointer records. Persisted in
SYS_INDEXES.TYPE, so that a server that does not know about the
format refuses to load the index. Never set on DICT_CLUSTERED,
DICT_FTS or DICT_SPATIAL indexes.
TODO : Blocked for ROW_FORMAT=REDUNDANT and COMPRESSED TABLES */
#define DICT_SEC_TRX_ID 4
#define DICT_IT_BITS 8 /*!< number of bits used for
SYS_INDEXES.TYPE */
/* @} */
@@ -1419,6 +1430,19 @@ public:
/** @return whether the index includes virtual columns */
bool has_virtual() const { return type & DICT_VIRTUAL; }
/** @return leaf page records of this secondary index
end with DB_TRX_ID */
bool has_trx_id() const
{ return type & DICT_SEC_TRX_ID; }
/** @return the position of DB_TRX_ID in a secondary index */
uint16_t sec_db_trx_id() const {
DBUG_ASSERT(has_trx_id());
DBUG_ASSERT(!is_primary());
DBUG_ASSERT(n_fields > n_uniq);
return static_cast<uint16_t>(n_fields - 1);
}
/** @return the position of DB_TRX_ID */
uint16_t db_trx_id() const {
DBUG_ASSERT(is_primary());

View File

@@ -92,7 +92,8 @@ constexpr size_t REC_OFFS_NORMAL_SIZE= 300;
constexpr size_t REC_OFFS_SMALL_SIZE= 18;
constexpr size_t REC_OFFS_SEC_INDEX_SIZE=
/* PK max key parts */ 16 + /* sec idx max key parts */ 16 +
/* child page number for non-leaf pages */ 1;
/* child page number for non-leaf pages */ 1 +
/* DB_TRX_ID of a DB_SEC_TRX_ID index */ + 1;
/** Get the base address of offsets. The extra_size is stored at
this position, and following positions hold the end offsets of
@@ -689,6 +690,49 @@ inline ulint rec_offs_comp(const rec_offs *offsets)
return (*rec_offs_base(offsets) & REC_OFFS_COMPACT);
}
#ifndef UNIV_INNOCHECKSUM
/** Get the DB_TRX_ID of a leaf-page record of a secondary index
that has the DICT_SEC_TRX_ID format.
@param rec leaf-page record
@param index secondary index, has_trx_id()
@param offsets offsets of the secondary index record
@return the stored transaction identifier or 0 */
inline trx_id_t rec_get_sec_trx_id(const rec_t *rec, const dict_index_t *index,
const rec_offs *offsets)
{
ut_ad(index->has_trx_id());
ut_ad(rec_offs_validate(rec, index, offsets));
ut_ad(rec_offs_n_fields(offsets) == index->n_fields);
ulint len;
const byte *b= rec_get_nth_field(rec, offsets,
index->sec_db_trx_id(), &len);
ut_ad(len == DATA_TRX_ID_LEN);
return mach_read_from_6(b);
}
/** Set the DB_TRX_ID of a secondary index record in a memory buffer.
is being built).
@param rec record in a memory buffer
@param index secondary index, has_trx_id()
@param offsets rec_get_offsets(rec, index)
@param id the transaction identifier to store */
inline void rec_set_sec_trx_id(rec_t *rec, const dict_index_t *index,
const rec_offs *offsets, trx_id_t id)
{
ut_ad(index->has_trx_id());
ut_ad(rec_offs_validate(rec, index, offsets));
ut_ad(rec_offs_n_fields(offsets) == index->n_fields);
ut_ad(id <= 0xFFFFFFFFFFFFULL);
ulint len;
byte *b=
const_cast<byte*>(rec_get_nth_field(rec, offsets,
index->sec_db_trx_id(), &len));
ut_ad(len == DATA_TRX_ID_LEN);
mach_write_to_6(b, id);
}
#endif /* !UNIV_INNOCHECKSUM */
/** Determine if the record is the metadata pseudo-record
in the clustered index for instant ADD COLUMN or ALTER TABLE.
@param[in] rec leaf page record

View File

@@ -77,7 +77,7 @@ upd_field_set_field_no(
upd_field_t* upd_field, /*!< in: update vector field */
uint16_t field_no, /*!< in: field number in a clustered
index */
dict_index_t* index);
const dict_index_t* index); /*!< in: Secondary index */
/** set field number to a update vector field, marks this field is updated
@param[in,out] upd_field update vector field
@@ -131,8 +131,26 @@ row_upd_build_sec_rec_difference_binary(
dict_index_t* index, /*!< in: index */
const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */
const dtuple_t* entry, /*!< in: entry to insert */
mem_heap_t* heap) /*!< in: memory heap from which allocated */
mem_heap_t* heap, /*!< in: memory heap from which allocated */
bool skip_trx_id= false)
/*!< in: Don't compare the trailing
DB_TRX_ID of an index that has
DB_TRX_ID as part of it */
MY_ATTRIBUTE((warn_unused_result, nonnull));
/** Append the DB_TRX_ID of a secondary index to an update vector
that was built by row_upd_build_sec_rec_difference_binary() with
skip_trx_id=true. The update vector must have room for
one more field, which is guaranteed because that function
leaves the last slot free.
@param[in,out] update update vector
@param[in] index secondary index
@param[in] trx_id identifier of the modifying transaction
@param[in,out] heap memory heap for the 6-byte value */
void row_upd_add_sec_trx_id(upd_t *update, const dict_index_t *index,
trx_id_t trx_id, mem_heap_t *heap)
MY_ATTRIBUTE((nonnull));
/** Builds an update vector from those fields, excluding the roll ptr and
trx id fields, which in an index entry differ from a record that has
the equal ordering fields. NOTE: we compare the fields as binary strings!

View File

@@ -95,7 +95,7 @@ upd_field_set_field_no(
upd_field_t* upd_field, /*!< in: update vector field */
uint16_t field_no, /*!< in: field number in a clustered
index */
dict_index_t* index) /*!< in: index */
const dict_index_t* index) /*!< in: index */
{
upd_field->field_no = field_no;
upd_field->orig_len = 0;

View File

@@ -1271,6 +1271,53 @@ lock_rec_other_has_conflicting(unsigned mode, const hash_cell_t &cell,
return {nullptr, const_cast<lock_t *>(insert_after), ut_d(bypassed)};
}
/** Find the transaction that holds an implicit exclusive
lock on a record of a secondary index that has_trx_id()
A secondary index record of this format carries the identifier
of the transaction that last inserted, delete-marked or
un-delete-marked it.
That transaction holds an implicit lock on the record if and only
if it is still registered in trx_sys, that is,
active or in XA PREPARE state. Neither the clustered index nor the
undo log is accessed, and PAGE_MAX_TRX_ID is not consulted.
The caller must hold a latch on the page of rec, which keeps the
DB_TRX_ID and the delete-mark flag of the record stable.
@param caller_trx trx of current thread
@param rec user record on a latched leaf page
@param index secondary index, has_trx_id()
@param offsets rec_get_offsets(rec, index)
@return a referenced transaction or nullptr if there is
no implicit lock. The state of the transaction must
be rechecked after acquiring trx->mutex. */
static trx_t *lock_sec_rec_some_has_impl_trx_id(trx_t *caller_trx,
const rec_t *rec,
dict_index_t *index,
const rec_offs *offsets)
{
ut_ad(index->has_trx_id());
const trx_id_t id= rec_get_sec_trx_id(rec, index, offsets);
if (!id || id <= caller_trx->max_inactive_id)
return nullptr;
if (id == caller_trx->id)
{
/* Own modification. */
caller_trx->reference();
return caller_trx;
}
if (!lock_check_trx_id_sanity(id, rec, index, offsets))
return nullptr;
return trx_sys.find(caller_trx, id);
}
/*********************************************************************//**
Checks if some transaction has an implicit x-lock on a record in a secondary
index.
@@ -1295,6 +1342,10 @@ lock_sec_rec_some_has_impl(
ut_ad(rec_offs_validate(rec, index, offsets));
ut_ad(!rec_is_metadata(rec, *index));
if (index->has_trx_id())
return lock_sec_rec_some_has_impl_trx_id(caller_trx, rec, index,
offsets);
const trx_id_t max_trx_id= page_get_max_trx_id(page_align(rec));
/* Note: It is possible to have caller_trx->id == 0 in a locking read
@@ -4875,6 +4926,17 @@ bool lock_rec_unlock_unmodified(buf_block_t *block, hash_cell_t *&cell,
unlock_rec:
lock_rec_unlock(*cell, lock, i);
}
else if (index->has_trx_id())
{
mem_heap_t *heap= nullptr;
std::ignore= rec_get_offsets(rec, index, offsets,
index->n_core_fields,
ULINT_UNDEFINED, &heap);
ut_ad(!heap);
if (rec_get_sec_trx_id(rec, index, offsets) == lock->trx->id)
continue;
goto unlock_rec;
}
else
{
/* lock_sec_rec_some_has_impl() must be invoked with released lock_sys
@@ -5132,8 +5194,9 @@ skip_try:
#endif
mtr_t mtr{trx};
/* Reserve enough offsets for the key and PRIMARY KEY. */
rec_offs offsets_[REC_OFFS_HEADER_SIZE + 2 * MAX_REF_PARTS + 1];
/* Reserve enough offsets for the key and PRIMARY KEY and
DB_TRX_ID of an index */
rec_offs offsets_[REC_OFFS_HEADER_SIZE + 2 * MAX_REF_PARTS + 2];
rec_offs *offsets= offsets_;
rec_offs_init(offsets_);

View File

@@ -522,7 +522,9 @@ cmp_rec_rec_simple(
/* Else, keep comparing so that we have the full internal
order. */
for (; n < dict_index_get_n_fields(index); n++) {
for (const ulint n_order = dict_index_get_n_fields(index)
- ulint(index->has_trx_id());
n < n_order; n++) {
int cmp = cmp_rec_rec_simple_field(
rec1, rec2, offsets1, offsets2, index, n);

View File

@@ -205,8 +205,11 @@ row_ins_sec_index_entry_by_modify(
there are char fields in them. Therefore we have to calculate the
difference. */
bool has_trx_id= cursor->index()->has_trx_id();
update = row_upd_build_sec_rec_difference_binary(
rec, cursor->index(), *offsets, entry, heap);
rec, cursor->index(), *offsets, entry, heap,
has_trx_id);
if (!rec_get_deleted_flag(rec, rec_offs_comp(*offsets))) {
/* We should never insert in place of a record that
@@ -225,6 +228,11 @@ row_ins_sec_index_entry_by_modify(
? DB_CORRUPTION : DB_SUCCESS;
}
if (has_trx_id) {
row_upd_add_sec_trx_id(update, cursor->index(),
thr_get_trx(thr)->id, heap);
}
if (mode == BTR_MODIFY_LEAF) {
/* Try an optimistic updating of the record, keeping changes
within the page */
@@ -3533,6 +3541,14 @@ row_ins_index_entry_set_vals(const ins_node_t* node, que_thr_t* thr)
field = dtuple_get_nth_field(entry, i);
ind_field = dict_index_get_nth_field(index, i);
col = ind_field->col;
if (index->has_trx_id() &&
i == index->sec_db_trx_id()) {
/* DB_TRX_ID value will be stored
in btr_cur_optimisitic_insert()
or by row_upd_add_sec_trx_id() */
continue;
}
}
if (col->is_virtual()) {

View File

@@ -722,6 +722,54 @@ nochange_index:
}
}
/** Determines if it is possible to remove a secondary index record
of an index that has_trx_id(), from the record alone.
A delete-marked record whose DB_TRX_ID is visible in
purge_sys.view was delete-marked, or un-delete-marked and
delete-marked again, before every existing and
every future read view, so no reader can need it. A later
transaction that re-inserts the same key un-delete-marks
this very record and restamps it, so the record is then no longer
delete-marked, or its DB_TRX_ID is not yet purgeable.
@param node row purge node
@param index secondary index, has_trx_id()
@param rec the secondary index record, on a latched page
@return whether the record can be purged
@retval true if the record is delete-marked and purgeable,
(or) if it is not delete-marked although it carries
the DB_TRX_ID of the transaction that is being purged */
static bool row_purge_poss_sec_stamp(const purge_node_t *node,
const dict_index_t *index,
const rec_t *rec)
{
ut_ad(index->has_trx_id());
mem_heap_t *heap= nullptr;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs_init(offsets_);
const rec_offs *offsets=
rec_get_offsets(rec, index, offsets_, index->n_core_fields,
ULINT_UNDEFINED, &heap);
const trx_id_t id= rec_get_sec_trx_id(rec, index, offsets);
const bool deleted= rec_get_deleted_flag(rec, true);
if (UNIV_LIKELY_NULL(heap))
mem_heap_free(heap);
if (!deleted)
return id == node->trx_id;
if (id && UNIV_UNLIKELY(id >= trx_sys.get_max_trx_id()))
{
ib::warn() << "A transaction id in a record of index "
<< index->name << " of table " << index->table->name
<< " is newer than the system-wide maximum.";
return false;
}
return purge_sys.is_purgeable(id);
}
/** Determines if it is possible to remove a secondary index entry.
Removal is possible if the secondary index entry does not refer to any
not delete marked version of a clustered index record where DB_TRX_ID
@@ -738,13 +786,19 @@ page latch.
@param node row purge node
@param index secondary index
@param entry secondary index entry
@param rec secondary index record that would be removed
@param mtr mini-transaction for looking up clustered index
@return whether the secondary index record can be purged */
static bool row_purge_poss_sec(purge_node_t *node, dict_index_t *index,
const dtuple_t *entry, mtr_t *mtr)
const dtuple_t *entry,
const rec_t *rec, mtr_t *mtr)
{
ut_ad(mtr->trx == node->trx);
ut_ad(!index->is_clust());
if (index->has_trx_id())
return row_purge_poss_sec_stamp(node, index, rec);
const auto savepoint= mtr->get_savepoint();
bool can_delete= !row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, mtr);
@@ -833,7 +887,8 @@ static bool row_purge_remove_sec_if_poss_tree(purge_node_t *node,
if (page_max_trx_id
== page_get_max_trx_id(btr_cur_get_page(&pcur.btr_cur))
|| row_purge_poss_sec(node, index, entry, &mtr)) {
|| row_purge_poss_sec(node, index, entry,
btr_pcur_get_rec(&pcur), &mtr)) {
/* Remove the index record, which should have been
marked for deletion. */
@@ -917,11 +972,12 @@ static trx_id_t row_purge_remove_sec_if_poss_leaf(purge_node_t *node,
== DB_SUCCESS
&& !btr_pcur_is_before_first_on_page(&pcur)
&& btr_pcur_get_low_match(&pcur)
== dtuple_get_n_fields(entry)) {
== dtuple_get_n_fields_cmp(entry)) {
found:
/* Before attempting to purge a record, check
if it is safe to do so. */
if (row_purge_poss_sec(node, index, entry, &mtr)) {
if (row_purge_poss_sec(node, index, entry,
btr_pcur_get_rec(&pcur), &mtr)) {
/* Only delete-marked records should be purged. */
if (!rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
index->table

View File

@@ -231,7 +231,9 @@ row_build_index_entry_low(
i = 1;
}
for (; i < entry_len; i++) {
bool has_trx_id = index->has_trx_id();
const ulint n_data_fields = entry_len - ulint(!!has_trx_id);
for (; i < n_data_fields; i++) {
const dict_field_t& f = index->fields[i];
dfield_t* dfield = dtuple_get_nth_field(entry, i);
@@ -366,6 +368,17 @@ row_build_index_entry_low(
}
}
if (has_trx_id) {
ut_ad(!index->is_primary());
dfield_t* dfield = dtuple_get_nth_field(
entry, n_data_fields);
dict_col_copy_type(index->fields[n_data_fields].col,
&dfield->type);
dfield_set_data(dfield,
mem_heap_zalloc(heap, DATA_TRX_ID_LEN),
DATA_TRX_ID_LEN);
}
for (i = num_v; i--; ) {
ut_ad(index->is_primary());
ut_ad(flag == ROW_BUILD_FOR_INSERT);
@@ -1279,7 +1292,7 @@ row_search_index_entry(
}
return !btr_pcur_is_before_first_on_page(pcur)
&& btr_pcur_get_low_match(pcur) == dtuple_get_n_fields(entry);
&& btr_pcur_get_low_match(pcur) == dtuple_get_n_fields_cmp(entry);
}
/*******************************************************************//**

View File

@@ -980,6 +980,52 @@ static dberr_t row_sel_clust_sees(const rec_t *rec, const dict_index_t &index,
return DB_CORRUPTION;
}
/** Check whether the state of a secondary index leaf record,
as far as the record itself tells, is the state that a consistent
read view must see.
A record of an index that has_trx_id() carries the identifier
of the transaction that last inserted, delete-marked or
un-delete-marked it, and every such modification restamps the record.
If that transaction is visible in the view, no later transaction has
modified the record, so the record, including its delete-mark,
is exactly what the view sees. If the stamp is not visible, the
record alone cannot tell whether it existed in the view
(secondary index changes are not undo-logged): the caller must
look at the clustered index as before.
@param rec secondary index leaf record on a latched page
@param index secondary index, has_trx_id()
@param offsets rec_get_offsets(rec, index)
@param view consistent read view
@retval DB_SUCCESS if the view sees the record as it is
@retval DB_SUCCESS_LOCKED_REC if this cannot be decided from the rec
ord
@retval DB_CORRUPTION if the DB_TRX_ID is corrupted */
static dberr_t row_sel_sec_rec_sees(const rec_t *rec,
const dict_index_t &index,
const rec_offs *offsets,
const ReadView &view)
{
ut_ad(index.has_trx_id());
ut_ad(page_rec_is_user_rec(rec));
ut_ad(rec_offs_validate(rec, &index, offsets));
ut_ad(!index.table->is_temporary());
const trx_id_t id= rec_get_sec_trx_id(rec, &index, offsets);
if (view.changes_visible(id))
return DB_SUCCESS;
if (UNIV_LIKELY(id < view.low_limit_id() || id < trx_sys.get_max_trx_id()))
return DB_SUCCESS_LOCKED_REC;
ib::warn() << "A transaction id in a record of index " << index.name
<< " of table " << index.table->name
<< " is newer than the system-wide maximum.";
return DB_CORRUPTION;
}
/*********************************************************************//**
Retrieves the clustered index record corresponding to a record in a
non-clustered index. Does the necessary locking.
@@ -3350,7 +3396,7 @@ public:
dberr_t operator()(row_prebuilt_t *prebuilt, dict_index_t *sec_index,
const rec_t *rec, que_thr_t *thr, const rec_t **out_rec,
rec_offs **offsets, mem_heap_t **offset_heap,
dtuple_t **vrow, mtr_t *mtr);
dtuple_t **vrow, mtr_t *mtr, bool sec_stamp_visible);
};
@@ -3380,9 +3426,14 @@ Row_sel_get_clust_rec_for_mysql::operator()(
mem_heap_t** offset_heap,/*!< in/out: memory heap from which
the offsets are allocated */
dtuple_t** vrow, /*!< out: virtual column to fill */
mtr_t* mtr) /*!< in: mtr used to get access to the
mtr_t* mtr, /*!< in: mtr used to get access to the
non-clustered record; the same mtr is used to
access the clustered index */
bool sec_stamp_visible)
/* !<in: Whether DB_TRX_ID of rec,
an index that has_trx_id(), is visible
in read view of trx; rec is then
version that view sees */
{
/* Extract values from prebuilt */
dtuple_t* clust_ref= prebuilt->clust_ref;
@@ -3614,7 +3665,15 @@ Row_sel_get_clust_rec_for_mysql::operator()(
/* And for spatial index, since the rec is from shadow buffer,
so we need to check if it's exactly match the clust_rec. */
/* When the stamp of rec is visible, rec is the
state that the view sees, and it cannot be for a
different version of the row: any later change of
its ordering fields would have delete-marked and
restamped it. The (expensive) comparison is not
needed. */
if (clust_rec
&& !sec_stamp_visible
&& (old_vers
|| trx->isolation_level <= TRX_ISO_READ_UNCOMMITTED
|| dict_index_is_spatial(sec_index)
@@ -4111,11 +4170,14 @@ row_search_idx_cond_check(
if (prebuilt->clust_index_was_generated) {
ulint len;
dict_index_t* index = prebuilt->index;
const byte* data = rec_get_nth_field(
rec, offsets, index->n_fields - 1,
const ulint row_id_pos =
index->n_fields - 1 -
ulint(index->has_trx_id());
const byte* data = rec_get_nth_field(
rec, offsets, row_id_pos,
&len);
ut_ad(dict_index_get_nth_col(index,
index->n_fields - 1)
row_id_pos)
->prtype == (DATA_ROW_ID | DATA_NOT_NULL));
ut_ad(len == DATA_ROW_ID_LEN);
memcpy(prebuilt->row_id, data, DATA_ROW_ID_LEN);
@@ -4460,6 +4522,7 @@ row_search_mvcc(
ibool table_lock_waited = FALSE;
byte* next_buf = 0;
bool spatial_search = false;
bool sec_stamp_visible = false;
ut_ad(index && pcur);
ut_a(prebuilt->magic_n == ROW_PREBUILT_ALLOCATED);
@@ -5019,6 +5082,7 @@ page_corrupted:
rec_loop:
DEBUG_SYNC_C("row_search_rec_loop");
sec_stamp_visible = false;
if (trx_is_interrupted(trx)) {
if (!spatial_search) {
btr_pcur_store_position(pcur, mtr);
@@ -5305,6 +5369,13 @@ wrong_offs:
transaction. Ignore the record. */
goto locks_ok_del_marked;
}
} else if (index->has_trx_id()) {
const trx_id_t id =
rec_get_sec_trx_id(rec, index, offsets);
if (!id || !trx_sys.is_registered(trx, id)) {
goto next_rec;
}
} else if (trx_t* t = row_vers_impl_x_locked(
trx, rec, index, offsets)) {
/* The record belongs to an active
@@ -5553,6 +5624,24 @@ no_gap_lock:
if (trx->read_view.sees(trx_id)) {
goto locks_ok;
}
if (index->has_trx_id()) {
err = row_sel_sec_rec_sees(
rec, *index, offsets,
trx->read_view);
switch (err) {
case DB_SUCCESS:
sec_stamp_visible = true;
goto locks_ok;
case DB_SUCCESS_LOCKED_REC:
err = DB_SUCCESS;
break;
default:
goto lock_wait_or_error;
}
}
/* We should look at the clustered index.
However, as this is a non-locking read,
we can skip the clustered index lookup if
@@ -5661,7 +5750,7 @@ requires_clust_rec:
thr, &clust_rec,
&offsets, &heap,
need_vrow ? &vrow : NULL,
mtr);
mtr, sec_stamp_visible);
if (err == DB_LOCK_WAIT && prebuilt->skip_locked) {
err = lock_trx_handle_wait(trx);
@@ -5891,11 +5980,13 @@ use_covering_index:
memcpy(prebuilt->row_id, result_rec, DATA_ROW_ID_LEN);
} else {
ulint len;
const ulint row_id_pos = index->n_fields
- 1 - ulint(index->has_trx_id());
const byte* data = rec_get_nth_field(
result_rec, offsets, index->n_fields - 1,
result_rec, offsets, row_id_pos,
&len);
ut_ad(dict_index_get_nth_col(index,
index->n_fields - 1)
row_id_pos)
->prtype == (DATA_ROW_ID | DATA_NOT_NULL));
ut_ad(len == DATA_ROW_ID_LEN);
memcpy(prebuilt->row_id, data, DATA_ROW_ID_LEN);

View File

@@ -691,8 +691,9 @@ found:
if (node->table->is_temporary()
|| row_undo_mod_sec_is_unsafe(
btr_pcur_get_rec(&node->pcur), index, entry, &mtr)) {
btr_rec_set_deleted<true>(btr_cur_get_block(btr_cur),
btr_cur_get_rec(btr_cur), &mtr);
btr_rec_set_deleted_sec<true>(
btr_cur_get_block(btr_cur),
btr_cur_get_rec(btr_cur), index, &mtr);
} else {
/* Remove the index record */
@@ -859,6 +860,10 @@ not_found:
big_rec_t* big_rec;
rec_t* insert_rec;
if (index->has_trx_id()) {
btr_cur_write_sec_trx_id(entry, index, trx->id);
}
err = btr_cur_optimistic_insert(
flags, btr_cur, &offsets, &offsets_heap,
entry, &insert_rec, &big_rec,
@@ -888,8 +893,10 @@ not_found:
}
} else {
found:
btr_rec_set_deleted<false>(btr_cur_get_block(btr_cur),
btr_cur_get_rec(btr_cur), &mtr);
btr_rec_set_deleted_sec<false>(
btr_cur_get_block(btr_cur),
btr_cur_get_rec(btr_cur), index, &mtr);
mem_heap_t* heap = mem_heap_create(
sizeof(upd_t)
+ dtuple_get_n_fields(entry) * sizeof(upd_field_t));
@@ -898,7 +905,8 @@ found:
index, nullptr, index->n_core_fields, ULINT_UNDEFINED,
&offsets_heap);
update = row_upd_build_sec_rec_difference_binary(
btr_cur_get_rec(btr_cur), index, offsets, entry, heap);
btr_cur_get_rec(btr_cur), index, offsets,
entry, heap, index->has_trx_id());
if (upd_get_n_fields(update) == 0) {
/* Do nothing */

View File

@@ -481,7 +481,8 @@ row_upd_build_sec_rec_difference_binary(
dict_index_t* index, /*!< in: index */
const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */
const dtuple_t* entry, /*!< in: entry to insert */
mem_heap_t* heap) /*!< in: memory heap from which allocated */
mem_heap_t* heap, /*!< in: memory heap from which allocated */
bool skip_trx_id)
{
upd_field_t* upd_field;
const dfield_t* dfield;
@@ -498,11 +499,14 @@ row_upd_build_sec_rec_difference_binary(
ut_ad(!rec_offs_any_default(offsets));
ut_ad(!index->table->skip_alter_undo);
ut_ad(!skip_trx_id || index->has_trx_id());
update = upd_create(dtuple_get_n_fields(entry), heap);
n_diff = 0;
for (uint16_t i = 0; i < dtuple_get_n_fields(entry); i++) {
const uint16_t n_cmp = uint16_t(dtuple_get_n_fields(entry) -
uint16_t(skip_trx_id));
for (uint16_t i = 0; i < n_cmp; i++) {
data = rec_get_nth_field(rec, offsets, i, &len);
@@ -536,6 +540,19 @@ row_upd_build_sec_rec_difference_binary(
return(update);
}
void row_upd_add_sec_trx_id(upd_t *update, const dict_index_t *index,
trx_id_t trx_id, mem_heap_t *heap)
{
ut_ad(index->has_trx_id());
ut_ad(update->n_fields < index->n_fields);
byte* b= static_cast<byte*>(mem_heap_alloc(heap, DATA_TRX_ID_LEN));
trx_write_trx_id(b, trx_id);
upd_field_t* uf= &update->fields[update->n_fields];
upd_field_set_field_no(uf, index->sec_db_trx_id(), index);
dfield_set_data(&uf->new_val, b, DATA_TRX_ID_LEN);
update->n_fields++;
}
/** Builds an update vector from those fields, excluding the roll ptr and
trx id fields, which in an index entry differ from a record that has
@@ -1920,9 +1937,9 @@ found:
goto close;
}
btr_rec_set_deleted<true>(btr_pcur_get_block(&pcur),
btr_pcur_get_rec(&pcur),
&mtr);
btr_rec_set_deleted_sec<true>(
btr_pcur_get_block(&pcur),
btr_pcur_get_rec(&pcur), index, &mtr);
#ifdef WITH_WSREP
if (!referenced && foreign
&& wsrep_must_process_fk(node, mtr.trx)