从Innodb的锁到事务隔离级别

个人感觉,应该先理解锁再来看事物隔离级别,因为事务的四种隔离级别实际上就是数据库厂商利用锁的机制,为我们这些程序员提供的四套总的并行读数据的策略。但是网上看到的文章都是隔离级别说完再说锁,也不知道这俩有什么关系,所以边写边学记录一下。

0. 先记录几个调试常用的命令,创建个测试表。

# 查看自动提交是否开启
show variables like 'autocommit';
# 打开/关闭自动提交
set autocommit = 0;
# 查看当前的事物隔离级别
select @@session.transaction_isolation;
select @@global.transaction_isolation;
# 设置事务隔离级别 read uncommitted/read committed/REPEATABLE READ/serializable
set session transaction isolation level REPEATABLE READ;
# 查看当前的事务 
select * from information_schema.innodb_trx;
# 查询锁的情况
select * from performance_schema.data_locks;
# 查询锁的等待情况
select * from performance_schema.data_lock_waits;
# 创建测试表
CREATE TABLE `account` (
  `id` int NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `account` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
INSERT INTO account (id,name,account) VALUES (1,'a',1000.0),(2,'b',1000.0),(3,'c',1000.0);

1. 观察一下innodb在事务执行过程中是怎么加锁的

begin;
UPDATE account a SET a.account = a.account + 100 WHERE id = 1;
# 查下现在加锁的情况,结果如下
select * from performance_schema.data_locks;
ENGINEENGINE_LOCK_IDENGINE_TRANSACTION_IDTHREAD_IDEVENT_IDOBJECT_SCHEMAOBJECT_NAMEPARTITION_NAMESUBPARTITION_NAMEINDEX_NAMEOBJECT_INSTANCE_BEGINLOCK_TYPELOCK_MODELOCK_STATUSLOCK_DATA
INNODB281473336931112:1067:28147323196889638937172local_testaccount281473231968896TABLEIXGRANTED
INNODB281473336931112:2:4:2:28147323196598438937172local_testaccountPRIMARY281473231965984RECORDX,REC_NOT_GAPGRANTED1

用这种办法去分析各种文章出现的情况

记录几篇文章,最好的还是直接看官方文档,建议看完再往下看
mysql锁文档
MVCC
MVCC2

2. 一个例子看懂锁和MVCC

# session 1
	begin;
	select * from account;
+----+------+---------+
| id | name | account |
+----+------+---------+
|  1 | a    |    1000 |
|  2 | b    |    1000 |
|  3 | c    |    1000 |
+----+------+---------+
# 这里的select是快照读,是MVCC实现的原理可以看上面的文章
# session 2
	begin;
	insert into account (id,name,account) value (4,'d',1000);
Query OK, 1 row affected (0.00 sec) 
	commit;
# session 1
	select * from account;
+----+------+---------+
| id | name | account |
+----+------+---------+
|  1 | a    |    1000 |
|  2 | b    |    1000 |
|  3 | c    |    1000 |
+----+------+---------+
# 在session 2 commit前查一下当前的事务
select * from information_schema.innodb_trx;

trx_idtrx_statetrx_startedtrx_requested_lock_idtrx_wait_startedtrx_weighttrx_mysql_thread_idtrx_querytrx_operation_statetrx_tables_in_usetrx_tables_lockedtrx_lock_structstrx_lock_memory_bytestrx_rows_lockedtrx_rows_modifiedtrx_concurrency_ticketstrx_isolation_leveltrx_unique_checkstrx_foreign_key_checkstrx_last_foreign_key_errortrx_adaptive_hash_latchedtrx_adaptive_hash_timeouttrx_is_read_onlytrx_autocommit_non_lockingtrx_schedule_weight
4405RUNNING2021-12-05 02:08:503210121128110REPEATABLE READ110000
562948627931136RUNNING2021-12-05 01:53:330110001128000REPEATABLE READ110000

现在session1在select后表没有发生改变是因为,在session1中进行select后,id为4的这条记录在undolog中的DB_TRX_ID(session2更新的,现在是4405)和当前事务的事务id不同(现在是562948627931136,不知道为啥和网上说的递增不一样,可能mysql8以后又有改动了)。
快照读就是这个时候判断的,因为事务id不同所以不会使用,会使用当前行的DB_ROLL_PTR找到undolog的原始数据,再select出来。

# session 1
	update account a SET a.account = a.account+100 where id = 4;
	select * from account;
+----+------+---------+
| id | name | account |
+----+------+---------+
|  1 | a    |    1000 |
|  2 | b    |    1000 |
|  3 | c    |    1000 |
|  4 | d    |    1100 |
+----+------+---------+
Query OK, 0 rows affected (0.00 sec)

这时发生了幻读。这是因为session1的的事务id和undolog中的DB_TRX_ID是相同的,所以select就能查到id=4的这条记录了。

还有一种情况就是session2进行了删除操作,如下

# session 2
	begin;
	delete from account where id =4;
Query OK, 1 row affected (0.00 sec) 
	commit;

现在session1进行update时不可能改到之前那条id=4的记录的(通过db_row_id区分),所以之前那条记录的事务id会保持为session2的事务id。

	update account a set a.account = a.account+100 where id = 4;
Query OK, 0 rows affected (0.00 sec)
2.1 RR隔离级别怎么解决幻读

间隙锁
这个例子中有个前提,唯一索引只有在值存在时才是行锁,值不存在的话,还是会变成间隙锁

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值