目 录CONTENT

文章目录

MySQL事务

FatFish1
2025-05-19 / 0 评论 / 0 点赞 / 3 阅读 / 0 字 / 正在检测是否收录...

事务四要素

事务的目的是让数据库操作符合现实世界中状态转换的规则

事务四要素就是所谓得ACID规则:

  • 原子性(Atomicity):事务中的操作是一个整体,要么全成功,要么全失败

  • 隔离性(Isolation):一个事务过程产生的任何影响其他事务不应该感知,例如存钱取钱,AB同时取5元,假设之前是10元,都读到A=A-5=5,都写入A=5,则取了两次结果A还是5,这是不合理的。其实解决隔离性就很类似JVM中的锁,即写锁和读锁的抢占问题

  • 一致性(Consistency):要求变化带来的影响是合理的业务原则的,例如一次存取款,账户收支是平衡的,原子性和隔离性就是保持一致性的手段

  • 持久性(Durability):事务带来的影响是持久的,不能因为断电等什么问题就直接丢失

事务的状态

事务的状态包括:活动的、失败的、终止的、部分提交的、提交的

正常事务运行时应该是活动的,完成的语句处于部分提交状态,提交完成后变成提交状态

但是这时事务可能失败,就变成失败的,然后就会执行回滚操作,最终变成终止状态

根据上面的逻辑,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了

MySQL的事务语法

开启/提交事务

可以使用BEGIN语句或START TRANSACTION语句开启事务,而使用COMMIT语句提交事务

其实START TRANSACTION语句是可以有修饰符的,例如:

  • READ ONLY:开启只读事务

  • READ WRITE:开启读写事务

  • WITH CONSISTENT SNAPSHOT :启动一致性读

例如:

START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;

而使用BEGIN一般是比较简单的,例如:

BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;
COMMIT;

手动回滚

如果写着写着出错了,可以通过ROLLBACK语句手动回滚事务

BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;
ROLLBACK;

通过自动提交变量修改

autocommit变量用于设置事务是否自动提交,在java编码中常常用到。使用如下语句就可以找到它:

SHOW VARIABLES LIKE 'autocommit';

在用navicat等写sql的时候也可以用它启停事务

SET autocommit = OFF;
UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;
SET autocommit = ON;

这里不用COMMIT因为SET autocommit = ON; 会隐性提交事务

注意点

  • 只有InnoDB支持事务,其实NDB引擎也支持,但是这个引擎着实不常用

  • DDL会隐性提交事务,例如CREATE TABLE 语句,如果一串代码中出现了DDL,会隐性提交点前面所有的事务,其他例如ALTER USER 、 CREATE USER 、 DROP USER 、 GRANT 、 RENAME USER 、 REVOKE 、 SET PASSWORD 等语句时也会隐式的提交前边语句所属于的事务。

  • SET autocommit = ON; 会隐性提交事务

  • 使用LOCK TABLES 、 UNLOCK TABLES等锁语句也会隐性提交事务

事务隔离级别和锁

从事务问题到隔离级别

事务并发执行遇到的问题严重程度注解下降为:

  • 一个事务修改另一个未提交事务修改过的数据 —— 脏写

  • 一个事务读取到另一个未提交事务修改过的数据 —— 脏读

  • 一个事务可以读取到另一个事务已提交的修改update,并且每提交一次,都能刷新一次,这样这个事务就会蒙B,怀疑这个值到底是啥 —— 不可重复读

  • 一个事务可以读取到另一个事务已提交的插入insert/删除delete,并且每提交一次,都能刷新一次,这样这个事务就会怀疑自己按某个条件查到的结果对不对 —— 幻读

针对这些问题,事务的隔离就别就越来越高,想要解决更高级的问题,就要使用更严格的隔离级别

  • 针对脏写问题,只要不允许事务1修改事务2未提交的数据即可 —— 该级别就叫读未提交/READ UNCOOMITTED

  • 如果想要升级到不允许脏读,则要求事务1不允许读写事务2未提交数据即可 —— 读已提交/READ COMMITTED

  • 想要解决不可重读问题,只需要保证事务1执行时,事务2不能同步update即可 —— 可重复读/REPEATEABLE READ

  • 想要解决幻读问题,只需要保证事务1执行时,事务2连insert/delete都不能操作即可 —— 连insert/delete都执行不了,意味着事务2只能select,这时候所有的DDL操作都是串行的,该场景就是串行化/SERIALIZABLE

