Browse Source

Experiment: Remove handler::unlock_row() and friends

Failing test(s): innodb.innodb main.partition_innodb_semi_consistent innodb.innodb-semi-consistent innodb.innodb_lock_wait_timeout_1 main.concurrent_innodb_unsafelog main.unsafe_binlog_innodb innodb.create_table_insert_skip_locked rpl.rpl_unsafe_statements innodb.partition_locking innodb.skip_locked_nowait innodb.deadlock_wait_lock_race
10.11-MDEV-16232-remove-unlock_row
Marko Mäkelä 2 years ago
parent
commit
d98922789e
  1. 7
      sql/filesort.cc
  2. 88
      sql/ha_partition.cc
  3. 12
      sql/ha_partition.h
  4. 14
      sql/handler.cc
  5. 22
      sql/handler.h
  6. 10
      sql/multi_range_read.cc
  7. 30
      sql/opt_range.cc
  8. 8
      sql/sql_delete.cc
  9. 9
      sql/sql_select.cc
  10. 25
      sql/sql_update.cc
  11. 66
      storage/innobase/handler/ha_innodb.cc
  12. 6
      storage/innobase/handler/ha_innodb.h
  13. 13
      storage/innobase/include/lock0lock.h
  14. 62
      storage/innobase/include/row0mysql.h
  15. 83
      storage/innobase/lock/lock0lock.cc
  16. 83
      storage/innobase/row/row0mysql.cc
  17. 219
      storage/innobase/row/row0sel.cc

7
sql/filesort.cc

@ -989,13 +989,6 @@ static ha_rows find_all_keys(THD *thd, Sort_param *param, SQL_SELECT *select,
/* It does not make sense to read more keys in case of a fatal error */
if (unlikely(thd->is_error()))
break;
/*
We need to this after checking the error as the transaction may have
rolled back in case of a deadlock
*/
if (!write_record)
file->unlock_row();
}
if (!quick_select)
{

88
sql/ha_partition.cc

@ -4380,94 +4380,6 @@ uint ha_partition::lock_count() const
}
/*
Unlock last accessed row
SYNOPSIS
unlock_row()
RETURN VALUE
NONE
DESCRIPTION
Record currently processed was not in the result set of the statement
and is thus unlocked. Used for UPDATE and DELETE queries.
*/
void ha_partition::unlock_row()
{
DBUG_ENTER("ha_partition::unlock_row");
m_file[m_last_part]->unlock_row();
DBUG_VOID_RETURN;
}
/**
Check if semi consistent read was used
SYNOPSIS
was_semi_consistent_read()
RETURN VALUE
TRUE Previous read was a semi consistent read
FALSE Previous read was not a semi consistent read
DESCRIPTION
See handler.h:
In an UPDATE or DELETE, if the row under the cursor was locked by another
transaction, and the engine used an optimistic read of the last
committed row value under the cursor, then the engine returns 1 from this
function. MySQL must NOT try to update this optimistic value. If the
optimistic value does not match the WHERE condition, MySQL can decide to
skip over this row. Currently only works for InnoDB. This can be used to
avoid unnecessary lock waits.
If this method returns nonzero, it will also signal the storage
engine that the next read will be a locking re-read of the row.
*/
bool ha_partition::was_semi_consistent_read()
{
DBUG_ENTER("ha_partition::was_semi_consistent_read");
DBUG_ASSERT(m_last_part < m_tot_parts);
DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), m_last_part));
DBUG_RETURN(m_file[m_last_part]->was_semi_consistent_read());
}
/**
Use semi consistent read if possible
SYNOPSIS
try_semi_consistent_read()
yes Turn on semi consistent read
RETURN VALUE
NONE
DESCRIPTION
See handler.h:
Tell the engine whether it should avoid unnecessary lock waits.
If yes, in an UPDATE or DELETE, if the row under the cursor was locked
by another transaction, the engine may try an optimistic read of
the last committed row value under the cursor.
Note: prune_partitions are already called before this call, so using
pruning is OK.
*/
void ha_partition::try_semi_consistent_read(bool yes)
{
uint i;
DBUG_ENTER("ha_partition::try_semi_consistent_read");
i= bitmap_get_first_set(&(m_part_info->read_partitions));
DBUG_ASSERT(i != MY_BIT_NONE);
for (;
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
m_file[i]->try_semi_consistent_read(yes);
}
DBUG_VOID_RETURN;
}
/****************************************************************************
MODULE change record
****************************************************************************/

12
sql/ha_partition.h

