2014-02-26 19:11:54 +01:00
|
|
|
/*****************************************************************************
|
|
|
|
|
2018-02-01 18:36:03 +02:00
|
|
|
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
|
2021-04-13 10:28:13 +03:00
|
|
|
Copyright (c) 2017, 2021, MariaDB Corporation.
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
|
|
the terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; version 2 of the License.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
this program; if not, write to the Free Software Foundation, Inc.,
|
2019-05-11 19:25:02 +03:00
|
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/**************************************************//**
|
|
|
|
@file row/row0vers.cc
|
|
|
|
Row versions
|
|
|
|
|
|
|
|
Created 2/6/1997 Heikki Tuuri
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
#include "row0vers.h"
|
|
|
|
#include "dict0dict.h"
|
|
|
|
#include "dict0boot.h"
|
|
|
|
#include "btr0btr.h"
|
|
|
|
#include "mach0data.h"
|
|
|
|
#include "trx0rseg.h"
|
|
|
|
#include "trx0trx.h"
|
|
|
|
#include "trx0roll.h"
|
|
|
|
#include "trx0undo.h"
|
|
|
|
#include "trx0purge.h"
|
|
|
|
#include "trx0rec.h"
|
|
|
|
#include "que0que.h"
|
|
|
|
#include "row0row.h"
|
|
|
|
#include "row0upd.h"
|
|
|
|
#include "rem0cmp.h"
|
|
|
|
#include "lock0lock.h"
|
2016-08-12 11:17:45 +03:00
|
|
|
#include "row0mysql.h"
|
|
|
|
|
2018-02-01 18:36:03 +02:00
|
|
|
/** Check whether all non-virtual index fields are equal.
|
|
|
|
@param[in] index the secondary index
|
|
|
|
@param[in] a first index entry to compare
|
|
|
|
@param[in] b second index entry to compare
|
|
|
|
@return whether all non-virtual fields are equal */
|
2016-08-12 11:17:45 +03:00
|
|
|
static
|
|
|
|
bool
|
2018-02-01 18:36:03 +02:00
|
|
|
row_vers_non_virtual_fields_equal(
|
|
|
|
const dict_index_t* index,
|
|
|
|
const dfield_t* a,
|
|
|
|
const dfield_t* b)
|
|
|
|
{
|
|
|
|
const dict_field_t* end = &index->fields[index->n_fields];
|
|
|
|
|
|
|
|
for (const dict_field_t* ifield = index->fields; ifield != end;
|
|
|
|
ifield++) {
|
2018-05-12 09:42:53 +03:00
|
|
|
if (!ifield->col->is_virtual()
|
2018-02-01 18:36:03 +02:00
|
|
|
&& cmp_dfield_dfield(a++, b++)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-12-11 12:37:19 +02:00
|
|
|
/** Determine if an active transaction has inserted or modified a secondary
|
2014-02-26 19:11:54 +01:00
|
|
|
index record.
|
2017-12-13 15:40:41 +04:00
|
|
|
@param[in,out] caller_trx trx of current thread
|
2017-12-11 12:37:19 +02:00
|
|
|
@param[in] clust_rec clustered index record
|
|
|
|
@param[in] clust_index clustered index
|
|
|
|
@param[in] rec secondary index record
|
|
|
|
@param[in] index secondary index
|
|
|
|
@param[in] offsets rec_get_offsets(rec, index)
|
|
|
|
@param[in,out] mtr mini-transaction
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
@return the active transaction; state must be rechecked after
|
2019-09-03 13:04:05 +03:00
|
|
|
trx_mutex_enter(), and trx->release_reference() must be invoked
|
2017-12-11 12:37:19 +02:00
|
|
|
@retval NULL if the record was committed */
|
2014-02-26 19:11:54 +01:00
|
|
|
UNIV_INLINE
|
2016-08-12 11:17:45 +03:00
|
|
|
trx_t*
|
2014-02-26 19:11:54 +01:00
|
|
|
row_vers_impl_x_locked_low(
|
2017-12-13 15:40:41 +04:00
|
|
|
trx_t* caller_trx,
|
2017-12-11 12:37:19 +02:00
|
|
|
const rec_t* clust_rec,
|
|
|
|
dict_index_t* clust_index,
|
|
|
|
const rec_t* rec,
|
|
|
|
dict_index_t* index,
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,
|
2017-12-11 12:37:19 +02:00
|
|
|
mtr_t* mtr)
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
|
|
|
trx_id_t trx_id;
|
|
|
|
rec_t* prev_version = NULL;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs clust_offsets_[REC_OFFS_NORMAL_SIZE];
|
2020-07-31 11:38:23 +03:00
|
|
|
rec_offs* clust_offsets;
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_t* heap;
|
2016-08-12 11:17:45 +03:00
|
|
|
dtuple_t* ientry = NULL;
|
2016-09-06 09:43:16 +03:00
|
|
|
mem_heap_t* v_heap = NULL;
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t* cur_vrow = NULL;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
MDEV-20950 Reduce size of record offsets
offset_t: this is a type which represents one record offset.
It's unsigned short int.
a lot of functions: replace ulint with offset_t
btr_pcur_restore_position_func(),
page_validate(),
row_ins_scan_sec_index_for_duplicate(),
row_upd_clust_rec_by_insert_inherit_func(),
row_vers_impl_x_locked_low(),
trx_undo_prev_version_build():
allocate record offsets on the stack instead of waiting for rec_get_offsets()
to allocate it from mem_heap_t. So, reducing memory allocations.
RECORD_OFFSET, INDEX_OFFSET:
now it's less convenient to store pointers in offset_t*
array. One pointer occupies now several offset_t. And those constant are start
indexes into array to places where to store pointer values
REC_OFFS_HEADER_SIZE: adjusted for the new reality
REC_OFFS_NORMAL_SIZE:
increase size from 100 to 300 which means less heap allocations.
And sizeof(offset_t[REC_OFFS_NORMAL_SIZE]) now is 600 bytes which
is smaller than previous 800 bytes.
REC_OFFS_SEC_INDEX_SIZE: adjusted for the new reality
rem0rec.h, rem0rec.ic, rem0rec.cc:
various arguments, return values and local variables types were changed to
fix numerous integer conversions issues.
enum field_type_t:
offset types concept was introduces which replaces old offset flags stuff.
Like in earlier version, 2 upper bits are used to store offset type.
And this enum represents those types.
REC_OFFS_SQL_NULL, REC_OFFS_MASK: removed
get_type(), set_type(), get_value(), combine():
these are convenience functions to work with offsets and it's types
rec_offs_base()[0]:
still uses an old scheme with flags REC_OFFS_COMPACT and REC_OFFS_EXTERNAL
rec_offs_base()[i]:
these have type offset_t now. Two upper bits contains type.
2019-11-04 22:30:12 +03:00
|
|
|
rec_offs_init(clust_offsets_);
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
DBUG_ENTER("row_vers_impl_x_locked_low");
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
|
|
|
MDEV-12288 Reset DB_TRX_ID when the history is removed, to speed up MVCC
Let InnoDB purge reset DB_TRX_ID,DB_ROLL_PTR when the history is removed.
[TODO: It appears that the resetting is not taking place as often as
it could be. We should test that a simple INSERT should eventually
cause row_purge_reset_trx_id() to be invoked unless DROP TABLE is
invoked soon enough.]
The InnoDB clustered index record system columns DB_TRX_ID,DB_ROLL_PTR
are used by multi-versioning. After the history is no longer needed, these
columns can safely be reset to 0 and 1<<55 (to indicate a fresh insert).
When a reader sees 0 in the DB_TRX_ID column, it can instantly determine
that the record is present the read view. There is no need to acquire
the transaction system mutex to check if the transaction exists, because
writes can never be conducted by a transaction whose ID is 0.
The persistent InnoDB undo log used to be split into two parts:
insert_undo and update_undo. The insert_undo log was discarded at
transaction commit or rollback, and the update_undo log was processed
by the purge subsystem. As part of this change, we will only generate
a single undo log for new transactions, and the purge subsystem will
reset the DB_TRX_ID whenever a clustered index record is touched.
That is, all persistent undo log will be preserved at transaction commit
or rollback, to be removed by purge.
The InnoDB redo log format is changed in two ways:
We remove the redo log record type MLOG_UNDO_HDR_REUSE, and
we introduce the MLOG_ZIP_WRITE_TRX_ID record for updating the
DB_TRX_ID,DB_ROLL_PTR in a ROW_FORMAT=COMPRESSED table.
This is also changing the format of persistent InnoDB data files:
undo log and clustered index leaf page records. It will still be
possible via import and export to exchange data files with earlier
versions of MariaDB. The change to clustered index leaf page records
is simple: we allow DB_TRX_ID to be 0.
When it comes to the undo log, we must be able to upgrade from earlier
MariaDB versions after a clean shutdown (no redo log to apply).
While it would be nice to perform a slow shutdown (innodb_fast_shutdown=0)
before an upgrade, to empty the undo logs, we cannot assume that this
has been done. So, separate insert_undo log may exist for recovered
uncommitted transactions. These transactions may be automatically
rolled back, or they may be in XA PREPARE state, in which case InnoDB
will preserve the transaction until an explicit XA COMMIT or XA ROLLBACK.
Upgrade has been tested by starting up MariaDB 10.2 with
./mysql-test-run --manual-gdb innodb.read_only_recovery
and then starting up this patched server with
and without --innodb-read-only.
trx_undo_ptr_t::undo: Renamed from update_undo.
trx_undo_ptr_t::old_insert: Renamed from insert_undo.
trx_rseg_t::undo_list: Renamed from update_undo_list.
trx_rseg_t::undo_cached: Merged from update_undo_cached
and insert_undo_cached.
trx_rseg_t::old_insert_list: Renamed from insert_undo_list.
row_purge_reset_trx_id(): New function to reset the columns.
This will be called for all undo processing in purge
that does not remove the clustered index record.
trx_undo_update_rec_get_update(): Allow trx_id=0 when copying the
old DB_TRX_ID of the record to the undo log.
ReadView::changes_visible(): Allow id==0. (Return true for it.
This is what speeds up the MVCC.)
row_vers_impl_x_locked_low(), row_vers_build_for_semi_consistent_read():
Implement a fast path for DB_TRX_ID=0.
Always initialize the TRX_UNDO_PAGE_TYPE to 0. Remove undo->type.
MLOG_UNDO_HDR_REUSE: Remove. This changes the redo log format!
innobase_start_or_create_for_mysql(): Set srv_undo_sources before
starting any transactions.
The parsing of the MLOG_ZIP_WRITE_TRX_ID record was successfully
tested by running the following:
./mtr --parallel=auto --mysqld=--debug=d,ib_log innodb_zip.bug56680
grep MLOG_ZIP_WRITE_TRX_ID var/*/log/mysqld.1.err
2017-07-07 13:08:16 +03:00
|
|
|
if (ulint trx_id_offset = clust_index->trx_id_offset) {
|
|
|
|
trx_id = mach_read_from_6(clust_rec + trx_id_offset);
|
|
|
|
if (trx_id == 0) {
|
|
|
|
/* The transaction history was already purged. */
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
|
2020-07-31 11:38:23 +03:00
|
|
|
clust_offsets = rec_get_offsets(clust_rec, clust_index, clust_offsets_,
|
2021-04-13 10:28:13 +03:00
|
|
|
clust_index->n_core_fields,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
trx_id = row_get_rec_trx_id(clust_rec, clust_index, clust_offsets);
|
MDEV-12288 Reset DB_TRX_ID when the history is removed, to speed up MVCC
Let InnoDB purge reset DB_TRX_ID,DB_ROLL_PTR when the history is removed.
[TODO: It appears that the resetting is not taking place as often as
it could be. We should test that a simple INSERT should eventually
cause row_purge_reset_trx_id() to be invoked unless DROP TABLE is
invoked soon enough.]
The InnoDB clustered index record system columns DB_TRX_ID,DB_ROLL_PTR
are used by multi-versioning. After the history is no longer needed, these
columns can safely be reset to 0 and 1<<55 (to indicate a fresh insert).
When a reader sees 0 in the DB_TRX_ID column, it can instantly determine
that the record is present the read view. There is no need to acquire
the transaction system mutex to check if the transaction exists, because
writes can never be conducted by a transaction whose ID is 0.
The persistent InnoDB undo log used to be split into two parts:
insert_undo and update_undo. The insert_undo log was discarded at
transaction commit or rollback, and the update_undo log was processed
by the purge subsystem. As part of this change, we will only generate
a single undo log for new transactions, and the purge subsystem will
reset the DB_TRX_ID whenever a clustered index record is touched.
That is, all persistent undo log will be preserved at transaction commit
or rollback, to be removed by purge.
The InnoDB redo log format is changed in two ways:
We remove the redo log record type MLOG_UNDO_HDR_REUSE, and
we introduce the MLOG_ZIP_WRITE_TRX_ID record for updating the
DB_TRX_ID,DB_ROLL_PTR in a ROW_FORMAT=COMPRESSED table.
This is also changing the format of persistent InnoDB data files:
undo log and clustered index leaf page records. It will still be
possible via import and export to exchange data files with earlier
versions of MariaDB. The change to clustered index leaf page records
is simple: we allow DB_TRX_ID to be 0.
When it comes to the undo log, we must be able to upgrade from earlier
MariaDB versions after a clean shutdown (no redo log to apply).
While it would be nice to perform a slow shutdown (innodb_fast_shutdown=0)
before an upgrade, to empty the undo logs, we cannot assume that this
has been done. So, separate insert_undo log may exist for recovered
uncommitted transactions. These transactions may be automatically
rolled back, or they may be in XA PREPARE state, in which case InnoDB
will preserve the transaction until an explicit XA COMMIT or XA ROLLBACK.
Upgrade has been tested by starting up MariaDB 10.2 with
./mysql-test-run --manual-gdb innodb.read_only_recovery
and then starting up this patched server with
and without --innodb-read-only.
trx_undo_ptr_t::undo: Renamed from update_undo.
trx_undo_ptr_t::old_insert: Renamed from insert_undo.
trx_rseg_t::undo_list: Renamed from update_undo_list.
trx_rseg_t::undo_cached: Merged from update_undo_cached
and insert_undo_cached.
trx_rseg_t::old_insert_list: Renamed from insert_undo_list.
row_purge_reset_trx_id(): New function to reset the columns.
This will be called for all undo processing in purge
that does not remove the clustered index record.
trx_undo_update_rec_get_update(): Allow trx_id=0 when copying the
old DB_TRX_ID of the record to the undo log.
ReadView::changes_visible(): Allow id==0. (Return true for it.
This is what speeds up the MVCC.)
row_vers_impl_x_locked_low(), row_vers_build_for_semi_consistent_read():
Implement a fast path for DB_TRX_ID=0.
Always initialize the TRX_UNDO_PAGE_TYPE to 0. Remove undo->type.
MLOG_UNDO_HDR_REUSE: Remove. This changes the redo log format!
innobase_start_or_create_for_mysql(): Set srv_undo_sources before
starting any transactions.
The parsing of the MLOG_ZIP_WRITE_TRX_ID record was successfully
tested by running the following:
./mtr --parallel=auto --mysqld=--debug=d,ib_log innodb_zip.bug56680
grep MLOG_ZIP_WRITE_TRX_ID var/*/log/mysqld.1.err
2017-07-07 13:08:16 +03:00
|
|
|
if (trx_id == 0) {
|
|
|
|
/* The transaction history was already purged. */
|
|
|
|
mem_heap_free(heap);
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2018-07-26 08:54:44 +03:00
|
|
|
ut_ad(!clust_index->table->is_temporary());
|
2018-07-25 13:56:39 +05:30
|
|
|
|
2018-07-03 15:10:06 +03:00
|
|
|
trx_t* trx;
|
|
|
|
|
|
|
|
if (trx_id == caller_trx->id) {
|
|
|
|
trx = caller_trx;
|
|
|
|
trx->reference();
|
|
|
|
} else {
|
|
|
|
trx = trx_sys.find(caller_trx, trx_id);
|
|
|
|
if (trx == 0) {
|
|
|
|
/* The transaction that modified or inserted
|
|
|
|
clust_rec is no longer active, or it is
|
|
|
|
corrupt: no implicit lock on rec */
|
|
|
|
lock_check_trx_id_sanity(trx_id, clust_rec,
|
|
|
|
clust_index, clust_offsets);
|
|
|
|
mem_heap_free(heap);
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
const ulint comp = page_rec_is_comp(rec);
|
2014-02-26 19:11:54 +01:00
|
|
|
ut_ad(index->table == clust_index->table);
|
|
|
|
ut_ad(!!comp == dict_table_is_comp(index->table));
|
|
|
|
ut_ad(!comp == !page_rec_is_comp(clust_rec));
|
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
const ulint rec_del = rec_get_deleted_flag(rec, comp);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
2016-09-06 09:43:16 +03:00
|
|
|
ulint est_size = DTUPLE_EST_ALLOC(index->n_fields);
|
|
|
|
|
|
|
|
/* Allocate the dtuple for virtual columns extracted from undo
|
|
|
|
log with its own heap, so to avoid it being freed as we
|
|
|
|
iterating in the version loop below. */
|
|
|
|
v_heap = mem_heap_create(est_size);
|
2020-01-17 11:11:19 +02:00
|
|
|
ientry = row_rec_to_index_entry(rec, index, offsets, v_heap);
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
/* We look up if some earlier version, which was modified by
|
|
|
|
the trx_id transaction, of the clustered index record would
|
|
|
|
require rec to be in a different state (delete marked or
|
|
|
|
unmarked, or have different field values, or not existing). If
|
|
|
|
there is such a version, then rec was modified by the trx_id
|
|
|
|
transaction, and it has an implicit x-lock on rec. Note that
|
|
|
|
if clust_rec itself would require rec to be in a different
|
|
|
|
state, then the trx_id transaction has not yet had time to
|
|
|
|
modify rec, and does not necessarily have an implicit x-lock
|
|
|
|
on rec. */
|
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
for (const rec_t* version = clust_rec;; version = prev_version) {
|
2014-02-26 19:11:54 +01:00
|
|
|
row_ext_t* ext;
|
2016-08-12 11:17:45 +03:00
|
|
|
dtuple_t* row;
|
2014-02-26 19:11:54 +01:00
|
|
|
dtuple_t* entry;
|
|
|
|
ulint vers_del;
|
|
|
|
trx_id_t prev_trx_id;
|
|
|
|
mem_heap_t* old_heap = heap;
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t* vrow = NULL;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
/* We keep the semaphore in mtr on the clust_rec page, so
|
|
|
|
that no other transaction can update it and get an
|
|
|
|
implicit x-lock on rec until mtr_commit(mtr). */
|
|
|
|
|
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
|
|
|
|
trx_undo_prev_version_build(
|
|
|
|
clust_rec, mtr, version, clust_index, clust_offsets,
|
2016-08-12 11:17:45 +03:00
|
|
|
heap, &prev_version, NULL,
|
|
|
|
dict_index_has_virtual(index) ? &vrow : NULL, 0);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
trx_mutex_enter(trx);
|
|
|
|
const bool committed = trx_state_eq(
|
|
|
|
trx, TRX_STATE_COMMITTED_IN_MEMORY);
|
|
|
|
trx_mutex_exit(trx);
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* The oldest visible clustered index version must not be
|
|
|
|
delete-marked, because we never start a transaction by
|
|
|
|
inserting a delete-marked record. */
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
ut_ad(committed || prev_version
|
|
|
|
|| !rec_get_deleted_flag(version, comp));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2020-07-31 11:38:23 +03:00
|
|
|
/* Free version and clust_offsets. */
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_free(old_heap);
|
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
if (committed) {
|
|
|
|
goto not_locked;
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
if (prev_version == NULL) {
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* We reached the oldest visible version without
|
|
|
|
finding an older version of clust_rec that would
|
|
|
|
match the secondary index record. If the secondary
|
|
|
|
index record is not delete marked, then clust_rec
|
|
|
|
is considered the correct match of the secondary
|
|
|
|
index record and hence holds the implicit lock. */
|
|
|
|
|
|
|
|
if (rec_del) {
|
|
|
|
/* The secondary index record is del marked.
|
|
|
|
So, the implicit lock holder of clust_rec
|
|
|
|
did not modify the secondary index record yet,
|
|
|
|
and is not holding an implicit lock on it.
|
|
|
|
|
|
|
|
This assumes that whenever a row is inserted
|
|
|
|
or updated, the leaf page record always is
|
|
|
|
created with a clear delete-mark flag.
|
|
|
|
(We never insert a delete-marked record.) */
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
not_locked:
|
2017-12-20 14:59:36 +04:00
|
|
|
trx->release_reference();
|
2016-08-12 11:17:45 +03:00
|
|
|
trx = 0;
|
2014-05-06 21:13:16 +02:00
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
clust_offsets = rec_get_offsets(
|
2021-04-13 10:28:13 +03:00
|
|
|
prev_version, clust_index, clust_offsets_,
|
|
|
|
clust_index->n_core_fields,
|
2017-09-19 19:20:11 +03:00
|
|
|
ULINT_UNDEFINED, &heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
vers_del = rec_get_deleted_flag(prev_version, comp);
|
|
|
|
|
|
|
|
prev_trx_id = row_get_rec_trx_id(prev_version, clust_index,
|
|
|
|
clust_offsets);
|
|
|
|
|
|
|
|
/* The stack of versions is locked by mtr. Thus, it
|
|
|
|
is safe to fetch the prefixes for externally stored
|
|
|
|
columns. */
|
|
|
|
|
|
|
|
row = row_build(ROW_COPY_POINTERS, clust_index, prev_version,
|
|
|
|
clust_offsets,
|
|
|
|
NULL, NULL, NULL, &ext, heap);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
2016-09-06 09:43:16 +03:00
|
|
|
if (vrow) {
|
|
|
|
/* Keep the virtual row info for the next
|
|
|
|
version */
|
|
|
|
cur_vrow = dtuple_copy(vrow, v_heap);
|
|
|
|
dtuple_dup_v_fld(cur_vrow, v_heap);
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2016-09-06 09:43:16 +03:00
|
|
|
if (!cur_vrow) {
|
2018-02-01 18:36:03 +02:00
|
|
|
/* Build index entry out of row */
|
|
|
|
entry = row_build_index_entry(row, ext, index,
|
|
|
|
heap);
|
|
|
|
|
|
|
|
/* entry could only be NULL (the
|
|
|
|
clustered index record could contain
|
|
|
|
BLOB pointers that are NULL) if we
|
|
|
|
were accessing a freshly inserted
|
|
|
|
record before it was fully inserted.
|
|
|
|
prev_version cannot possibly be such
|
|
|
|
an incomplete record, because its
|
|
|
|
transaction would have to be committed
|
|
|
|
in order for later versions of the
|
|
|
|
record to be able to exist. */
|
|
|
|
ut_ad(entry);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
/* If the indexed virtual columns has changed,
|
|
|
|
there must be log record to generate vrow.
|
|
|
|
Otherwise, it is not changed, so no need
|
|
|
|
to compare */
|
2018-02-01 18:36:03 +02:00
|
|
|
if (!row_vers_non_virtual_fields_equal(
|
|
|
|
index,
|
|
|
|
ientry->fields, entry->fields)) {
|
2016-09-06 09:43:16 +03:00
|
|
|
if (rec_del != vers_del) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (!rec_del) {
|
2016-08-12 11:17:45 +03:00
|
|
|
break;
|
|
|
|
}
|
2016-09-06 09:43:16 +03:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
goto result_check;
|
|
|
|
} else {
|
2016-09-06 09:43:16 +03:00
|
|
|
ut_ad(row->n_v_fields == cur_vrow->n_v_fields);
|
|
|
|
dtuple_copy_v_fields(row, cur_vrow);
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
entry = row_build_index_entry(row, ext, index, heap);
|
|
|
|
|
2018-02-01 17:39:10 +02:00
|
|
|
/* entry could only be NULL (the clustered index
|
|
|
|
record could contain BLOB pointers that are NULL) if
|
|
|
|
we were accessing a freshly inserted record before it
|
|
|
|
was fully inserted. prev_version cannot possibly be
|
|
|
|
such an incomplete record, because its transaction
|
|
|
|
would have to be committed in order for later versions
|
|
|
|
of the record to be able to exist. */
|
|
|
|
ut_ad(entry);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
/* If we get here, we know that the trx_id transaction
|
|
|
|
modified prev_version. Let us check if prev_version
|
|
|
|
would require rec to be in a different state. */
|
|
|
|
|
|
|
|
/* The previous version of clust_rec must be
|
|
|
|
accessible, because clust_rec was not a fresh insert.
|
|
|
|
There is no guarantee that the transaction is still
|
|
|
|
active. */
|
|
|
|
|
|
|
|
/* We check if entry and rec are identified in the alphabetical
|
|
|
|
ordering */
|
2016-09-06 09:43:16 +03:00
|
|
|
if (0 == cmp_dtuple_rec(entry, rec, offsets)) {
|
2014-02-26 19:11:54 +01:00
|
|
|
/* The delete marks of rec and prev_version should be
|
|
|
|
equal for rec to be in the state required by
|
|
|
|
prev_version */
|
|
|
|
|
|
|
|
if (rec_del != vers_del) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It is possible that the row was updated so that the
|
|
|
|
secondary index record remained the same in
|
|
|
|
alphabetical ordering, but the field values changed
|
|
|
|
still. For example, 'abc' -> 'ABC'. Check also that. */
|
|
|
|
|
|
|
|
dtuple_set_types_binary(
|
|
|
|
entry, dtuple_get_n_fields(entry));
|
|
|
|
|
|
|
|
if (0 != cmp_dtuple_rec(entry, rec, offsets)) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (!rec_del) {
|
|
|
|
/* The delete mark should be set in rec for it to be
|
|
|
|
in the state required by prev_version */
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
result_check:
|
|
|
|
if (trx->id != prev_trx_id) {
|
2014-02-26 19:11:54 +01:00
|
|
|
/* prev_version was the first version modified by
|
|
|
|
the trx_id transaction: no implicit x-lock */
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
goto not_locked;
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
if (trx) {
|
|
|
|
DBUG_PRINT("info", ("Implicit lock is held by trx:" TRX_ID_FMT,
|
|
|
|
trx_id));
|
|
|
|
}
|
2014-05-06 21:13:16 +02:00
|
|
|
|
2016-09-06 09:43:16 +03:00
|
|
|
if (v_heap != NULL) {
|
|
|
|
mem_heap_free(v_heap);
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_free(heap);
|
2016-08-12 11:17:45 +03:00
|
|
|
DBUG_RETURN(trx);
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
2017-12-11 12:37:19 +02:00
|
|
|
/** Determine if an active transaction has inserted or modified a secondary
|
2014-02-26 19:11:54 +01:00
|
|
|
index record.
|
2017-12-13 15:40:41 +04:00
|
|
|
@param[in,out] caller_trx trx of current thread
|
2017-12-11 12:37:19 +02:00
|
|
|
@param[in] rec secondary index record
|
|
|
|
@param[in] index secondary index
|
|
|
|
@param[in] offsets rec_get_offsets(rec, index)
|
MDEV-15326: InnoDB: Failing assertion: !other_lock
MySQL 5.7.9 (and MariaDB 10.2.2) introduced a race condition
between InnoDB transaction commit and the conversion of implicit
locks into explicit ones.
The assertion failure can be triggered with a test that runs
3 concurrent single-statement transactions in a loop on a simple
table:
CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB;
thread1: INSERT INTO t SET a=1;
thread2: DELETE FROM t;
thread3: SELECT * FROM t FOR UPDATE; -- or DELETE FROM t;
The failure scenarios are like the following:
(1) The INSERT statement is being committed, waiting for lock_sys->mutex.
(2) At the time of the failure, both the DELETE and SELECT transactions
are active but have not logged any changes yet.
(3) The transaction where the !other_lock assertion fails started
lock_rec_convert_impl_to_expl().
(4) After this point, the commit of the INSERT removed the transaction from
trx_sys->rw_trx_set, in trx_erase_lists().
(5) The other transaction consulted trx_sys->rw_trx_set and determined
that there is no implicit lock. Hence, it grabbed the lock.
(6) The !other_lock assertion fails in lock_rec_add_to_queue()
for the lock_rec_convert_impl_to_expl(), because the lock was 'stolen'.
This assertion failure looks genuine, because the INSERT transaction
is still active (trx->state=TRX_STATE_ACTIVE).
The problematic step (4) was introduced in
mysql/mysql-server@e27e0e0bb75b4d35e87059816f1cc370c09890ad
which fixed something related to MVCC (covered by the test
innodb.innodb-read-view). Basically, it reintroduced an error
that had been mentioned in an earlier commit
mysql/mysql-server@a17be6963fc0d9210fa0642d3985b7219cdaf0c5:
"The active transaction was removed from trx_sys->rw_trx_set prematurely."
Our fix goes along the following lines:
(a) Implicit locks will released by assigning
trx->state=TRX_STATE_COMMITTED_IN_MEMORY as the first step.
This transition will no longer be protected by lock_sys_t::mutex,
only by trx->mutex. This idea is by Sergey Vojtovich.
(b) We detach the transaction from trx_sys before starting to release
explicit locks.
(c) All callers of trx_rw_is_active() and trx_rw_is_active_low() must
recheck trx->state after acquiring trx->mutex.
(d) Before releasing any explicit locks, we will ensure that any activity
by other threads to convert implicit locks into explicit will have ceased,
by checking !trx_is_referenced(trx). There was a glitch
in this check when it was part of lock_trx_release_locks(); at the end
we would release trx->mutex and acquire lock_sys->mutex and trx->mutex,
and fail to recheck (trx_is_referenced() is protected by trx_t::mutex).
(e) Explicit locks can be released in batches (LOCK_RELEASE_INTERVAL=1000)
just like we did before.
trx_t::state: Document that the transition to COMMITTED is only
protected by trx_t::mutex, no longer by lock_sys_t::mutex.
trx_rw_is_active_low(), trx_rw_is_active(): Document that the transaction
state should be rechecked after acquiring trx_t::mutex.
trx_t::commit_state(): New function to change a transaction to committed
state, to release implicit locks.
trx_t::release_locks(): New function to release the explicit locks
after commit_state().
lock_trx_release_locks(): Move much of the logic to the caller
(which must invoke trx_t::commit_state() and trx_t::release_locks()
as needed), and assert that the transaction will have locks.
trx_get_trx_by_xid(): Make the parameter a pointer to const.
lock_rec_other_trx_holds_expl(): Recheck trx->state after acquiring
trx->mutex, and avoid a redundant lookup of the transaction.
lock_rec_queue_validate(): Recheck impl_trx->state while holding
impl_trx->mutex.
row_vers_impl_x_locked(), row_vers_impl_x_locked_low():
Document that the transaction state must be rechecked after
trx_mutex_enter().
trx_free_prepared(): Adjust for the changes to lock_trx_release_locks().
2019-09-03 12:31:37 +03:00
|
|
|
@return the active transaction; state must be rechecked after
|
2019-09-03 13:04:05 +03:00
|
|
|
trx_mutex_enter(), and trx->release_reference() must be invoked
|
2017-12-11 12:37:19 +02:00
|
|
|
@retval NULL if the record was committed */
|
2016-08-12 11:17:45 +03:00
|
|
|
trx_t*
|
2014-02-26 19:11:54 +01:00
|
|
|
row_vers_impl_x_locked(
|
2017-12-13 15:40:41 +04:00
|
|
|
trx_t* caller_trx,
|
2017-12-11 12:37:19 +02:00
|
|
|
const rec_t* rec,
|
|
|
|
dict_index_t* index,
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets)
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
|
|
|
mtr_t mtr;
|
2016-08-12 11:17:45 +03:00
|
|
|
trx_t* trx;
|
|
|
|
const rec_t* clust_rec;
|
|
|
|
dict_index_t* clust_index;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
ut_ad(!lock_mutex_own());
|
2017-12-22 16:15:41 +02:00
|
|
|
ut_ad(!mutex_own(&trx_sys.mutex));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
/* Search for the clustered index record. The latch on the
|
|
|
|
page of clust_rec locks the top of the stack of versions. The
|
|
|
|
bottom of the version stack is not locked; oldest versions may
|
|
|
|
disappear by the fact that transactions may be committed and
|
|
|
|
collected by the purge. This is not a problem, because we are
|
|
|
|
only interested in active transactions. */
|
|
|
|
|
|
|
|
clust_rec = row_get_clust_rec(
|
|
|
|
BTR_SEARCH_LEAF, rec, index, &clust_index, &mtr);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (!clust_rec) {
|
2014-02-26 19:11:54 +01:00
|
|
|
/* In a rare case it is possible that no clust rec is found
|
|
|
|
for a secondary index record: if in row0umod.cc
|
|
|
|
row_undo_mod_remove_clust_low() we have already removed the
|
|
|
|
clust rec, while purge is still cleaning and removing
|
|
|
|
secondary index records associated with earlier versions of
|
|
|
|
the clustered index record. In that case there cannot be
|
|
|
|
any implicit lock on the secondary index record, because
|
|
|
|
an active transaction which has modified the secondary index
|
|
|
|
record has also modified the clustered index record. And in
|
|
|
|
a rollback we always undo the modifications to secondary index
|
|
|
|
records before the clustered index record. */
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
trx = 0;
|
2014-02-26 19:11:54 +01:00
|
|
|
} else {
|
2016-08-12 11:17:45 +03:00
|
|
|
trx = row_vers_impl_x_locked_low(
|
2017-12-13 15:40:41 +04:00
|
|
|
caller_trx, clust_rec, clust_index, rec, index,
|
|
|
|
offsets, &mtr);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2017-12-20 14:59:36 +04:00
|
|
|
ut_ad(trx == 0 || trx->is_referenced());
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
return(trx);
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/** build virtual column value from current cluster index record data
|
|
|
|
@param[in,out] row the cluster index row in dtuple form
|
2016-09-06 09:43:16 +03:00
|
|
|
@param[in] clust_index clustered index
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in] index the secondary index
|
2018-07-06 17:13:53 +03:00
|
|
|
@param[in] heap heap used to build virtual dtuple
|
2020-08-11 00:38:32 +10:00
|
|
|
@param[in,out] vcol_info virtual column information.
|
|
|
|
@return true in case of success
|
|
|
|
false if virtual column computation fails */
|
2016-08-12 11:17:45 +03:00
|
|
|
static
|
2020-08-11 00:38:32 +10:00
|
|
|
bool
|
2016-08-12 11:17:45 +03:00
|
|
|
row_vers_build_clust_v_col(
|
2018-07-06 17:13:53 +03:00
|
|
|
dtuple_t* row,
|
|
|
|
dict_index_t* clust_index,
|
|
|
|
dict_index_t* index,
|
|
|
|
mem_heap_t* heap,
|
|
|
|
purge_vcol_info_t* vcol_info)
|
2016-08-12 11:17:45 +03:00
|
|
|
{
|
2018-06-17 14:19:51 +03:00
|
|
|
THD* thd= current_thd;
|
|
|
|
TABLE* maria_table= 0;
|
|
|
|
|
|
|
|
ut_ad(dict_index_has_virtual(index));
|
2018-08-23 13:11:11 +03:00
|
|
|
ut_ad(index->table == clust_index->table);
|
2018-06-17 14:19:51 +03:00
|
|
|
|
2018-07-06 17:13:53 +03:00
|
|
|
if (vcol_info != NULL) {
|
|
|
|
vcol_info->set_used();
|
2018-07-23 18:23:54 +03:00
|
|
|
maria_table = vcol_info->table();
|
2018-07-06 17:13:53 +03:00
|
|
|
}
|
2019-07-25 22:17:04 +10:00
|
|
|
DEBUG_SYNC(current_thd, "ib_clust_v_col_before_row_allocated");
|
2018-07-06 17:13:53 +03:00
|
|
|
|
2020-08-21 20:05:02 +10:00
|
|
|
ib_vcol_row vc(NULL);
|
|
|
|
byte *record = vc.record(thd, index, &maria_table);
|
2018-06-17 14:19:51 +03:00
|
|
|
|
2018-07-23 18:23:54 +03:00
|
|
|
if (vcol_info && !vcol_info->table()) {
|
|
|
|
vcol_info->set_table(maria_table);
|
2020-08-11 00:38:32 +10:00
|
|
|
// wait for second fetch
|
|
|
|
return true;
|
2018-07-06 17:13:53 +03:00
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
for (ulint i = 0; i < dict_index_get_n_fields(index); i++) {
|
|
|
|
const dict_field_t* ind_field = dict_index_get_nth_field(
|
|
|
|
index, i);
|
|
|
|
|
2018-05-12 09:42:53 +03:00
|
|
|
if (ind_field->col->is_virtual()) {
|
2016-08-12 11:17:45 +03:00
|
|
|
const dict_v_col_t* col;
|
|
|
|
|
|
|
|
col = reinterpret_cast<const dict_v_col_t*>(
|
|
|
|
ind_field->col);
|
|
|
|
|
2020-08-11 00:38:32 +10:00
|
|
|
dfield_t *vfield = innobase_get_computed_value(
|
2020-08-21 20:05:02 +10:00
|
|
|
row, col, clust_index, &vc.heap,
|
2018-06-17 14:19:51 +03:00
|
|
|
heap, NULL, thd, maria_table, record, NULL,
|
2021-10-27 18:37:33 +03:00
|
|
|
NULL);
|
2020-08-11 00:38:32 +10:00
|
|
|
if (vfield == NULL) {
|
|
|
|
innobase_report_computed_value_failed(row);
|
|
|
|
ut_ad(0);
|
|
|
|
return false;
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
}
|
2020-08-11 00:38:32 +10:00
|
|
|
return true;
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
2018-06-17 14:19:51 +03:00
|
|
|
|
2016-09-06 09:43:16 +03:00
|
|
|
/** Build latest virtual column data from undo log
|
|
|
|
@param[in] in_purge whether this is the purge thread
|
|
|
|
@param[in] rec clustered index record
|
|
|
|
@param[in] clust_index clustered index
|
|
|
|
@param[in,out] clust_offsets offsets on the clustered index record
|
|
|
|
@param[in] index the secondary index
|
|
|
|
@param[in] roll_ptr the rollback pointer for the purging record
|
|
|
|
@param[in] trx_id trx id for the purging record
|
|
|
|
@param[in,out] v_heap heap used to build vrow
|
|
|
|
@param[out] v_row dtuple holding the virtual rows
|
|
|
|
@param[in,out] mtr mtr holding the latch on rec */
|
|
|
|
static
|
|
|
|
void
|
|
|
|
row_vers_build_cur_vrow_low(
|
2018-07-06 17:13:53 +03:00
|
|
|
bool in_purge,
|
|
|
|
const rec_t* rec,
|
|
|
|
dict_index_t* clust_index,
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* clust_offsets,
|
2018-07-06 17:13:53 +03:00
|
|
|
dict_index_t* index,
|
|
|
|
roll_ptr_t roll_ptr,
|
|
|
|
trx_id_t trx_id,
|
|
|
|
mem_heap_t* v_heap,
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t** vrow,
|
2018-07-06 17:13:53 +03:00
|
|
|
mtr_t* mtr)
|
2016-09-06 09:43:16 +03:00
|
|
|
{
|
|
|
|
const rec_t* version;
|
|
|
|
rec_t* prev_version;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
ulint num_v = dict_table_get_n_v_cols(index->table);
|
|
|
|
const dfield_t* field;
|
|
|
|
ulint i;
|
|
|
|
bool all_filled = false;
|
|
|
|
|
|
|
|
*vrow = dtuple_create_with_vcol(v_heap, 0, num_v);
|
|
|
|
dtuple_init_v_fld(*vrow);
|
|
|
|
|
|
|
|
for (i = 0; i < num_v; i++) {
|
|
|
|
dfield_get_type(dtuple_get_nth_v_field(*vrow, i))->mtype
|
|
|
|
= DATA_MISSING;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = rec;
|
|
|
|
|
|
|
|
/* If this is called by purge thread, set TRX_UNDO_PREV_IN_PURGE
|
|
|
|
bit to search the undo log until we hit the current undo log with
|
|
|
|
roll_ptr */
|
|
|
|
const ulint status = in_purge
|
|
|
|
? TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE
|
|
|
|
: TRX_UNDO_GET_OLD_V_VALUE;
|
|
|
|
|
|
|
|
while (!all_filled) {
|
|
|
|
mem_heap_t* heap2 = heap;
|
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
roll_ptr_t cur_roll_ptr = row_get_rec_roll_ptr(
|
|
|
|
version, clust_index, clust_offsets);
|
|
|
|
|
|
|
|
trx_undo_prev_version_build(
|
|
|
|
rec, mtr, version, clust_index, clust_offsets,
|
|
|
|
heap, &prev_version, NULL, vrow, status);
|
|
|
|
|
|
|
|
if (heap2) {
|
|
|
|
mem_heap_free(heap2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prev_version) {
|
|
|
|
/* Versions end here */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
clust_offsets = rec_get_offsets(prev_version, clust_index,
|
2017-09-19 19:20:11 +03:00
|
|
|
NULL,
|
2021-04-13 10:28:13 +03:00
|
|
|
clust_index->n_core_fields,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
2016-09-06 09:43:16 +03:00
|
|
|
|
|
|
|
ulint entry_len = dict_index_get_n_fields(index);
|
|
|
|
|
|
|
|
all_filled = true;
|
|
|
|
|
|
|
|
for (i = 0; i < entry_len; i++) {
|
|
|
|
const dict_field_t* ind_field
|
|
|
|
= dict_index_get_nth_field(index, i);
|
|
|
|
const dict_col_t* col = ind_field->col;
|
|
|
|
|
2018-05-12 09:42:53 +03:00
|
|
|
if (!col->is_virtual()) {
|
2016-09-06 09:43:16 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dict_v_col_t* v_col
|
|
|
|
= reinterpret_cast<const dict_v_col_t*>(col);
|
|
|
|
field = dtuple_get_nth_v_field(*vrow, v_col->v_pos);
|
|
|
|
|
|
|
|
if (dfield_get_type(field)->mtype == DATA_MISSING) {
|
|
|
|
all_filled = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
trx_id_t rec_trx_id = row_get_rec_trx_id(
|
|
|
|
prev_version, clust_index, clust_offsets);
|
|
|
|
|
|
|
|
if (rec_trx_id < trx_id || roll_ptr == cur_roll_ptr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = prev_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
/** Check a virtual column value index secondary virtual index matches
|
|
|
|
that of current cluster index record, which is recreated from information
|
|
|
|
stored in undo log
|
|
|
|
@param[in] rec record in the clustered index
|
2018-02-01 18:36:03 +02:00
|
|
|
@param[in] icentry the index entry built from a cluster row
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in] clust_index cluster index
|
|
|
|
@param[in] clust_offsets offsets on the cluster record
|
|
|
|
@param[in] index the secondary index
|
|
|
|
@param[in] ientry the secondary index entry
|
|
|
|
@param[in] roll_ptr the rollback pointer for the purging record
|
|
|
|
@param[in] trx_id trx id for the purging record
|
|
|
|
@param[in,out] v_heap heap used to build virtual dtuple
|
|
|
|
@param[in,out] v_row dtuple holding the virtual rows (if needed)
|
|
|
|
@param[in] mtr mtr holding the latch on rec
|
|
|
|
@return true if matches, false otherwise */
|
|
|
|
static
|
|
|
|
bool
|
|
|
|
row_vers_vc_matches_cluster(
|
|
|
|
const rec_t* rec,
|
2018-02-01 18:36:03 +02:00
|
|
|
const dtuple_t* icentry,
|
2016-08-12 11:17:45 +03:00
|
|
|
dict_index_t* clust_index,
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* clust_offsets,
|
2016-08-12 11:17:45 +03:00
|
|
|
dict_index_t* index,
|
|
|
|
const dtuple_t* ientry,
|
|
|
|
roll_ptr_t roll_ptr,
|
|
|
|
trx_id_t trx_id,
|
|
|
|
mem_heap_t* v_heap,
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t** vrow,
|
2016-08-12 11:17:45 +03:00
|
|
|
mtr_t* mtr)
|
|
|
|
{
|
|
|
|
const rec_t* version;
|
|
|
|
rec_t* prev_version;
|
|
|
|
mem_heap_t* heap2;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
mem_heap_t* tuple_heap;
|
|
|
|
ulint num_v = dict_table_get_n_v_cols(index->table);
|
|
|
|
bool compare[REC_MAX_N_FIELDS];
|
|
|
|
ulint n_fields = dtuple_get_n_fields(ientry);
|
|
|
|
ulint n_non_v_col = 0;
|
|
|
|
ulint n_cmp_v_col = 0;
|
|
|
|
const dfield_t* field1;
|
|
|
|
dfield_t* field2;
|
|
|
|
ulint i;
|
|
|
|
|
|
|
|
/* First compare non-virtual columns (primary keys) */
|
2018-02-01 18:36:03 +02:00
|
|
|
ut_ad(index->n_fields == n_fields);
|
|
|
|
ut_ad(n_fields == dtuple_get_n_fields(icentry));
|
|
|
|
{
|
|
|
|
const dfield_t* a = ientry->fields;
|
|
|
|
const dfield_t* b = icentry->fields;
|
|
|
|
|
|
|
|
for (const dict_field_t *ifield = index->fields,
|
|
|
|
*const end = &index->fields[index->n_fields];
|
|
|
|
ifield != end; ifield++, a++, b++) {
|
2018-05-12 09:42:53 +03:00
|
|
|
if (!ifield->col->is_virtual()) {
|
2018-02-01 18:36:03 +02:00
|
|
|
if (cmp_dfield_dfield(a, b)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
n_non_v_col++;
|
|
|
|
}
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
|
2018-02-01 18:36:03 +02:00
|
|
|
tuple_heap = mem_heap_create(1024);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(n_fields > n_non_v_col);
|
|
|
|
|
|
|
|
*vrow = dtuple_create_with_vcol(v_heap ? v_heap : tuple_heap, 0, num_v);
|
|
|
|
dtuple_init_v_fld(*vrow);
|
|
|
|
|
|
|
|
for (i = 0; i < num_v; i++) {
|
|
|
|
dfield_get_type(dtuple_get_nth_v_field(*vrow, i))->mtype
|
|
|
|
= DATA_MISSING;
|
|
|
|
compare[i] = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = rec;
|
|
|
|
|
|
|
|
while (n_cmp_v_col < n_fields - n_non_v_col) {
|
|
|
|
heap2 = heap;
|
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
roll_ptr_t cur_roll_ptr = row_get_rec_roll_ptr(
|
|
|
|
version, clust_index, clust_offsets);
|
|
|
|
|
2016-09-06 09:43:16 +03:00
|
|
|
ut_ad(cur_roll_ptr != 0);
|
2020-04-24 10:42:08 +03:00
|
|
|
ut_ad(roll_ptr != 0);
|
2016-09-06 09:43:16 +03:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
trx_undo_prev_version_build(
|
|
|
|
rec, mtr, version, clust_index, clust_offsets,
|
2020-04-24 10:42:08 +03:00
|
|
|
heap, &prev_version, NULL, vrow,
|
|
|
|
TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
if (heap2) {
|
|
|
|
mem_heap_free(heap2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prev_version) {
|
|
|
|
/* Versions end here */
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
clust_offsets = rec_get_offsets(prev_version, clust_index,
|
2017-09-19 19:20:11 +03:00
|
|
|
NULL,
|
2021-04-13 10:28:13 +03:00
|
|
|
clust_index->n_core_fields,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
ulint entry_len = dict_index_get_n_fields(index);
|
|
|
|
|
|
|
|
for (i = 0; i < entry_len; i++) {
|
|
|
|
const dict_field_t* ind_field
|
|
|
|
= dict_index_get_nth_field(index, i);
|
|
|
|
const dict_col_t* col = ind_field->col;
|
|
|
|
field1 = dtuple_get_nth_field(ientry, i);
|
|
|
|
|
2018-05-12 09:42:53 +03:00
|
|
|
if (!col->is_virtual()) {
|
2016-08-12 11:17:45 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dict_v_col_t* v_col
|
|
|
|
= reinterpret_cast<const dict_v_col_t*>(col);
|
|
|
|
field2
|
|
|
|
= dtuple_get_nth_v_field(*vrow, v_col->v_pos);
|
|
|
|
|
|
|
|
if ((dfield_get_type(field2)->mtype != DATA_MISSING)
|
|
|
|
&& (!compare[v_col->v_pos])) {
|
|
|
|
|
|
|
|
if (ind_field->prefix_len != 0
|
2023-09-19 09:31:34 +03:00
|
|
|
&& !dfield_is_null(field2)) {
|
|
|
|
field2->len = unsigned(
|
|
|
|
dtype_get_at_most_n_mbchars(
|
|
|
|
field2->type.prtype,
|
|
|
|
field2->type.mbminlen,
|
|
|
|
field2->type.mbmaxlen,
|
|
|
|
ind_field->prefix_len,
|
|
|
|
field2->len,
|
|
|
|
static_cast<char*>
|
|
|
|
(field2->data)));
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* The index field mismatch */
|
|
|
|
if (v_heap
|
|
|
|
|| cmp_dfield_dfield(field2, field1) != 0) {
|
|
|
|
if (v_heap) {
|
|
|
|
dtuple_dup_v_fld(*vrow, v_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(tuple_heap);
|
|
|
|
mem_heap_free(heap);
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
compare[v_col->v_pos] = true;
|
|
|
|
n_cmp_v_col++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trx_id_t rec_trx_id = row_get_rec_trx_id(
|
|
|
|
prev_version, clust_index, clust_offsets);
|
|
|
|
|
|
|
|
if (rec_trx_id < trx_id || roll_ptr == cur_roll_ptr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = prev_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
func_exit:
|
|
|
|
if (n_cmp_v_col == 0) {
|
|
|
|
*vrow = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(tuple_heap);
|
|
|
|
mem_heap_free(heap);
|
|
|
|
|
|
|
|
/* FIXME: In the case of n_cmp_v_col is not the same as
|
|
|
|
n_fields - n_non_v_col, callback is needed to compare the rest
|
|
|
|
columns. At the timebeing, we will need to return true */
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Build a dtuple contains virtual column data for current cluster index
|
|
|
|
@param[in] in_purge called by purge thread
|
|
|
|
@param[in] rec cluster index rec
|
|
|
|
@param[in] clust_index cluster index
|
|
|
|
@param[in] clust_offsets cluster rec offset
|
|
|
|
@param[in] index secondary index
|
|
|
|
@param[in] roll_ptr roll_ptr for the purge record
|
|
|
|
@param[in] trx_id transaction ID on the purging record
|
|
|
|
@param[in,out] heap heap memory
|
|
|
|
@param[in,out] v_heap heap memory to keep virtual colum dtuple
|
|
|
|
@param[in] mtr mtr holding the latch on rec
|
2018-07-06 17:13:53 +03:00
|
|
|
@param[in,out] vcol_info virtual column information for purge thread
|
2016-08-12 11:17:45 +03:00
|
|
|
@return dtuple contains virtual column data */
|
|
|
|
static
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t*
|
2016-08-12 11:17:45 +03:00
|
|
|
row_vers_build_cur_vrow(
|
2018-07-06 17:13:53 +03:00
|
|
|
bool in_purge,
|
|
|
|
const rec_t* rec,
|
|
|
|
dict_index_t* clust_index,
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs** clust_offsets,
|
2018-07-06 17:13:53 +03:00
|
|
|
dict_index_t* index,
|
|
|
|
roll_ptr_t roll_ptr,
|
|
|
|
trx_id_t trx_id,
|
|
|
|
mem_heap_t* heap,
|
|
|
|
mem_heap_t* v_heap,
|
|
|
|
mtr_t* mtr,
|
|
|
|
purge_vcol_info_t* vcol_info)
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t* cur_vrow = NULL;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
roll_ptr_t t_roll_ptr = row_get_rec_roll_ptr(
|
|
|
|
rec, clust_index, *clust_offsets);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/* if the row is newly inserted, then the virtual
|
|
|
|
columns need to be computed */
|
|
|
|
if (trx_undo_roll_ptr_is_insert(t_roll_ptr)) {
|
2016-09-06 09:43:16 +03:00
|
|
|
|
|
|
|
ut_ad(!rec_get_deleted_flag(rec, page_rec_is_comp(rec)));
|
|
|
|
|
|
|
|
/* This is a newly inserted record and cannot
|
|
|
|
be deleted, So the externally stored field
|
|
|
|
cannot be freed yet. */
|
|
|
|
dtuple_t* row = row_build(ROW_COPY_POINTERS, clust_index,
|
|
|
|
rec, *clust_offsets,
|
|
|
|
NULL, NULL, NULL, NULL, heap);
|
|
|
|
|
2018-07-23 18:23:54 +03:00
|
|
|
if (vcol_info && !vcol_info->is_used()) {
|
2018-07-06 17:13:53 +03:00
|
|
|
mtr->commit();
|
|
|
|
}
|
|
|
|
|
2020-08-11 00:38:32 +10:00
|
|
|
bool res = row_vers_build_clust_v_col(
|
2018-07-06 17:13:53 +03:00
|
|
|
row, clust_index, index, heap, vcol_info);
|
2020-08-11 00:38:32 +10:00
|
|
|
if (!res) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-07-06 17:13:53 +03:00
|
|
|
|
|
|
|
if (vcol_info != NULL && vcol_info->is_first_fetch()) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
cur_vrow = dtuple_copy(row, v_heap);
|
|
|
|
dtuple_dup_v_fld(cur_vrow, v_heap);
|
|
|
|
} else {
|
2016-09-06 09:43:16 +03:00
|
|
|
/* Try to fetch virtual column data from undo log */
|
|
|
|
row_vers_build_cur_vrow_low(
|
|
|
|
in_purge, rec, clust_index, *clust_offsets,
|
|
|
|
index, roll_ptr, trx_id, v_heap, &cur_vrow, mtr);
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
|
2021-04-13 10:28:13 +03:00
|
|
|
*clust_offsets = rec_get_offsets(rec, clust_index, NULL,
|
|
|
|
clust_index->n_core_fields,
|
2016-08-12 11:17:45 +03:00
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
return(cur_vrow);
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
2023-02-14 14:20:48 +05:30
|
|
|
/** Find out whether data tuple has missing data type
|
2023-02-23 19:56:07 +05:30
|
|
|
for indexed virtual column.
|
2023-02-14 14:20:48 +05:30
|
|
|
@param tuple data tuple
|
2023-02-23 19:56:07 +05:30
|
|
|
@param index virtual index
|
2023-02-14 14:20:48 +05:30
|
|
|
@return true if tuple has missing column type */
|
2023-02-23 19:56:07 +05:30
|
|
|
static bool dtuple_vcol_data_missing(const dtuple_t &tuple,
|
|
|
|
dict_index_t *index)
|
2023-02-14 14:20:48 +05:30
|
|
|
{
|
2023-02-23 19:56:07 +05:30
|
|
|
for (ulint i= 0; i < index->n_uniq; i++)
|
2023-02-14 14:20:48 +05:30
|
|
|
{
|
2023-02-23 19:56:07 +05:30
|
|
|
dict_col_t *col= index->fields[i].col;
|
|
|
|
if (!col->is_virtual())
|
|
|
|
continue;
|
|
|
|
dict_v_col_t *vcol= reinterpret_cast<dict_v_col_t*>(col);
|
|
|
|
for (ulint j= 0; j < index->table->n_v_cols; j++)
|
|
|
|
{
|
|
|
|
if (vcol == &index->table->v_cols[j]
|
|
|
|
&& tuple.v_fields[j].type.mtype == DATA_MISSING)
|
|
|
|
return true;
|
|
|
|
}
|
2023-02-14 14:20:48 +05:30
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-06 17:13:53 +03:00
|
|
|
/** Finds out if a version of the record, where the version >= the current
|
2014-02-26 19:11:54 +01:00
|
|
|
purge view, should have ientry as its secondary index entry. We check
|
|
|
|
if there is any not delete marked version of the record where the trx
|
2018-07-06 17:13:53 +03:00
|
|
|
id >= purge view, and the secondary index entry == ientry; exactly in
|
|
|
|
this case we return TRUE.
|
|
|
|
@param[in] also_curr TRUE if also rec is included in the versions
|
|
|
|
to search; otherwise only versions prior
|
|
|
|
to it are searched
|
|
|
|
@param[in] rec record in the clustered index; the caller
|
|
|
|
must have a latch on the page
|
|
|
|
@param[in] mtr mtr holding the latch on rec; it will
|
|
|
|
also hold the latch on purge_view
|
|
|
|
@param[in] index secondary index
|
|
|
|
@param[in] ientry secondary index entry
|
|
|
|
@param[in] roll_ptr roll_ptr for the purge record
|
|
|
|
@param[in] trx_id transaction ID on the purging record
|
|
|
|
@param[in,out] vcol_info virtual column information for purge thread.
|
2016-08-12 11:17:45 +03:00
|
|
|
@return TRUE if earlier version should have */
|
2018-07-06 17:13:53 +03:00
|
|
|
bool
|
2014-02-26 19:11:54 +01:00
|
|
|
row_vers_old_has_index_entry(
|
2018-07-06 17:13:53 +03:00
|
|
|
bool also_curr,
|
|
|
|
const rec_t* rec,
|
|
|
|
mtr_t* mtr,
|
|
|
|
dict_index_t* index,
|
|
|
|
const dtuple_t* ientry,
|
|
|
|
roll_ptr_t roll_ptr,
|
|
|
|
trx_id_t trx_id,
|
|
|
|
purge_vcol_info_t* vcol_info)
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
|
|
|
const rec_t* version;
|
|
|
|
rec_t* prev_version;
|
|
|
|
dict_index_t* clust_index;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* clust_offsets;
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_t* heap;
|
|
|
|
mem_heap_t* heap2;
|
2016-08-12 11:17:45 +03:00
|
|
|
dtuple_t* row;
|
2014-02-26 19:11:54 +01:00
|
|
|
const dtuple_t* entry;
|
|
|
|
ulint comp;
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t* vrow = NULL;
|
2016-08-12 11:17:45 +03:00
|
|
|
mem_heap_t* v_heap = NULL;
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t* cur_vrow = NULL;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
MDEV-11831 Make InnoDB mini-transaction memo checks stricter
InnoDB keeps track of buffer-fixed buf_block_t or acquired rw_lock_t
within a mini-transaction. There are some memo_contains assertions
in the code that document when certain blocks or rw_locks must be held.
But, these assertions only check the mini-transaction memo, not the fact
whether the rw_lock_t are actually being held by the caller.
btr_pcur_store_position(): Remove #ifdef, and assert that the block
is always buffer-fixed.
rtr_pcur_getnext_from_path(), rtr_pcur_open_low(),
ibuf_rec_get_page_no_func(), ibuf_rec_get_space_func(),
ibuf_rec_get_info_func(), ibuf_rec_get_op_type_func(),
ibuf_build_entry_from_ibuf_rec_func(), ibuf_rec_get_volume_func(),
ibuf_get_merge_page_nos_func(), ibuf_get_volume_buffered_count_func()
ibuf_get_entry_counter_low_func(), page_set_ssn_id(),
row_vers_old_has_index_entry(), row_vers_build_for_consistent_read(),
row_vers_build_for_semi_consistent_read(),
trx_undo_prev_version_build():
Make use of mtr_memo_contains_page_flagged().
mtr_t::memo_contains(): Take a const memo. Assert rw_lock_own().
FindPage, FlaggedCheck: Assert rw_lock_own_flagged().
2017-01-18 12:53:35 +02:00
|
|
|
ut_ad(mtr_memo_contains_page_flagged(mtr, rec, MTR_MEMO_PAGE_X_FIX
|
|
|
|
| MTR_MEMO_PAGE_S_FIX));
|
2019-07-24 15:34:24 +03:00
|
|
|
ut_ad(!rw_lock_own(&purge_sys.latch, RW_LOCK_S));
|
2019-07-24 16:34:14 +05:30
|
|
|
ut_ad(also_curr || !vcol_info);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
clust_index = dict_table_get_first_index(index->table);
|
|
|
|
|
|
|
|
comp = page_rec_is_comp(rec);
|
|
|
|
ut_ad(!dict_table_is_comp(index->table) == !comp);
|
|
|
|
heap = mem_heap_create(1024);
|
2021-04-13 10:28:13 +03:00
|
|
|
clust_offsets = rec_get_offsets(rec, clust_index, NULL,
|
|
|
|
clust_index->n_core_fields,
|
2014-02-26 19:11:54 +01:00
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
|
|
|
v_heap = mem_heap_create(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_purge_virtual_index_crash",
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
if (also_curr && !rec_get_deleted_flag(rec, comp)) {
|
|
|
|
row_ext_t* ext;
|
|
|
|
|
|
|
|
/* The top of the stack of versions is locked by the
|
|
|
|
mtr holding a latch on the page containing the
|
|
|
|
clustered index record. The bottom of the stack is
|
2018-02-22 09:30:41 +02:00
|
|
|
locked by the fact that the purge_sys.view must
|
2014-02-26 19:11:54 +01:00
|
|
|
'overtake' any read view of an active transaction.
|
|
|
|
Thus, it is safe to fetch the prefixes for
|
|
|
|
externally stored columns. */
|
|
|
|
row = row_build(ROW_COPY_POINTERS, clust_index,
|
|
|
|
rec, clust_offsets,
|
|
|
|
NULL, NULL, NULL, &ext, heap);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
2016-09-06 09:43:16 +03:00
|
|
|
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
#ifdef DBUG_OFF
|
|
|
|
# define dbug_v_purge false
|
|
|
|
#else /* DBUG_OFF */
|
|
|
|
bool dbug_v_purge = false;
|
|
|
|
#endif /* DBUG_OFF */
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"ib_purge_virtual_index_callback",
|
|
|
|
dbug_v_purge = true;);
|
|
|
|
|
|
|
|
roll_ptr_t t_roll_ptr = row_get_rec_roll_ptr(
|
|
|
|
rec, clust_index, clust_offsets);
|
|
|
|
|
|
|
|
/* if the row is newly inserted, then the virtual
|
|
|
|
columns need to be computed */
|
|
|
|
if (trx_undo_roll_ptr_is_insert(t_roll_ptr)
|
|
|
|
|| dbug_v_purge) {
|
2018-07-06 17:13:53 +03:00
|
|
|
|
2018-07-23 18:23:54 +03:00
|
|
|
if (vcol_info && !vcol_info->is_used()) {
|
2018-07-06 17:13:53 +03:00
|
|
|
mtr->commit();
|
|
|
|
}
|
|
|
|
|
2020-08-11 00:38:32 +10:00
|
|
|
bool res = row_vers_build_clust_v_col(
|
2018-07-06 17:13:53 +03:00
|
|
|
row, clust_index, index, heap,
|
|
|
|
vcol_info);
|
|
|
|
|
2020-08-11 00:38:32 +10:00
|
|
|
if (!res) {
|
|
|
|
goto unsafe_to_purge;
|
|
|
|
}
|
|
|
|
|
2018-07-06 17:13:53 +03:00
|
|
|
if (vcol_info && vcol_info->is_first_fetch()) {
|
|
|
|
goto unsafe_to_purge;
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
entry = row_build_index_entry(
|
|
|
|
row, ext, index, heap);
|
|
|
|
if (entry && !dtuple_coll_cmp(ientry, entry)) {
|
2019-07-24 16:34:14 +05:30
|
|
|
goto unsafe_to_purge;
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
} else {
|
2018-02-01 18:36:03 +02:00
|
|
|
/* Build index entry out of row */
|
|
|
|
entry = row_build_index_entry(row, ext, index, heap);
|
|
|
|
/* entry could only be NULL if
|
|
|
|
the clustered index record is an uncommitted
|
|
|
|
inserted record whose BLOBs have not been
|
|
|
|
written yet. The secondary index record
|
|
|
|
can be safely removed, because it cannot
|
|
|
|
possibly refer to this incomplete
|
|
|
|
clustered index record. (Insert would
|
|
|
|
always first be completed for the
|
|
|
|
clustered index record, then proceed to
|
|
|
|
secondary indexes.) */
|
|
|
|
|
|
|
|
if (entry && row_vers_vc_matches_cluster(
|
2020-04-24 10:42:08 +03:00
|
|
|
rec, entry,
|
2018-02-01 18:36:03 +02:00
|
|
|
clust_index, clust_offsets,
|
|
|
|
index, ientry, roll_ptr,
|
|
|
|
trx_id, NULL, &vrow, mtr)) {
|
2019-07-24 16:34:14 +05:30
|
|
|
goto unsafe_to_purge;
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
clust_offsets = rec_get_offsets(rec, clust_index, NULL,
|
2021-04-13 10:28:13 +03:00
|
|
|
clust_index
|
|
|
|
->n_core_fields,
|
2016-08-12 11:17:45 +03:00
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
} else {
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
entry = row_build_index_entry(
|
|
|
|
row, ext, index, heap);
|
|
|
|
|
|
|
|
/* If entry == NULL, the record contains unset BLOB
|
|
|
|
pointers. This must be a freshly inserted record. If
|
|
|
|
this is called from
|
|
|
|
row_purge_remove_sec_if_poss_low(), the thread will
|
|
|
|
hold latches on the clustered index and the secondary
|
|
|
|
index. Because the insert works in three steps:
|
|
|
|
|
|
|
|
(1) insert the record to clustered index
|
|
|
|
(2) store the BLOBs and update BLOB pointers
|
|
|
|
(3) insert records to secondary indexes
|
|
|
|
|
|
|
|
the purge thread can safely ignore freshly inserted
|
|
|
|
records and delete the secondary index record. The
|
|
|
|
thread that inserted the new record will be inserting
|
|
|
|
the secondary index records. */
|
|
|
|
|
|
|
|
/* NOTE that we cannot do the comparison as binary
|
|
|
|
fields because the row is maybe being modified so that
|
|
|
|
the clustered index record has already been updated to
|
|
|
|
a different binary value in a char field, but the
|
|
|
|
collation identifies the old and new value anyway! */
|
|
|
|
if (entry && !dtuple_coll_cmp(ientry, entry)) {
|
2019-07-24 16:34:14 +05:30
|
|
|
unsafe_to_purge:
|
2016-08-12 11:17:45 +03:00
|
|
|
mem_heap_free(heap);
|
|
|
|
|
|
|
|
if (v_heap) {
|
|
|
|
mem_heap_free(v_heap);
|
|
|
|
}
|
2018-07-06 17:13:53 +03:00
|
|
|
return true;
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
} else if (dict_index_has_virtual(index)) {
|
|
|
|
/* The current cluster index record could be
|
|
|
|
deleted, but the previous version of it might not. We will
|
|
|
|
need to get the virtual column data from undo record
|
|
|
|
associated with current cluster index */
|
2018-07-06 17:13:53 +03:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
cur_vrow = row_vers_build_cur_vrow(
|
|
|
|
also_curr, rec, clust_index, &clust_offsets,
|
2018-07-06 18:17:24 +03:00
|
|
|
index, roll_ptr, trx_id, heap, v_heap, mtr, vcol_info);
|
2018-07-06 17:13:53 +03:00
|
|
|
|
|
|
|
if (vcol_info && vcol_info->is_first_fetch()) {
|
|
|
|
goto unsafe_to_purge;
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
version = rec;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
heap2 = heap;
|
|
|
|
heap = mem_heap_create(1024);
|
2016-08-12 11:17:45 +03:00
|
|
|
vrow = NULL;
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
trx_undo_prev_version_build(rec, mtr, version,
|
|
|
|
clust_index, clust_offsets,
|
2016-08-12 11:17:45 +03:00
|
|
|
heap, &prev_version, NULL,
|
|
|
|
dict_index_has_virtual(index)
|
|
|
|
? &vrow : NULL, 0);
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_free(heap2); /* free version and clust_offsets */
|
|
|
|
|
|
|
|
if (!prev_version) {
|
|
|
|
/* Versions end here */
|
|
|
|
mem_heap_free(heap);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (v_heap) {
|
|
|
|
mem_heap_free(v_heap);
|
|
|
|
}
|
|
|
|
|
2018-07-06 17:13:53 +03:00
|
|
|
return false;
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
clust_offsets = rec_get_offsets(prev_version, clust_index,
|
2021-04-13 10:28:13 +03:00
|
|
|
NULL,
|
|
|
|
clust_index->n_core_fields,
|
2017-09-19 19:20:11 +03:00
|
|
|
ULINT_UNDEFINED, &heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
|
|
|
if (vrow) {
|
2023-02-23 19:56:07 +05:30
|
|
|
if (dtuple_vcol_data_missing(*vrow, index)) {
|
2023-02-14 14:20:48 +05:30
|
|
|
goto nochange_index;
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
/* Keep the virtual row info for the next
|
|
|
|
version, unless it is changed */
|
|
|
|
mem_heap_empty(v_heap);
|
|
|
|
cur_vrow = dtuple_copy(vrow, v_heap);
|
|
|
|
dtuple_dup_v_fld(cur_vrow, v_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cur_vrow) {
|
|
|
|
/* Nothing for this index has changed,
|
|
|
|
continue */
|
2023-02-14 14:20:48 +05:30
|
|
|
nochange_index:
|
2016-08-12 11:17:45 +03:00
|
|
|
version = prev_version;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
if (!rec_get_deleted_flag(prev_version, comp)) {
|
|
|
|
row_ext_t* ext;
|
|
|
|
|
|
|
|
/* The stack of versions is locked by mtr.
|
|
|
|
Thus, it is safe to fetch the prefixes for
|
|
|
|
externally stored columns. */
|
|
|
|
row = row_build(ROW_COPY_POINTERS, clust_index,
|
|
|
|
prev_version, clust_offsets,
|
|
|
|
NULL, NULL, NULL, &ext, heap);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
if (dict_index_has_virtual(index)) {
|
|
|
|
ut_ad(cur_vrow);
|
|
|
|
ut_ad(row->n_v_fields == cur_vrow->n_v_fields);
|
|
|
|
dtuple_copy_v_fields(row, cur_vrow);
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:11:54 +01:00
|
|
|
entry = row_build_index_entry(row, ext, index, heap);
|
|
|
|
|
|
|
|
/* If entry == NULL, the record contains unset
|
|
|
|
BLOB pointers. This must be a freshly
|
|
|
|
inserted record that we can safely ignore.
|
|
|
|
For the justification, see the comments after
|
|
|
|
the previous row_build_index_entry() call. */
|
|
|
|
|
|
|
|
/* NOTE that we cannot do the comparison as binary
|
|
|
|
fields because maybe the secondary index record has
|
|
|
|
already been updated to a different binary value in
|
|
|
|
a char field, but the collation identifies the old
|
|
|
|
and new value anyway! */
|
|
|
|
|
|
|
|
if (entry && !dtuple_coll_cmp(ientry, entry)) {
|
2019-07-24 16:34:14 +05:30
|
|
|
goto unsafe_to_purge;
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
version = prev_version;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************//**
|
|
|
|
Constructs the version of a clustered index record which a consistent
|
|
|
|
read should see. We assume that the trx id stored in rec is such that
|
|
|
|
the consistent read should not see rec in its present version.
|
2016-08-12 11:17:45 +03:00
|
|
|
@return DB_SUCCESS or DB_MISSING_HISTORY */
|
2014-02-26 19:11:54 +01:00
|
|
|
dberr_t
|
|
|
|
row_vers_build_for_consistent_read(
|
|
|
|
/*===============================*/
|
|
|
|
const rec_t* rec, /*!< in: record in a clustered index; the
|
|
|
|
caller must have a latch on the page; this
|
|
|
|
latch locks the top of the stack of versions
|
|
|
|
of this records */
|
|
|
|
mtr_t* mtr, /*!< in: mtr holding the latch on rec */
|
|
|
|
dict_index_t* index, /*!< in: the clustered index */
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs** offsets,/*!< in/out: offsets returned by
|
2014-02-26 19:11:54 +01:00
|
|
|
rec_get_offsets(rec, index) */
|
2016-08-12 11:17:45 +03:00
|
|
|
ReadView* view, /*!< in: the consistent read view */
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_t** offset_heap,/*!< in/out: memory heap from which
|
|
|
|
the offsets are allocated */
|
|
|
|
mem_heap_t* in_heap,/*!< in: memory heap from which the memory for
|
|
|
|
*old_vers is allocated; memory for possible
|
|
|
|
intermediate versions is allocated and freed
|
|
|
|
locally within the function */
|
2016-08-12 11:17:45 +03:00
|
|
|
rec_t** old_vers,/*!< out, own: old version, or NULL
|
2014-02-26 19:11:54 +01:00
|
|
|
if the history is missing or the record
|
|
|
|
does not exist in the view, that is,
|
|
|
|
it was freshly inserted afterwards */
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t** vrow) /*!< out: virtual row */
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
|
|
|
const rec_t* version;
|
|
|
|
rec_t* prev_version;
|
|
|
|
trx_id_t trx_id;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
byte* buf;
|
|
|
|
dberr_t err;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
MDEV-11831 Make InnoDB mini-transaction memo checks stricter
InnoDB keeps track of buffer-fixed buf_block_t or acquired rw_lock_t
within a mini-transaction. There are some memo_contains assertions
in the code that document when certain blocks or rw_locks must be held.
But, these assertions only check the mini-transaction memo, not the fact
whether the rw_lock_t are actually being held by the caller.
btr_pcur_store_position(): Remove #ifdef, and assert that the block
is always buffer-fixed.
rtr_pcur_getnext_from_path(), rtr_pcur_open_low(),
ibuf_rec_get_page_no_func(), ibuf_rec_get_space_func(),
ibuf_rec_get_info_func(), ibuf_rec_get_op_type_func(),
ibuf_build_entry_from_ibuf_rec_func(), ibuf_rec_get_volume_func(),
ibuf_get_merge_page_nos_func(), ibuf_get_volume_buffered_count_func()
ibuf_get_entry_counter_low_func(), page_set_ssn_id(),
row_vers_old_has_index_entry(), row_vers_build_for_consistent_read(),
row_vers_build_for_semi_consistent_read(),
trx_undo_prev_version_build():
Make use of mtr_memo_contains_page_flagged().
mtr_t::memo_contains(): Take a const memo. Assert rw_lock_own().
FindPage, FlaggedCheck: Assert rw_lock_own_flagged().
2017-01-18 12:53:35 +02:00
|
|
|
ut_ad(mtr_memo_contains_page_flagged(mtr, rec, MTR_MEMO_PAGE_X_FIX
|
|
|
|
| MTR_MEMO_PAGE_S_FIX));
|
2018-02-22 09:30:41 +02:00
|
|
|
ut_ad(!rw_lock_own(&(purge_sys.latch), RW_LOCK_S));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
ut_ad(rec_offs_validate(rec, index, *offsets));
|
|
|
|
|
|
|
|
trx_id = row_get_rec_trx_id(rec, index, *offsets);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(!view->changes_visible(trx_id, index->table->name));
|
|
|
|
|
|
|
|
ut_ad(!vrow || !(*vrow));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
version = rec;
|
|
|
|
|
|
|
|
for (;;) {
|
2016-08-12 11:17:45 +03:00
|
|
|
mem_heap_t* prev_heap = heap;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
heap = mem_heap_create(1024);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (vrow) {
|
|
|
|
*vrow = NULL;
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/* If purge can't see the record then we can't rely on
|
|
|
|
the UNDO log record. */
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
bool purge_sees = trx_undo_prev_version_build(
|
|
|
|
rec, mtr, version, index, *offsets, heap,
|
|
|
|
&prev_version, NULL, vrow, 0);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
err = (purge_sees) ? DB_SUCCESS : DB_MISSING_HISTORY;
|
2014-02-26 19:11:54 +01:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (prev_heap != NULL) {
|
|
|
|
mem_heap_free(prev_heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (prev_version == NULL) {
|
|
|
|
/* It was a freshly inserted version */
|
|
|
|
*old_vers = NULL;
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(!vrow || !(*vrow));
|
2014-02-26 19:11:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
*offsets = rec_get_offsets(
|
2017-09-19 19:20:11 +03:00
|
|
|
prev_version, index, *offsets,
|
2021-04-13 10:28:13 +03:00
|
|
|
index->n_core_fields, ULINT_UNDEFINED, offset_heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
|
|
|
ut_a(!rec_offs_any_null_extern(prev_version, *offsets));
|
|
|
|
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
|
|
|
|
|
|
|
trx_id = row_get_rec_trx_id(prev_version, index, *offsets);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (view->changes_visible(trx_id, index->table->name)) {
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
/* The view already sees this version: we can copy
|
|
|
|
it to in_heap and return */
|
|
|
|
|
|
|
|
buf = static_cast<byte*>(
|
|
|
|
mem_heap_alloc(
|
|
|
|
in_heap, rec_offs_size(*offsets)));
|
|
|
|
|
|
|
|
*old_vers = rec_copy(buf, prev_version, *offsets);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
rec_offs_make_valid(*old_vers, index, true, *offsets);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
if (vrow && *vrow) {
|
|
|
|
*vrow = dtuple_copy(*vrow, in_heap);
|
|
|
|
dtuple_dup_v_fld(*vrow, in_heap);
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = prev_version;
|
2016-08-12 11:17:45 +03:00
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************//**
|
|
|
|
Constructs the last committed version of a clustered index record,
|
|
|
|
which should be seen by a semi-consistent read. */
|
|
|
|
void
|
|
|
|
row_vers_build_for_semi_consistent_read(
|
|
|
|
/*====================================*/
|
2017-12-13 15:40:41 +04:00
|
|
|
trx_t* caller_trx,/*!<in/out: trx of current thread */
|
2014-02-26 19:11:54 +01:00
|
|
|
const rec_t* rec, /*!< in: record in a clustered index; the
|
|
|
|
caller must have a latch on the page; this
|
|
|
|
latch locks the top of the stack of versions
|
|
|
|
of this records */
|
|
|
|
mtr_t* mtr, /*!< in: mtr holding the latch on rec */
|
|
|
|
dict_index_t* index, /*!< in: the clustered index */
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs** offsets,/*!< in/out: offsets returned by
|
2014-02-26 19:11:54 +01:00
|
|
|
rec_get_offsets(rec, index) */
|
|
|
|
mem_heap_t** offset_heap,/*!< in/out: memory heap from which
|
|
|
|
the offsets are allocated */
|
|
|
|
mem_heap_t* in_heap,/*!< in: memory heap from which the memory for
|
|
|
|
*old_vers is allocated; memory for possible
|
|
|
|
intermediate versions is allocated and freed
|
|
|
|
locally within the function */
|
2016-08-12 11:17:45 +03:00
|
|
|
const rec_t** old_vers,/*!< out: rec, old version, or NULL if the
|
2014-02-26 19:11:54 +01:00
|
|
|
record does not exist in the view, that is,
|
|
|
|
it was freshly inserted afterwards */
|
2019-05-03 16:47:07 +03:00
|
|
|
dtuple_t** vrow) /*!< out: virtual row, old version, or NULL
|
2016-08-12 11:17:45 +03:00
|
|
|
if it is not updated in the view */
|
2014-02-26 19:11:54 +01:00
|
|
|
{
|
|
|
|
const rec_t* version;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
byte* buf;
|
|
|
|
trx_id_t rec_trx_id = 0;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
MDEV-11831 Make InnoDB mini-transaction memo checks stricter
InnoDB keeps track of buffer-fixed buf_block_t or acquired rw_lock_t
within a mini-transaction. There are some memo_contains assertions
in the code that document when certain blocks or rw_locks must be held.
But, these assertions only check the mini-transaction memo, not the fact
whether the rw_lock_t are actually being held by the caller.
btr_pcur_store_position(): Remove #ifdef, and assert that the block
is always buffer-fixed.
rtr_pcur_getnext_from_path(), rtr_pcur_open_low(),
ibuf_rec_get_page_no_func(), ibuf_rec_get_space_func(),
ibuf_rec_get_info_func(), ibuf_rec_get_op_type_func(),
ibuf_build_entry_from_ibuf_rec_func(), ibuf_rec_get_volume_func(),
ibuf_get_merge_page_nos_func(), ibuf_get_volume_buffered_count_func()
ibuf_get_entry_counter_low_func(), page_set_ssn_id(),
row_vers_old_has_index_entry(), row_vers_build_for_consistent_read(),
row_vers_build_for_semi_consistent_read(),
trx_undo_prev_version_build():
Make use of mtr_memo_contains_page_flagged().
mtr_t::memo_contains(): Take a const memo. Assert rw_lock_own().
FindPage, FlaggedCheck: Assert rw_lock_own_flagged().
2017-01-18 12:53:35 +02:00
|
|
|
ut_ad(mtr_memo_contains_page_flagged(mtr, rec, MTR_MEMO_PAGE_X_FIX
|
|
|
|
| MTR_MEMO_PAGE_S_FIX));
|
2018-02-22 09:30:41 +02:00
|
|
|
ut_ad(!rw_lock_own(&(purge_sys.latch), RW_LOCK_S));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
ut_ad(rec_offs_validate(rec, index, *offsets));
|
|
|
|
|
|
|
|
version = rec;
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(!vrow || !(*vrow));
|
2014-02-26 19:11:54 +01:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
mem_heap_t* heap2;
|
|
|
|
rec_t* prev_version;
|
|
|
|
trx_id_t version_trx_id;
|
|
|
|
|
|
|
|
version_trx_id = row_get_rec_trx_id(version, index, *offsets);
|
|
|
|
if (rec == version) {
|
|
|
|
rec_trx_id = version_trx_id;
|
|
|
|
}
|
|
|
|
|
2018-01-25 18:29:59 +04:00
|
|
|
if (!trx_sys.is_registered(caller_trx, version_trx_id)) {
|
2014-02-26 19:11:54 +01:00
|
|
|
committed_version_trx:
|
|
|
|
/* We found a version that belongs to a
|
|
|
|
committed transaction: return it. */
|
|
|
|
|
|
|
|
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
|
|
|
ut_a(!rec_offs_any_null_extern(version, *offsets));
|
|
|
|
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
|
|
|
|
|
|
|
if (rec == version) {
|
|
|
|
*old_vers = rec;
|
2016-08-12 11:17:45 +03:00
|
|
|
if (vrow) {
|
|
|
|
*vrow = NULL;
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We assume that a rolled-back transaction stays in
|
|
|
|
TRX_STATE_ACTIVE state until all the changes have been
|
|
|
|
rolled back and the transaction is removed from
|
|
|
|
the global list of transactions. */
|
|
|
|
|
|
|
|
if (rec_trx_id == version_trx_id) {
|
|
|
|
/* The transaction was committed while
|
|
|
|
we searched for earlier versions.
|
|
|
|
Return the current version as a
|
|
|
|
semi-consistent read. */
|
|
|
|
|
|
|
|
version = rec;
|
2021-04-13 10:28:13 +03:00
|
|
|
*offsets = rec_get_offsets(
|
|
|
|
version, index, *offsets,
|
|
|
|
index->n_core_fields, ULINT_UNDEFINED,
|
|
|
|
offset_heap);
|
2014-02-26 19:11:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
buf = static_cast<byte*>(
|
|
|
|
mem_heap_alloc(
|
|
|
|
in_heap, rec_offs_size(*offsets)));
|
|
|
|
|
|
|
|
*old_vers = rec_copy(buf, version, *offsets);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
rec_offs_make_valid(*old_vers, index, true, *offsets);
|
2016-08-12 11:17:45 +03:00
|
|
|
if (vrow && *vrow) {
|
|
|
|
*vrow = dtuple_copy(*vrow, in_heap);
|
|
|
|
dtuple_dup_v_fld(*vrow, in_heap);
|
|
|
|
}
|
2014-02-26 19:11:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_SYNC_C("after_row_vers_check_trx_active");
|
|
|
|
|
|
|
|
heap2 = heap;
|
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
|
|
|
|
if (!trx_undo_prev_version_build(rec, mtr, version, index,
|
|
|
|
*offsets, heap,
|
2016-08-12 11:17:45 +03:00
|
|
|
&prev_version,
|
|
|
|
in_heap, vrow, 0)) {
|
2014-02-26 19:11:54 +01:00
|
|
|
mem_heap_free(heap);
|
|
|
|
heap = heap2;
|
|
|
|
heap2 = NULL;
|
|
|
|
goto committed_version_trx;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (heap2) {
|
|
|
|
mem_heap_free(heap2); /* free version */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prev_version == NULL) {
|
|
|
|
/* It was a freshly inserted version */
|
|
|
|
*old_vers = NULL;
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(!vrow || !(*vrow));
|
2014-02-26 19:11:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
version = prev_version;
|
2021-04-13 10:28:13 +03:00
|
|
|
*offsets = rec_get_offsets(version, index, *offsets,
|
|
|
|
index->n_core_fields,
|
2014-02-26 19:11:54 +01:00
|
|
|
ULINT_UNDEFINED, offset_heap);
|
|
|
|
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
|
|
|
ut_a(!rec_offs_any_null_extern(version, *offsets));
|
|
|
|
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
|
|
|
}/* for (;;) */
|
|
|
|
|
|
|
|
if (heap) {
|
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
|
|
|
}
|