如何设置隔离级别

可以通过以下设置语句设置:

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

或直接修改系统变量transaction_isolation

查询变量可以使用以下两种:

SHOW VARIABLES LIKE 'transaction_isolation';
SELECT @@transaction_isolation;

事务隔离级别的生效原理 —— 锁

共享锁与独占锁

事务在select一条数据时要获取共享锁,简称S锁

事务在改动一条现有数据时要获取排他锁,简称X锁

既然是共享锁,那么S锁和S锁就是兼容的,即加锁后两个事务可以同时select一条数据

反过来,X锁是排他的,那么X锁和X锁、X锁和S锁都是不兼容的,即加锁后两个事务不可以同时改动一条数据,同时一个事务读取时另一个事务也不能改动

表级锁

顾名思义,就是给表加锁

如果一个事务给表加了 S锁 ,那么:

  • 别的事务可以继续获得该表的 S锁

  • 别的事务可以继续获得该表中的某些记录的 S锁

  • 别的事务不可以继续获得该表的 X锁

  • 别的事务不可以继续获得该表中的某些记录的 X锁

如果一个事务给表加了 X锁 (意味着该事务要独占这个表),那么:

  • 别的事务不可以继续获得该表的 S锁

  • 别的事务不可以继续获得该表中的某些记录的 S锁

  • 别的事务不可以继续获得该表的 X锁 别的事务

  • 不可以继续获得该表中的某些记录的 X锁

意向锁

除了普通的S锁和X锁,还有一种特殊的行级锁,叫意向锁

因为MySQL要提高并发性能,因此不能啥都直接加表锁,而是改为使用行锁

但是如果一个事务有必要加表锁的时候,难道还要遍历行有没有行锁吗,显然这个很费劲,因此设计了意向锁

如果一个事务要加行共享锁,就先在表上面加意向共享锁IS锁,从而允许其他事务对行或表加S锁;如果一个事务要加行排他锁,就先在表上加意向排他锁IX锁,从而拒绝其他事务加表级别的X锁或S锁,但是其他事务想加行级别的X锁或S锁,还是可以加的,因为行不同的话,互不影响

因此在表这个级别上,X、S、IX、IS四种锁的兼容性如下:

表级兼容性

X

IX

S

IS

X

不兼容

不兼容

不兼容

不兼容

IX

不兼容

兼容

不兼容

兼容

S

不兼容

不兼容

兼容

兼容

IS

不兼容

兼容

兼容

兼容

AUTO-INC锁

InnoDB对于某列的AUTO_INCREMENT属性,会做自增,这个自增的原理就是是在执行插入语句时就在表级别加一个 AUTO-INC 锁,然后为每条待插入记录 的 AUTO_INCREMENT 修饰的列分配递增的值,插入结束后,将AUTO-INC 锁释放掉

除此之外,还有一种轻量级锁,只在修改到自增列时获取下,修改完就释放

使用AUTO-INC锁还是轻量级锁,看插入语句在执行前是否可以确定具体要插入多少条记录

例如INSERT t_example VALUES (AAA, BBB, CCC), (AAB, BBA, CCE); 这个语句就是定死插入两条数据的

行级锁

故名思意,就给行加锁

行级锁除了行级别的S、X锁,还有一些特殊的锁

Record Locks记录锁

就是针对一条特定记录修改/读取时加的锁

Gap Locks间隙锁

为了解决幻读问题,就要避免事务1执行期间读取到事务2执行insert插入的数据

但是事务2还没insert,事务1就没办法给这条数据加记录锁,这时候就要加间隙锁,从而不允许事务2插入数据

假设事务1读取到id为1、3、8的数据,就会在区间(3,8)之间加间隙锁,从而不允许其他事务插入

0

评论区