@ -656,18 +656,6 @@ public:
Lock count is number of locked underlying handlers (I assume)
*/
uint lock_count() const override;
/*
Call to unlock rows not to be updated in transaction
*/
void unlock_row() override;
/*
Check if semi consistent read
*/
bool was_semi_consistent_read() override;
/*
Call to hint about semi consistent read
*/
void try_semi_consistent_read(bool) override;
/*
NOTE: due to performance and resource issues with many partitions,

14
sql/handler.cc

@ -3649,14 +3649,6 @@ int handler::ha_index_next_same(uchar *buf, const uchar *key, uint keylen)
}
bool handler::ha_was_semi_consistent_read()
{
bool result= was_semi_consistent_read();
if (result)
increment_statistics(&SSV::ha_read_retry_count);
return result;
}
/* Initialize handler for random reading, with error handling */
int handler::ha_rnd_init_with_error(bool scan)
@ -6666,7 +6658,6 @@ int handler::read_range_first(const key_range *start_key,
The last read row does not fall in the range. So request
storage engine to release row lock if possible.
*/
unlock_row();
DBUG_RETURN(HA_ERR_END_OF_FILE);
}
}
@ -6707,11 +6698,6 @@ int handler::read_range_next()
}
else
{
/*
The last read row does not fall in the range. So request
storage engine to release row lock if possible.
*/
unlock_row();
DBUG_RETURN(HA_ERR_END_OF_FILE);
}
}

22
sql/handler.h

@ -4028,28 +4028,6 @@ public:
*/
virtual ulonglong table_version() const { return 0; }
/**
In an UPDATE or DELETE, if the row under the cursor was locked by another
transaction, and the engine used an optimistic read of the last
committed row value under the cursor, then the engine returns 1 from this
function. MySQL must NOT try to update this optimistic value. If the
optimistic value does not match the WHERE condition, MySQL can decide to
skip over this row. Currently only works for InnoDB. This can be used to
avoid unnecessary lock waits.
If this method returns nonzero, it will also signal the storage
engine that the next read will be a locking re-read of the row.
*/
bool ha_was_semi_consistent_read();
virtual bool was_semi_consistent_read() { return 0; }
/**
Tell the engine whether it should avoid unnecessary lock waits.
If yes, in an UPDATE or DELETE, if the row under the cursor was locked
by another transaction, the engine may try an optimistic read of
the last committed row value under the cursor.
*/
virtual void try_semi_consistent_read(bool) {}
virtual void unlock_row() {}
virtual int start_stmt(THD *thd, thr_lock_type lock_type) {return 0;}
virtual bool need_info_for_auto_inc() { return 0; }
virtual bool can_use_for_auto_inc_init() { return 1; }

10
sql/multi_range_read.cc

