事务四要素
事务的目的是让数据库操作符合现实世界中状态转换的规则
事务四要素就是所谓得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四种锁的兼容性如下:
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)之间加间隙锁,从而不允许其他事务插入
评论区