MySQL事务与锁实例教程详解

MySQL
335
0
0
2023-06-12
目录
  • MySQL事务和锁
  • 事务
  • 事务的控制语句
  • 事务隔离级别设置
  • 脏读
  • 不可重复读
  • 幻读
  • 锁机制
  • InnoDB的行级锁
  • 锁实战
  • 死锁
  • 总结

MySQL事务和锁

事务

说到关系型的数据库的事务,相信大家对四大特性都不陌生,分别是原子性、一致性、隔离性、持久性,简称为ACID特性。

MySQL中支持3种不同的存储引擎:

MyISAM存储引擎、Memory存储引擎、和InnoDB存储引擎

注:只有InnoDB才支持事务。

事务的控制语句

控制语句

作用

begin或者start transaction

开启一个事务

commit 或者 commit work

提交事务,进行持久性修改

rollback 或者 rollback work

回滚事务,撤销已经进行修改但未提交的操作

savepoint [保存点]

在事务中创建一个保存点,一个事务可以有多个保存点

releasavepoint [保存点]

回滚到指定的保存点

set transaction

设置事务的隔离级别

事务隔离级别设置

先复习一个事务的四大隔离级别

  • 读未提交(READ-UNCOMMITTED)
  • 读已提交(READ-COMMITTED)
  • 可重复读(RE-PEATABLE-READ)
  • 可序列化读(SERIALIZABLE)

下面是操作过程。

首先,查看默认的事务隔离级别,可以看到是可重复读(REPEATABLE-READ),

show variables like '%isolation%';

脏读

我们来演示一下脏读的场景,下面这张图是展示了我原先已经创建好的两个用户的账号都为100元。

分别打开两个连接mysql的会话窗口,其中一个会话的隔离级别为READ-UNCOMMITED,然后再另一个窗口中开启一个事务,例如,lisi给zhangsan转账100元,

set session transaction isolation level read uncommitted;
begin;
update user set money=money- where user=lisi;

我们在另外一个读未提交的窗口中查看,zhangsan看到钱已经转过来了,但是实际上lisi的事务还没有提交,假如这个时候,lisi不想转账了,回滚事务,那zhangsan就读到脏数据了。

不可重复读

下面来展示一下不可重复读的场景。

首先我们将zhangsan的窗口的事务隔离级别设置成READ-COMMITTED,并且在两个窗口都开启事务

set session transaction isolation level read committed;

假设zhangsan现在想统计全部人的钱有多少,很明显200;

但是这个时候lisi往账户里面存了100元,并提交事务,但是这个时候,zhangsan再次查询总和,我们会查询到总金额为300,但是这次查询是处于同一个事务中,查询到两次不一样的结果,属于不可重复读的情况。

update user set money=money+ where user='lisi';
commit;

幻读

为了解决不可重复读的问题,我们将事务的隔离等级设置成RE-PEATABLE-READ,即MySQL默认的事务隔离等级,然后在两边都开启一个事务。

set session transaction isolation level repeatable read;

 我们先在一个窗口插入一条数据并提交,然后在另外一个窗口查看,此时是查询不到这个记录的,但是假如这个时候我们新插入一条主键和刚插入的记录一样的话,我们就可以发现

insert into user values('zly',100);
# 另外一个窗口
insert into user values('zly',100);
ERROR (23000): Duplicate entry 'zly1' for key 'user.PRIMARY'

这样也算是一种幻读的现象,但是网上也有一种说法在可重复读的等级下,幻读是可避免的,这种说法不是非常准确的,如果在进行更新和插入时就可能会出现幻读的情况,如果想要解决幻读的情况,可以将事务隔离等级设置到SERIALIZABLE

锁机制

InnoDB的行级锁

InnoDB默认采用的行级锁,分为以下这两种,分别为共享锁和排他锁。

这两个概念我在这篇文章中也有介绍。

共享锁(S锁):也叫读锁,如果在该数据对象上加了共享锁,该事务可以读取但是不能修改数据。其他事务也可以在该对象上加共享锁,但是不能修改数据。

排他锁(X锁):也叫写锁,在一个数据对象只有一把排他锁,获取到该锁的事务可以读取数据和修改数据。

(加锁:一般的查询语句不会加任何的锁类型,当然也可以为数据加锁,比如在select * from … for update,这样可以为数据添加排他锁,而使用select … lock in share mode 可以为数据添加共享锁。

锁实战

首先关闭事务自动提交

set autocommit=;

我们先在一个窗口输入一条获取到排他锁,虽然操作的是一条数据,但是锁的是整张表,因为我们没有添加索引。

select * from user where user='zly' for update;

这个时候我们对user这一列添加索引,就可以看到我们对其进行加锁就不会出现阻塞的情况了。

alter table user add index(user);

注:在MySQL的行级锁是针对索引加的锁,而不是针对表中的行加级锁,虽然访问不同行的记录,但是如果不存在对应的索引,或者使用相同的索引的话,就会造成锁的冲突而锁住整张表。

死锁

死锁是指两个或者两个以上的额事务在执行过程中,因为互相的等待或者因为争抢相同的资源而造成的互相等待现象。

我们还是采用刚刚的例子,还是将事务的自动提交关闭掉,首先先在会话1中,更新id为1的记录,然后在会话2中更新id为2的记录,这个时候我们再回来在会话1更新id为2的记录,在会话2中更新id为1的记录,就会产生一个死锁;

总结

本文主要介绍了事务的隔离等级和事务的锁机制,主要更加偏向于实战部分,我前面也有一些文章涉及到。