@ -494,15 +494,6 @@ int handler::multi_range_read_next(range_id_t *range_info)
}
else
{
if (ha_was_semi_consistent_read())
{
/*
The following assignment is redundant, but for extra safety and to
remove the compiler warning:
*/
range_res= FALSE;
goto scan_it_again;
}
/*
We need to set this for the last range only, but checking this
condition is more expensive than just setting the result code.
@ -514,7 +505,6 @@ start:
/* Try the next range(s) until one matches a record. */
while (!(range_res= mrr_funcs.next(mrr_iter, &mrr_cur_range)))
{
scan_it_again:
result= read_range_first(mrr_cur_range.start_key.keypart_map ?
&mrr_cur_range.start_key : 0,
mrr_cur_range.end_key.keypart_map ?

30
sql/opt_range.cc

@ -12444,12 +12444,6 @@ int QUICK_ROR_INTERSECT_SELECT::get_next()
List_iterator_fast<QUICK_SELECT_WITH_RECORD> quick_it(quick_selects);
QUICK_SELECT_WITH_RECORD *qr;
QUICK_RANGE_SELECT* quick;
/* quick that reads the given rowid first. This is needed in order
to be able to unlock the row using the same handler object that locked
it */
QUICK_RANGE_SELECT* quick_with_last_rowid;
int error, cmp;
uint last_rowid_count=0;
DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::get_next");
@ -12461,10 +12455,7 @@ int QUICK_ROR_INTERSECT_SELECT::get_next()
if (cpk_quick)
{
while (!error && !cpk_quick->row_in_ranges())
{
quick->file->unlock_row(); /* row not in range; unlock */
error= quick->get_next();
}
}
if (unlikely(error))
DBUG_RETURN(error);
@ -12476,7 +12467,6 @@ int QUICK_ROR_INTERSECT_SELECT::get_next()
quick->file->position(quick->record);
memcpy(last_rowid, quick->file->ref, head->file->ref_length);
last_rowid_count= 1;
quick_with_last_rowid= quick;
while (last_rowid_count < quick_selects.elements)
{
@ -12492,19 +12482,9 @@ int QUICK_ROR_INTERSECT_SELECT::get_next()
DBUG_EXECUTE_IF("innodb_quick_report_deadlock",
DBUG_SET("+d,innodb_report_deadlock"););
if (unlikely((error= quick->get_next())))
{
/* On certain errors like deadlock, trx might be rolled back.*/
if (!thd->transaction_rollback_request)
quick_with_last_rowid->file->unlock_row();
DBUG_RETURN(error);
}
quick->file->position(quick->record);
cmp= head->file->cmp_ref(quick->file->ref, last_rowid);
if (cmp < 0)
{
/* This row is being skipped. Release lock on it. */
quick->file->unlock_row();
}
} while (cmp < 0);
key_copy(qr->key_tuple, record, head->key_info + quick->index,
@ -12517,22 +12497,12 @@ int QUICK_ROR_INTERSECT_SELECT::get_next()
if (cpk_quick)
{
while (!cpk_quick->row_in_ranges())
{
quick->file->unlock_row(); /* row not in range; unlock */
if (unlikely((error= quick->get_next())))
{
/* On certain errors like deadlock, trx might be rolled back.*/
if (!thd->transaction_rollback_request)
quick_with_last_rowid->file->unlock_row();
DBUG_RETURN(error);
}
}
quick->file->position(quick->record);
}
memcpy(last_rowid, quick->file->ref, head->file->ref_length);
quick_with_last_rowid->file->unlock_row();
last_rowid_count= 1;
quick_with_last_rowid= quick;
//save the fields here
key_copy(qr->key_tuple, record, head->key_info + quick->index,

8
sql/sql_delete.cc

@ -887,13 +887,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
}
}
}
/*
Don't try unlocking the row if skip_record reported an error since in
this case the transaction might have been rolled back already.
*/
else if (likely(!thd->is_error()))
table->file->unlock_row(); // Row failed selection, release lock on it
else
else if (unlikely(thd->is_error()))
break;
}

9
sql/sql_select.cc

@ -12102,7 +12102,6 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j,
}
static store_key *
get_store_key(THD *thd, KEYUSE *keyuse, table_map used_tables,
KEY_PART_INFO *key_part, uchar *key_buff, uint maybe_null)
@ -13541,10 +13540,8 @@ void set_join_cache_denial(JOIN_TAB *join_tab)
used in all access methods.
*/
void rr_unlock_row(st_join_table *tab)
void rr_unlock_row(st_join_table *)
{
READ_RECORD *info= &tab->read_record;
info->table->file->unlock_row();
}
@ -22797,12 +22794,10 @@ int join_read_key2(THD *thd, JOIN_TAB *tab, TABLE *table, TABLE_REF *table_ref)
return -1;
}
/*
Moving away from the current record. Unlock the row
in the handler if it did not match the partial WHERE.
Moving away from the current record.
*/
if (tab && tab->ref.has_record && tab->ref.use_count == 0)
{
tab->read_record.table->file->unlock_row();
table_ref->has_record= FALSE;
}
error=table->file->ha_index_read_map(table->record[0],

25
sql/sql_update.cc

@ -871,8 +871,6 @@ int mysql_update(THD *thd,
goto err;
}
table->file->try_semi_consistent_read(1);
/*
When we get here, we have one of the following options:
A. query_plan.index == MAX_KEY
@ -905,9 +903,6 @@ int mysql_update(THD *thd,
thd->inc_examined_row_count(1);
if (!select || (error= select->skip_record(thd)) > 0)
{
if (table->file->ha_was_semi_consistent_read())
continue; /* repeat the read of the same row if it still exists */
explain->buf_tracker.on_record_after_where();
table->file->position(table->record[0]);
if (unlikely(my_b_write(&tempfile,table->file->ref,
@ -935,14 +930,11 @@ int mysql_update(THD *thd,
error= 1;
break;
}
else
table->file->unlock_row();
}
}
if (unlikely(thd->killed) && !error)
error= 1; // Aborted
limit= tmp_limit;
table->file->try_semi_consistent_read(0);
end_read_record(&info);
/* Change select to use tempfile */
@ -981,7 +973,6 @@ update_begin:
if (select && select->quick && select->quick->reset())
goto err;
table->file->try_semi_consistent_read(1);
if (init_read_record(&info, thd, table, select, file_sort, 0, 1, FALSE))
goto err;
@ -1046,9 +1037,6 @@ update_begin:
thd->inc_examined_row_count(1);
if (!select || select->skip_record(thd) > 0)
{
if (table->file->ha_was_semi_consistent_read())
continue; /* repeat the read of the same row if it still exists */
explain->tracker.on_record_after_where();
store_record(table,record[1]);
@ -1235,17 +1223,6 @@ error:
}
}
}
/*
Don't try unlocking the row if skip_record reported an error since in
this case the transaction might have been rolled back already.
*/
else if (likely(!thd->is_error()))
table->file->unlock_row();
else
{
error= 1;
break;
}
thd->get_stmt_da()->inc_current_row_for_warning();
if (unlikely(thd->is_error()))
{
@ -1298,8 +1275,6 @@ error:
update_end:
table->file->try_semi_consistent_read(0);
if (!transactional_table && updated > 0)
thd->transaction->stmt.modified_non_trans_table= TRUE;

66
storage/innobase/handler/ha_innodb.cc

@ -8757,65 +8757,6 @@ ha_innobase::delete_row(
error, m_prebuilt->table->flags, m_user_thd));
}
/**********************************************************************//**
Removes a new lock set on a row, if it was not read optimistically. This can
be called after a row has been read in the processing of an UPDATE or a DELETE
query. */
void
ha_innobase::unlock_row(void)
/*=========================*/
{
DBUG_ENTER("ha_innobase::unlock_row");
if (m_prebuilt->select_lock_type == LOCK_NONE) {
DBUG_VOID_RETURN;
}
ut_ad(trx_state_eq(m_prebuilt->trx, TRX_STATE_ACTIVE, true));
switch (m_prebuilt->row_read_type) {
case ROW_READ_WITH_LOCKS:
if (m_prebuilt->trx->isolation_level > TRX_ISO_READ_COMMITTED)
break;
/* fall through */
case ROW_READ_TRY_SEMI_CONSISTENT:
{
bool hold = (m_prebuilt->batch_mtr
&& !m_prebuilt->batch_mtr->cursor_stored());
row_unlock_for_mysql(m_prebuilt, hold);
break;
}
case ROW_READ_DID_SEMI_CONSISTENT:
m_prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
break;
}
DBUG_VOID_RETURN;
}
/* See handler.h and row0mysql.h for docs on this function. */
bool
ha_innobase::was_semi_consistent_read(void)
/*=======================================*/
{
return(m_prebuilt->row_read_type == ROW_READ_DID_SEMI_CONSISTENT);
}
/* See handler.h and row0mysql.h for docs on this function. */
void ha_innobase::try_semi_consistent_read(bool yes)
{
ut_ad(m_prebuilt->trx == thd_to_trx(ha_thd()));
/* Row read type is set to semi consistent read if this was
requested by the SQL layer and the transaction isolation level is
READ UNCOMMITTED or READ COMMITTED. */
m_prebuilt->row_read_type = yes
&& m_prebuilt->trx->isolation_level <= TRX_ISO_READ_COMMITTED
? ROW_READ_TRY_SEMI_CONSISTENT
: ROW_READ_WITH_LOCKS;
}
/******************************************************************//**
Initializes a handle to use an index.
@return 0 or error number */
@ -9449,13 +9390,6 @@ ha_innobase::rnd_init(
err = change_active_index(m_primary_key);
}
/* Don't use semi-consistent read in random row reads (by position).
This means we must disable semi_consistent_read if scan is false */
if (!scan) {
try_semi_consistent_read(0);
}
m_start_of_scan = true;
return(err);

