Mysql 事务隔离级别
并发问题
mysql中事务并发时,会产生的问题如下
脏读:
读到了其他事务中,暂未提交的数据
脏读 (Dirty Read) 是数据库事务隔离级别中最低的一种隔离级别 (READ UNCOMMITTED) 下可能出现的一种并发问题。 它指的是一个事务读取了另一个事务尚未提交 (未持久化) 的数据。 如果后续未提交的事务回滚了,那么第一个事务读取到的数据就是无效的、不正确的,相当于读取了“脏”数据。
幻读:
同一个事务中,相同的查询,前后记录条数不一致
幻读 (Phantom Read) 是数据库事务隔离级别下可能出现的一种并发问题。 它指的是在一个事务内,第一次读取符合某些条件的数据,第二次读取同一范围的数据时,却发现了第一次查询中不存在的新数据 (幻影行),好像凭空出现了一样。 这是因为在两次读取之间,另一个事务插入了满足查询条件的新数据并提交了
常见问题:
事务1:读取A表,判断id=1是否存在,发现不存在,(此时事务2:写入A表,id=1),事务1:写入A表id=1报错主键冲突
不可重复读:
同一个事务中,相同的查询,相同记录,前后结果不一致
不可重复读 (Non-repeatable Read) 是数据库事务隔离级别下可能出现的一种并发问题。 它指的是在一个事务内,两次读取同一行数据,得到的结果不一致。 这是因为在两次读取之间,另一个事务修改了该数据并提交了
隔离级别
RU:
RU(READ UNCOMMITTED) 不管任何并发性问题,上述问题一个没解决
RC:
RC (READ COMMITTED) 读已提交,只解决了脏读
RR:
RR (REPEATABLE READ) 可重复读,它保证在同一个事务中,多次读取同一数据时,数据值是一致的,但可能存在“幻读”问题。
SERIALIZABLE
SERIALIZABLE 顺序读,最严格事务机制,事务串行执行,上述问题都可以解决,常见于金融系统
实现方案:MVCC
多版本并发控制 (Multi-Version Concurrency Control, MVCC) ,上述隔离级别的实现方案
UNDO LOG:
每个mysql表都有两个隐藏"字段",trx_id(修改该数据的事务的事务编号),roll_pointer(修改该数据之前数据视图指针),这两个关键字段是在事务执行过程中修改数据时立即更新的,而不是在事务提交后
INSERT 操作: 当一个事务插入一条新记录时,InnoDB 会立即将该记录的 row.trx_id 设置为当前事务的 trx_id。 这意味着从那一刻起,InnoDB 就知道这条记录是由哪个事务创建的。
UPDATE 操作: 当一个事务更新一条现有记录时,InnoDB 不会直接修改原始记录。 而是创建一个新的数据版本,并将 row.trx_id 设置为当前事务的 trx_id。 原始记录会被保留,并标记其 row_del_trx_id (如果需要,取决于更新的性质)。 这就是 MVCC 的核心思想:保留多个版本的数据,以便不同的事务可以看到不同时间点的数据状态。
DELETE 操作: 虽然是删除操作,但 InnoDB 实际上只是将该记录的 row_del_trx_id 设置为当前事务的 trx_id, 标记为已删除。 数据的物理删除可能会在以后的某个时间进行 (例如,通过 purge 线程)。
READ VIEW:
read view 数据版本管理重要参数封装,有四个关键参数
m_ids : 生成Read View时,同时运行的事务编号List (不包括当前事务自身)
min_limit_id :表示在生成 Read View 时,当前系统中活跃的读写事务中最小的事务 id,即 m_ids 中的最小值。
max_limit_id :表示生成 Read View 时,系统中应该分配给下一个事务的 id 值,即max(m_ids)+1。
creator_trx_id : 创建当前 Read View 的事务 ID (也就是当前事务的 ID)
如上图
m_ids:[100,103,104]
min_limit_id: 100
max_limit_id: 105
creator_trx_id: 102
这里会有个疑问,102事务启动时,103、104肯定还没启动或同时启动,此时102创建的m_ids中怎么可能包含103、104?
答:102启动立刻分配trx_id=102,但是从102启动到生成read_view 是有一段时间间隔的
T0: 事务 102 开始,trx_id 102被分配。
T1: 事务 102 正在创建 Read View。
T2: 事务 103 开始,trx_id 103 被分配。
T3: 事务 104 开始,trx_id 104 被分配。
T5: 事务 102 完成 Read View 的创建。
RR实现方式
沿用上图readview
102事务进行查询时,row.trx_id 可能出现如下情况
- 如果数据事务 ID trx_id < min_limit_id ,表明生成该版本的事务在生成 ReadView前,已经提交(因为事务 ID 的递增的),所以该版本可以被当前事务访问。
- 如果 trx_id>= max_limit_id ,表明生成该版本的事务在生成 Read View 后才生成,所以该版本不可以被当前事务访问。
- 如果 min_limit_id =<trx_id< max_limit_id ,需要分 3 种情况讨论
-
如果 m_ids 包含 trx_id ,则代表 Read View 生成时刻,这个事务还未提交,但是如果数据的 trx_id 等于 creator_trx_id 的话,表明数据是自己生成的,因此是可见的。
-
如果 m_ids 包含 trx_id ,并且 trx_id 不等于 creator_trx_id ,则ReadView 生成时,事务未提交,并且不是自己生产的,所以当前事务也是看不见的;
-
如果 m_ids 不包含 trx_id ,则说明你这个事务在 Read View 生成之前就已经提交了,修改的结果,当前事务是能看见的
-
如果某一版本不可见,就会根据undo_log版本链向后查询(101不行看100,100不行看99),直到找到可见版本,然后返回
举例说明:
read_view
m_ids:[101,102,103,104]
min_limit_id: 100
max_limit_id: 105
creator_trx_id: 102
1、eg: row.trx_id <= 99
102查询时,事务<=99版本不处于m_ids中,是历史版本,因此可见
2、eg: row.trx_id = 100
102查询时,事务100版本处于m_ids中是当前版本,不是自己的,因此不可见
3、eg: row.trx_id = 101
102查询时,事务101版本不处于m_ids中,是历史版本,因此可见
3、eg: row.trx_id = 102
102查询时,事务102版本是自己的,因此可见
4、eg: row.trx_id = 103
102查询时,事务103版本处于m_ids中,是当前版本,不是自己的,因此不可见
5、eg: row.trx_id = 104
102查询时,事务104版本处于m_ids中,是当前版本,不是自己的,因此不可见
6、eg: row.trx_id >= 105
102查询时,事务>=105版本不处于m_ids中,是未来版本,因此不可见