Mysql存储引擎Innodb的读写锁、行级锁

读写锁

Mysql存储引擎Innodb在处理并发读或者写的时候,通过两种类型的锁来解决并发问题,这两种锁通常称为共享锁和排他锁,也叫读锁和写锁。

读锁是共享的,即多个客户端可以同时读取同一资源。

写锁是排他的,也就是说写锁会阻塞其他的写锁和读锁。

举个例子:

客户端A读取操作不需要等待客户端B读取完成并释放锁。但客户端A进行写操作的时候,会阻塞其他客户端的读和写操作,直到客户端A写操作完成并释放锁,其他客户端才可以进行读写。

上面说了这么多,但实际上当客户端A更新某行记录的同时,客户端B任然可读取到数据,不会被阻塞,这是为什么呢?

先来了解两个东西,Redo Log(重做日志)和Undo Log(回滚日志)

redo log通常是物理日志,记录的是数据页的物理修改,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。

如果看不懂,就这样理解:当发生写操作时,Innodb会把旧数据存储到Undo Log(回滚日志)中,新数据写到Redo Log(重做日志)中。

当客户端A更新某行记录的同时,客户端B会被写锁阻塞,这时,客户端B会去Undo Log中读取旧数据。

下面来实践下。

CREATE TABLE `test001` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `first_name` char(50) DEFAULT NULL,
  `last_name` char(50) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (1, 'fn001', 'ln001');
INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (2, 'fn002', 'ln002');
INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (3, 'fn003', 'ln003');

在一个终端执行

BEGIN;
UPDATE test001 SET first_name='update_name' WHERE last_name='ln001';

另一个终端执行

SELECT * FROM `test001` WHERE last_name='ln001';

查询依旧可以获取数据

ps:试验完成后,记得commit或者rollback结束事物。

行级锁

Innodb使用行级锁,然而这并不意味着Innodb只会锁住被修改的行。请看下面的操作。

在一个终端连接中执行

BEGIN;
UPDATE test001 SET first_name='update_name' WHERE last_name='ln001';

在另一个终端连接中执行

BEGIN;
UPDATE test001 SET first_name='update_name2222' WHERE last_name='ln002';

第二条SQL被阻塞超时,即更新操作不仅仅锁住了 last_name='ln001'的行,还锁住了其他行。在这个例子中,实际上锁住了整张表。

回滚上面的事物再进行下面的操作。运行  rollback

那为什么说Innodb实现了行级锁呢?我们来给last_name行加一个索引。

再次执行刚才的语句

可以看到两条更新语句互不干扰,由此可知Innodb通过索引,筛选出了last_name='ln001'的数据行,然后仅对last_name='ln001'的数据行加了锁,其他数据行不会被锁住,所以此时可以更新last_name='ln002'的数据行。

没加索引前,Innodb就没法通过索引筛选出指定的数据行,只能锁住整张表了。

发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/93975909
今日推荐