6
storage/innobase/handler/ha_innodb.h

@ -115,12 +115,6 @@ public:
int delete_row(const uchar * buf) override;
bool was_semi_consistent_read() override;
void try_semi_consistent_read(bool yes) override;
void unlock_row() override;
int index_init(uint index, bool sorted) override;
int index_end() override;

13
storage/innobase/include/lock0lock.h

@ -444,19 +444,6 @@ dberr_t lock_table_for_trx(dict_table_t *table, trx_t *trx, lock_mode mode,
@retval DB_SUCCESS on success */
dberr_t lock_sys_tables(trx_t *trx);
/*************************************************************//**
Removes a granted record lock of a transaction from the queue and grants
locks to other transactions waiting in the queue if they now are entitled
to a lock. */
void
lock_rec_unlock(
/*============*/
trx_t* trx, /*!< in/out: transaction that has
set a record lock */
const page_id_t id, /*!< in: page containing rec */
const rec_t* rec, /*!< in: record */
lock_mode lock_mode);/*!< in: LOCK_S or LOCK_X */
/** Release the explicit locks of a committing transaction,
and release possible other transactions waiting because of these locks. */
void lock_release(trx_t* trx);

62
storage/innobase/include/row0mysql.h

@ -261,24 +261,6 @@ row_update_for_mysql(
row_prebuilt_t* prebuilt)
MY_ATTRIBUTE((warn_unused_result));
/** This can only be used when the current transaction is at
READ COMMITTED or READ UNCOMMITTED isolation level.
Before calling this function row_search_mvcc() must have
initialized prebuilt->new_rec_locks to store the information which new
record locks really were set. This function removes a newly set
clustered index record lock under prebuilt->pcur or
prebuilt->clust_pcur. Thus, this implements a 'mini-rollback' that
releases the latest clustered index record lock we set.
@param[in,out] prebuilt prebuilt struct in MySQL handle
@param[in] has_latches_on_recs TRUE if called so that we have the
latches on the records under pcur
and clust_pcur, and we do not need
to reposition the cursors. */
void
row_unlock_for_mysql(
row_prebuilt_t* prebuilt,
bool has_latches_on_recs);
/*********************************************************************//**
Creates an query graph node of 'update' type to be used in the MySQL
interface.
@ -672,45 +654,6 @@ struct row_prebuilt_t {
that was decided in ha_innodb.cc,
::store_lock(), ::external_lock(),
etc. */
ulint row_read_type; /*!< ROW_READ_WITH_LOCKS if row locks
should be the obtained for records
under an UPDATE or DELETE cursor.
At READ UNCOMMITTED or
READ COMMITTED isolation level,
this can be set to
ROW_READ_TRY_SEMI_CONSISTENT, so that
if the row under an UPDATE or DELETE
cursor was locked by another
transaction, InnoDB will resort
to reading the last committed value
('semi-consistent read'). Then,
this field will be set to
ROW_READ_DID_SEMI_CONSISTENT to
indicate that. If the row does not
match the WHERE condition, MySQL will
invoke handler::unlock_row() to
clear the flag back to
ROW_READ_TRY_SEMI_CONSISTENT and
to simply skip the row. If
the row matches, the next call to
row_search_mvcc() will lock
the row.
This eliminates lock waits in some
cases; note that this breaks
serializability. */
ulint new_rec_locks; /*!< normally 0; if
the session is using READ
COMMITTED or READ UNCOMMITTED
isolation level, set in
row_search_mvcc() if we set a new
record lock on the secondary
or clustered index; this is
used in row_unlock_for_mysql()
when releasing the lock under
the cursor if we determine
after retrieving the row that
it does not need to be locked
('mini-rollback') */
ulint mysql_prefix_len;/*!< byte offset of the end of
the last requested column */
ulint mysql_row_len; /*!< length in bytes of a row in the
@ -926,9 +869,4 @@ innobase_rename_vc_templ(
#define ROW_RETRIEVE_PRIMARY_KEY 1
#define ROW_RETRIEVE_ALL_COLS 2
/* Values for row_read_type */
#define ROW_READ_WITH_LOCKS 0
#define ROW_READ_TRY_SEMI_CONSISTENT 1
#define ROW_READ_DID_SEMI_CONSISTENT 2
#endif /* row0mysql.h */

83
storage/innobase/lock/lock0lock.cc

@ -3791,89 +3791,6 @@ dberr_t lock_sys_tables(trx_t *trx)
/*=========================== LOCK RELEASE ==============================*/
/*************************************************************//**
Removes a granted record lock of a transaction from the queue and grants
locks to other transactions waiting in the queue if they now are entitled
to a lock. */
TRANSACTIONAL_TARGET
void
lock_rec_unlock(
/*============*/
trx_t* trx, /*!< in/out: transaction that has
set a record lock */
const page_id_t id, /*!< in: page containing rec */
const rec_t* rec, /*!< in: record */
lock_mode lock_mode)/*!< in: LOCK_S or LOCK_X */
{
lock_t* first_lock;
lock_t* lock;
ulint heap_no;
ut_ad(trx);
ut_ad(rec);
ut_ad(!trx->lock.wait_lock);
ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
ut_ad(!page_rec_is_metadata(rec));
heap_no = page_rec_get_heap_no(rec);
LockGuard g{lock_sys.rec_hash, id};
first_lock = lock_sys_t::get_first(g.cell(), id, heap_no);
/* Find the last lock with the same lock_mode and transaction
on the record. */
for (lock = first_lock; lock != NULL;
lock = lock_rec_get_next(heap_no, lock)) {
if (lock->trx == trx && lock->mode() == lock_mode) {
goto released;
}
}
{
ib::error err;
err << "Unlock row could not find a " << lock_mode
<< " mode lock on the record. Current statement: ";
size_t stmt_len;
if (const char* stmt = innobase_get_stmt_unsafe(
trx->mysql_thd, &stmt_len)) {
err.write(stmt, stmt_len);
}
}
return;
released:
ut_a(!lock->is_waiting());
{
TMTrxGuard tg{*trx};
lock_rec_reset_nth_bit(lock, heap_no);
}
/* Check if we can now grant waiting lock requests */
for (lock = first_lock; lock != NULL;
lock = lock_rec_get_next(heap_no, lock)) {
if (!lock->is_waiting()) {
continue;
}
mysql_mutex_lock(&lock_sys.wait_mutex);
ut_ad(lock->trx->lock.wait_trx);
ut_ad(lock->trx->lock.wait_lock);
if (const lock_t* c = lock_rec_has_to_wait_in_queue(g.cell(),
lock)) {
lock->trx->lock.wait_trx = c->trx;
} else {
/* Grant the lock */
ut_ad(trx != lock->trx);
lock_grant(lock);
}
mysql_mutex_unlock(&lock_sys.wait_mutex);
}
}
/** Release the explicit locks of a committing transaction,
and release possible other transactions waiting because of these locks.
@return whether the operation succeeded */

83
storage/innobase/row/row0mysql.cc

@ -1726,89 +1726,6 @@ error:
DBUG_RETURN(err);
}
/** This can only be used when the current transaction is at
READ COMMITTED or READ UNCOMMITTED isolation level.
Before calling this function row_search_mvcc() must have
initialized prebuilt->new_rec_locks to store the information which new
record locks really were set. This function removes a newly set
clustered index record lock under prebuilt->pcur or
prebuilt->clust_pcur. Thus, this implements a 'mini-rollback' that
releases the latest clustered index record lock we set.
@param[in,out] prebuilt prebuilt struct in MySQL handle
@param[in] has_latches_on_recs TRUE if called so that we have the
latches on the records under pcur
and clust_pcur, and we do not need
to reposition the cursors. */
void
row_unlock_for_mysql(
row_prebuilt_t* prebuilt,
bool has_latches_on_recs)
{
if (prebuilt->new_rec_locks == 1 && prebuilt->index->is_clust()) {
trx_t* trx = prebuilt->trx;
ut_ad(trx->isolation_level <= TRX_ISO_READ_COMMITTED);
trx->op_info = "unlock_row";
const rec_t* rec;
dict_index_t* index;
trx_id_t rec_trx_id;
mtr_t mtr;
btr_pcur_t* pcur = prebuilt->pcur;
mtr_start(&mtr);
/* Restore the cursor position and find the record */
if (!has_latches_on_recs
&& pcur->restore_position(BTR_SEARCH_LEAF, &mtr)
!= btr_pcur_t::SAME_ALL) {
goto no_unlock;
}
rec = btr_pcur_get_rec(pcur);
index = pcur->index();
/* If the record has been modified by this
transaction, do not unlock it. */
if (index->trx_id_offset) {
rec_trx_id = trx_read_trx_id(rec
+ index->trx_id_offset);
} else {
mem_heap_t* heap = NULL;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs* offsets = offsets_;
rec_offs_init(offsets_);
offsets = rec_get_offsets(rec, index, offsets,
index->n_core_fields,
ULINT_UNDEFINED, &heap);
rec_trx_id = row_get_rec_trx_id(rec, index, offsets);
if (UNIV_LIKELY_NULL(heap)) {
mem_heap_free(heap);
}
}
if (rec_trx_id != trx->id) {
/* We did not update the record: unlock it */
rec = btr_pcur_get_rec(pcur);
lock_rec_unlock(
trx,
btr_pcur_get_block(pcur)->page.id(),
rec,
static_cast<enum lock_mode>(
prebuilt->select_lock_type));
}
no_unlock:
mtr_commit(&mtr);
trx->op_info = "";
}
}
/** Write query start time as SQL field data to a buffer. Needed by InnoDB.
@param thd Thread object
@param buf Buffer to hold start time data */

