MySQL之事务和锁机制


提示:以下是本篇文章正文内容,MySQL 系列学习将会持续更新

在这里插入图片描述

一、事务

  • 在数据库里面,我们希望有些操作能够以原子的方式进行,要么都能执行成功,要么就都不执行,也就是只能是一个整体的被执行,这样的一组具有原子性的操作我们就称之为事务
  • 我们的 MySQL 支持 9 种数据库引擎,但只有默认的 Innodb 引擎支持事务功能。

1.1 事务特征

  • 原子性 (atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性 (consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
  • 隔离性 (isolation):当多个事务对同一资源同时操作时,一个事务的执行不能被其他事务干扰。这里的同时只是宏观上的表现,实际上也就是微观上同一时刻只有一个事务在执行,而其它事务是在等待中。
  • 持久性 (durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,即便系统故障也不会丢失。

1.2 隔离级别

更追求隔离性(数据更正确) ----------------------------- ------------------------------------------------ ---------------------------------> 更追求并发性(性能更高)
(可串行性)
serializable
(快照读)
snapshot_read
(可重复读)
repeatable_read
(读已提交)
read_committed
(读未提交)
read_uncommitted

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。



不是标准中存在的隔离级别,目前来说,没有副作用。
MySQL中的可重复读就是实际上的快照读。因为MVCC机制解决了幻读


这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这会导致幻读当用户修改某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有一条未修改的数据“幻影” 只能读取其它事务已经提交的内容,存在不可重复读问题:一个事务多次读取同一数据可能会得到多个不同的结果



能够读取到其它事务中未提交的内容,存在脏读问题。读取未提交的数据,也被称之为脏读




我们可以修改隔离级别:

set session transaction isolation level read uncommitted;

回到目录…

1.3 开启事务

①SQL开启事务

-- 开启事务
start transaction; / begin;
SQL1;
SQL2;
rollback; -- 主动回滚

-- 开启事务
start transaction; / begin;
SQL1;
SQL2;
SQL3;
commit; -- 提交事务,失败也会回滚

②JDBC使用事务

// 要使用事务,在同一个事务中,操作 sql1 和 sql2,意味着必须在一条 Connection 完成
try (Connection c = DBUtil.connection()) {
    
    
    // connection 中有一个自动提交(autocommit)的属性,默认情况下是 true(开启)
    // 开启状态下,意味着,每一条 sql 都会被独立的视为一个事务
    // 我们要让 sql1 和 sql2 看作整体,只需要关闭 connection 的自动提交
    c.setAutoCommit(false);
    // 此时就可以手动的控制事务的结束位置,并且需要手动提交

    try (PreparedStatement ps = c.prepareStatement(sql1)) {
    
    
        ps.executeUpdate();
    }

    try (PreparedStatement ps = c.prepareStatement(sql2)) {
    
    
        ps.executeUpdate();
    }

    // 由于我们关闭了自动提交了,所以,所有的修改还没有真正地落盘
    c.commit();     // 只有加上这句话,才表示事务被提交了(数据真正落盘了)
}

回到目录…

二、锁机制

我们知道在可重复读的级别下,MySQL 在一定程度上解决了幻读问题:

  • 在快照读(不加锁)读情况下,mysql 通过 MVCC (多版本并发控制) 来避免幻读。
  • 在当前读(加锁)读情况下,mysql 通过 next-key 来避免幻读。

2.1 读锁、写锁

从对数据的操作类型上来说,锁分为读锁和写锁:

扫描二维码关注公众号,回复: 15394451 查看本文章
  • 读锁:也叫共享锁,当一个事务添加了读锁后,其他的事务也可以添加读锁或是读取数据,但是不能进行写操作,只能等到所有的读锁全部释放。
  • 写锁:也叫排他锁,当一个事务添加了写锁后,其他事务不能读不能写也不能添加任何锁,只能等待当前事务释放锁。

2.2 全局锁、表锁、行锁

从锁的作用范围上划分,分为全局锁、表锁和行锁:

全局锁:锁作用于全局,整个数据库的所有操作全部受到锁限制。

flush tables with read lock;

表锁:锁作用于整个表,所有对表的操作都会收到锁限制。

lock table 表名称 read; -- 读锁
lock table 表名称 write; -- 写锁

-- 除了手动释放锁之外,当我们的会话结束后,锁也会被自动释放。
unlock tables;

行锁:锁作用于表中的某一行,只会通过锁限制对某一行的操作(仅InnoDB支持)

-- 添加读锁(共享锁)
select * from 表名 where ... lock in share mode;
-- 添加写锁(排他锁)
select * from 表名 where ... for update;

回到目录…

2.3 记录锁、间隙锁、临键锁

我们知道 InnoDB 支持使用行锁,但是行锁比较复杂,它可以继续分为多个类型,详细可查看文章:MySQL的锁机制 - 记录锁、间隙锁、临键锁

记录锁(Record Locks): 仅仅锁住索引记录的一行,在单条索引记录上加锁。Record lock 锁住的永远是索引,而非记录本身。所以说当一条 sql 没有走任何索引时,那么将会在每一条聚合索引后面加写锁,这个类似于表锁,但原理上和表锁应该是完全不同的。

  • id 列必须为唯一索引列或主键列,否则加的锁就会变成临键锁。
  • 同时,查询语句必须为精准匹配(=),不能为 >、<、like等,否则也会退化成临键锁。

间隙锁(Gap Locks): 仅仅锁住一个索引区间(开区间)。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。比如在 1、2 中,间隙锁的可能值有 (-∞, 1),(1, 2),(2, +∞),间隙锁可用于防止幻读,保证索引间的不会被插入数据。

  • 对于主键索引:精准查询存在列,只会产生记录锁;精准查询不存在列,会产生记录锁和间隙锁;范围查询会产生间隙锁。
  • 对于普通索引:不管是何种查询,只要加锁,都会产生间隙锁。
    在这里插入图片描述

临键锁(Next-Key Locks): Record lock + Gap lock左开右闭区间。默认情况下,InnoDB 正是使用 Next-key Locks 来锁定记录(如select … for update)。

它还会根据场景进行灵活变换:

场景 转换
使用唯一索引进行精确匹配,但表中不存在记录 自动转换为 Gap Locks
使用唯一索引进行精确匹配,且表中存在记录 自动转换为 Record Locks
使用非唯一索引进行精确匹配 不转换
使用唯一索引进行范围匹配 不转换,但是只锁上界,不锁下界

回到目录…


总结:
提示:这里对文章进行总结:
本文是MySQL的学习,先学习了事务的四大特征、隔离级别,如何开启事务。又学习了锁机制,认识了读写锁、行表锁、记录锁等。之后的学习内容将持续更新!!!

猜你喜欢

转载自blog.csdn.net/qq15035899256/article/details/130003231