MySQL锁机制二(行锁)

229 阅读3分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

1、行锁(偏写)

偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度高。

InnoDB引擎和MyISAM引擎不同的是:InniDB支持事务,且是行级表。

建表

create table test_innodb_lock(a int(11),b varchar(16))engine=innodb;

insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');

create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);

行锁基本演示

终端A

# 关闭自动提交
SET autocommit = 0;

# 操作一
UPDATE test_innodb_lock SET b='4001' WHERE a = 4;

# 操作二
COMMIT;

# 操作三
UPDATE test_innodb_lock SET b='4002' WHERE a = 4;

# 操作四
COMMIT;

# 操作五
UPDATE test_innodb_lock SET b='4004' WHERE a = 4;

# 操作六
COMMIT;

终端B

# 关闭自动提交
SET autocommit = 0;

# 操作一、操作二
COMMIT;
SELECT * FROM test_innodb_lock;

# 操作三
UPDATE test_innodb_lock SET b = '4003' WHERE a = 4;
# 操作四
COMMIT;

# 操作五
UPDATE test_innodb_lock SET b='4005' WHERE a = 5;

#操作六
COMMIT;

结果:

终端A修改数据但未提交,终端B取查询数据会被阻塞。

终端A提交 数据,终端A查询成功。

终端A修改数据但未提交,终端B也去修改数据会被阻塞。

终端A提交,终端B执行成功并提交。

终端A修改a=4的数据,不提交。终端B 修改a=5的数据,没有被阻塞。

无索引行锁升级为表锁

终端A

# 这里的 b 是 varchar 类型的,此处故意不写引号,这样会自动类型转换,使索引失效
UPDATE test_innodb_lock SET a = 4 WHERE b = 4001;

终端B

UPDATE test_innodb_lock SET a = 9 WHERE b = 9000;

结果:

两个终端没有对同一行的数据进行修改,但是终端B执行后会进入阻塞状态,因为索引失效行锁自动转换为表锁了。

2、间隙锁的危害

什么是间隙锁:

当我们使用的是范围条件而不是相等条件检索数据,并请求共享数据排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内,但不存在记录的叫做间隙。

危害:

通过 Query 执行过程中通过范围查找了话,他会锁定整个范围内所有的索引键值,即使这个键值不存在。

问题描述:

一个线程对数据进行范围修改,然后索引不是连续的,另一个线程进行插入操作,插入的数据不在之前线程操作的范围内,最后插入的操作会进入阻塞。

image-20200923231858660

端口A范围修改

UPDATE test_innodb_lock SET b = "1997" WHERE a > 1 and a < 6;

端口B新增数据

INSERT INTO test_innodb_lock VALUES (2,"2000");

解决方式:只锁一行

SELECT * FROM test_innodb_lock WHERE a = 8 FOR UPDATE

在SQL后面加上 FOR UPDATE 对当前操作的行上锁。其他操作会被阻塞,直到 COMMIT