219
storage/innobase/row/row0sel.cc

@ -849,40 +849,6 @@ row_sel_build_prev_vers(
return(err);
}
/*********************************************************************//**
Builds the last committed version of a clustered index record for a
semi-consistent read. */
static
void
row_sel_build_committed_vers_for_mysql(
/*===================================*/
dict_index_t* clust_index, /*!< in: clustered index */
row_prebuilt_t* prebuilt, /*!< in: prebuilt struct */
const rec_t* rec, /*!< in: record in a clustered index */
rec_offs** offsets, /*!< in/out: offsets returned by
rec_get_offsets(rec, clust_index) */
mem_heap_t** offset_heap, /*!< in/out: memory heap from which
the offsets are allocated */
const rec_t** old_vers, /*!< out: old version, or NULL if the
record does not exist in the view:
i.e., it was freshly inserted
afterwards */
dtuple_t** vrow, /*!< out: to be filled with old virtual
column version if any */
mtr_t* mtr) /*!< in: mtr */
{
if (prebuilt->old_vers_heap) {
mem_heap_empty(prebuilt->old_vers_heap);
} else {
prebuilt->old_vers_heap = mem_heap_create(
rec_offs_size(*offsets));
}
row_vers_build_for_semi_consistent_read(prebuilt->trx,
rec, mtr, clust_index, offsets, offset_heap,
prebuilt->old_vers_heap, old_vers, vrow);
}
/*********************************************************************//**
Tests the conditions which determine when the index segment we are searching
through has been exhausted.
@ -4288,12 +4254,6 @@ row_search_mvcc(
bool need_vrow = prebuilt->read_just_key
&& prebuilt->index->has_virtual();
/* Reset the new record lock info if READ UNCOMMITTED or
READ COMMITED isolation level is used. Then
we are able to remove the record locks set here on an individual
row. */
prebuilt->new_rec_locks = 0;
/*-------------------------------------------------------------*/
/* PHASE 1: Try to pop the row from the prefetch cache */
@ -4352,9 +4312,6 @@ row_search_mvcc(
DBUG_RETURN(DB_END_OF_INDEX);
}
/* if the query is a plain locking SELECT, and the isolation level
is <= TRX_ISO_READ_COMMITTED, then this is set to FALSE */
bool did_semi_consistent_read = false;
mtr_t *mtr = nullptr;
row_batch_mtr_t *batch_mtr = prebuilt->batch_mtr;
if (batch_mtr) {
@ -4462,7 +4419,6 @@ aborted:
/* NOTE that we do NOT store the cursor
position */
trx->op_info = "";
ut_ad(!did_semi_consistent_read);
if (UNIV_LIKELY_NULL(heap)) {
mem_heap_free(heap);
}
@ -4601,27 +4557,11 @@ wait_table_again:
}
return DB_CORRUPTION;
}
if (UNIV_UNLIKELY(prebuilt->row_read_type
== ROW_READ_DID_SEMI_CONSISTENT)) {
/* We did a semi-consistent read,
but the record was removed in
the meantime. */
prebuilt->row_read_type
= ROW_READ_TRY_SEMI_CONSISTENT;
}
} else if (UNIV_LIKELY(prebuilt->row_read_type
!= ROW_READ_DID_SEMI_CONSISTENT)) {
} else {
/* The cursor was positioned on the record
that we returned previously. If we need
to repeat a semi-consistent read as a
pessimistic locking read, the record
cannot be skipped. */
goto next_rec_after_check;
that we returned previously. */
goto next_rec;
}
} else if (dtuple_get_n_fields(search_tuple) > 0) {
if (batch_mtr && batch_mtr->has_lock()) {
@ -5100,87 +5040,11 @@ no_gap_lock:
lock_type, thr, mtr);
switch (err) {
const rec_t* old_vers;
case DB_SUCCESS_LOCKED_REC:
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
/* Note that a record of
prebuilt->index was locked. */
prebuilt->new_rec_locks = 1;
}
err = DB_SUCCESS;
/* fall through */
case DB_SUCCESS:
break;
case DB_LOCK_WAIT:
/* Lock wait for R-tree should already
be handled in sel_set_rtr_rec_lock() */
ut_ad(!dict_index_is_spatial(index));
/* Never unlock rows that were part of a conflict. */
prebuilt->new_rec_locks = 0;
if (UNIV_LIKELY(prebuilt->row_read_type
!= ROW_READ_TRY_SEMI_CONSISTENT)
|| unique_search
|| index != clust_index) {
if (!prebuilt->skip_locked) {
goto lock_wait_or_error;
}
} else {
/* The following call returns 'offsets'
associated with 'old_vers' */
row_sel_build_committed_vers_for_mysql(
clust_index, prebuilt, rec,
&offsets, &heap, &old_vers,
need_vrow ? &vrow : NULL, mtr);
}
/* Check whether it was a deadlock or not, if not
a deadlock and the transaction had to wait then
release the lock it is waiting on. */
err = lock_trx_handle_wait(trx);
switch (err) {
case DB_SUCCESS:
ut_ad(
!trx->lock.was_chosen_as_deadlock_victim);
/* The lock was granted while we were
searching for the last committed version.
Do a normal locking read. */
offsets = rec_get_offsets(
rec, index, offsets,
index->n_core_fields,
ULINT_UNDEFINED, &heap);
goto locks_ok;
case DB_DEADLOCK:
goto lock_wait_or_error;
case DB_LOCK_WAIT:
ut_ad(!dict_index_is_spatial(index));
err = DB_SUCCESS;
if (prebuilt->skip_locked) {
goto next_rec;
}
break;
case DB_LOCK_WAIT_TIMEOUT:
if (prebuilt->skip_locked) {
err = DB_SUCCESS;
goto next_rec;
}
/* fall through */
default:
ut_error;
}
if (old_vers == NULL) {
/* The row was not yet committed */
goto next_rec;
}
did_semi_consistent_read = true;
rec = old_vers;
break;
case DB_RECORD_NOT_FOUND:
if (dict_index_is_spatial(index)) {
goto next_rec;
@ -5188,14 +5052,17 @@ no_gap_lock:
goto lock_wait_or_error;
}
break;
case DB_LOCK_WAIT:
case DB_LOCK_WAIT_TIMEOUT:
/* Lock wait for R-tree should already
be handled in sel_set_rtr_rec_lock() */
ut_ad(!index->is_spatial());
if (prebuilt->skip_locked) {
err = DB_SUCCESS;
goto next_rec;
}
/* fall through */
default:
goto lock_wait_or_error;
}
} else {
@ -5332,9 +5199,6 @@ locks_ok_del_marked:
/* Check if the record matches the index condition. */
switch (row_search_idx_cond_check(buf, prebuilt, rec, offsets)) {
case CHECK_NEG:
if (did_semi_consistent_read) {
row_unlock_for_mysql(prebuilt, TRUE);
}
goto next_rec;
case CHECK_ABORTED_BY_USER:
err = DB_INTERRUPTED;
@ -5388,11 +5252,6 @@ requires_clust_rec:
break;
case DB_SUCCESS_LOCKED_REC:
ut_a(clust_rec != NULL);
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
/* Note that the clustered index record
was locked. */
prebuilt->new_rec_locks = 2;
}
err = DB_SUCCESS;
break;
case DB_LOCK_WAIT_TIMEOUT:
@ -5408,19 +5267,7 @@ requires_clust_rec:
}
if (rec_get_deleted_flag(clust_rec, comp)) {
/* The record is delete marked: we can skip it */
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
&& prebuilt->select_lock_type != LOCK_NONE) {
/* No need to keep a lock on a delete-marked
record if we do not want to use next-key
locking. */
row_unlock_for_mysql(prebuilt, TRUE);
}
goto next_rec;
}
@ -5525,14 +5372,6 @@ idx_cond_failed:
goto normal_return;
next_rec:
/* Reset the old and new "did semi-consistent read" flags. */
if (UNIV_UNLIKELY(prebuilt->row_read_type
== ROW_READ_DID_SEMI_CONSISTENT)) {
prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
}
next_rec_after_check:
did_semi_consistent_read = false;
prebuilt->new_rec_locks = 0;
vrow = NULL;
/*-------------------------------------------------------------*/
@ -5615,13 +5454,6 @@ lock_wait_or_error:
btr_pcur_store_position(pcur, mtr);
}
page_read_error:
/* Reset the old and new "did semi-consistent read" flags. */
if (UNIV_UNLIKELY(prebuilt->row_read_type
== ROW_READ_DID_SEMI_CONSISTENT)) {
prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
}
did_semi_consistent_read = false;
lock_table_wait:
if (batch_mtr) {
batch_mtr->commit();
@ -5657,30 +5489,7 @@ lock_table_wait:
moves_up, mtr);
}
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
&& !same_user_rec) {
/* Since we were not able to restore the cursor
on the same user record, we cannot use
row_unlock_for_mysql() to unlock any records, and
we must thus reset the new rec lock info. Since
in lock0lock.cc we have blocked the inheriting of gap
X-locks, we actually do not have any new record locks
set in this case.
Note that if we were able to restore on the 'same'
user record, it is still possible that we were actually
waiting on a delete-marked record, and meanwhile
it was removed by purge and inserted again by some
other user. But that is no problem, because in
rec_loop we will again try to set a lock, and
new_rec_lock_info in trx will be right at the end. */
prebuilt->new_rec_locks = 0;
}
mode = pcur->search_mode;
goto rec_loop;
}
@ -5712,20 +5521,6 @@ func_exit:
mem_heap_free(heap);
}
/* Set or reset the "did semi-consistent read" flag on return.
The flag did_semi_consistent_read is set if and only if
the record being returned was fetched with a semi-consistent read. */
ut_ad(prebuilt->row_read_type != ROW_READ_WITH_LOCKS
|| !did_semi_consistent_read);
if (prebuilt->row_read_type != ROW_READ_WITH_LOCKS) {
if (did_semi_consistent_read) {
prebuilt->row_read_type = ROW_READ_DID_SEMI_CONSISTENT;
} else {
prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
}
}
if (!batch_mtr) {
delete mtr;
}

Loading…
Cancel
Save