Mysql如何实现隔离级别 - 可重复读和读提交
Abstract
本文会(1) 演示Mysql的两种隔离级别. (2) 跟着mysql的源代码来看看它是怎么实现这两种隔离级别的.
Mysql的隔离级别
当有多个事务并发执行时, 我们需要考虑他们之间的相互影响. 比如 事务A写了数据d, 事务B是否应该看见呢.
Mysql的事务级别包括: read uncommitted -> read commited -> repeatable read -> serializable.
Serializable是最严格的一种隔离级别. 类似的也是性能最差的.
read committed 指当前事务A可以看到已经提交的事务B的所有修改. 不管B是否在事务A开始时提交, 还是A开始后, B提交的.
repeateable read 保证事务A只能看到事务A开始时, 其他事务已经提交的修改和本事务A内的修改.
具体的例子
使用如下语句来查看隔离级别: show variables like "%isolation%"
可以参考: https://blog.csdn.net/scugxl/article/details/97388674
源码解读
源码用的我fork的代码, (主要是添加了一些方便debug和查看的日志). 编译方法在这里
按照官网的说法, 读提交会每次重新创建一个快照.
Each consistent read, even within the same transaction, sets and reads its own fresh snapshot. For information about consistent reads, see Section 15.7.2.3, “Consistent Nonlocking Reads”.
但是可重复读只会在事务开始时创建:
This is the default isolation level for InnoDB
. Consistent reads within the same transaction read the snapshot established by the first read.
快照在代码中就是ReadView, 它包含几个比较重要的值:
/** The read should not see any transaction with trx id >= this
value. In other words, this is the "high water mark". */
trx_id_t m_low_limit_id;
/** The read should see all trx ids which are strictly
smaller (<) than this value. In other words, this is the
low water mark". */
trx_id_t m_up_limit_id;
/** trx id of creating transaction, set to TRX_ID_MAX for free
views. */
trx_id_t m_creator_trx_id;
这些值分别决定了当前ReadView/快照中的改动对哪些事务可见. 哪些事务不可见.
后面我们会根据mysql的代码来看看是不是这样的.
一条SQL执行顺序大概是这样: 解析语法 -> 执行 -> 返回.
1. 开始执行:
2. 获取锁, 检查隔离级别. 此时trx (事务) 绑定的 read_view还是NULL
3. 然后for循环读取表
可以看到MVCC相关的代码在row_search_mvcc方法中.
4. 事务还没绑定ReadView. 申请一个新的
ReadView *trx_assign_read_view(trx_t *trx) /*!< in/out: active transaction */
{
ut_ad(trx->state == TRX_STATE_ACTIVE);
if (srv_read_only_mode) {
ut_ad(trx->read_view == NULL);
return (NULL);
} else if (!MVCC::is_view_active(trx->read_view)) {
trx_sys->mvcc->view_open(trx->read_view, trx);
}
return (trx->read_view);
}
新生成的ReadView的前面提到的都设置为当前最大的事务ID:
m_low_limit_no = m_low_limit_id = m_up_limit_id = trx_sys->max_trx_id;
5. 然后就是根据ReadView需要读取数据了.
这里 lock_clust_rec_cons_read_sees 会根据当前行的修改的最新事务id(row_get_rec_trx_id)来比较当前事务绑定ReadView是否可以看到最新的修改. 如果不能看到则根据undo日志恢复到当时的版本返回.
/** Checks that a record is seen in a consistent read.
@return true if sees, or false if an earlier version of the record
should be retrieved */
bool lock_clust_rec_cons_read_sees(
const rec_t *rec, /*!< in: user record which should be read or
passed over by a read cursor */
dict_index_t *index, /*!< in: clustered index */
const ulint *offsets, /*!< in: rec_get_offsets(rec, index) */
ReadView *view) /*!< in: consistent read view */
{
// ......
/* NOTE that we call this function while holding the search
system latch. */
trx_id_t trx_id = row_get_rec_trx_id(rec, index, offsets);
return (view->changes_visible(trx_id, index->table->name));
}
判断某个ReadView对某个事务的改动是否可见:
读提交不涉及. 因为后面可以看到读提交的ReadView每次都会重新创建.
6. 读取完成后释放锁. 这里可以看到<=读提交的会释放视图. 那么事务再有请求的时候就得重新assign view了. 可以看到他们具体的int值. 可重复读隔离级别视图不会close. 那么我们知道同一个事务, 读提交会在最后关闭read view. 但是可重复读不会.
7. 经过这样我们就知道了mysql这2个并发控制的隔离级别到底是怎么实现的了.
上一篇: 面试必备基础知识 — Java基础(一)
下一篇: MySQL(二)、事务