目录
介绍
事务(transactional),英语直译为‘交易’,在数据库中主要处理操作量大,复杂程度高,有数据完整性、安全性的数据;
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行;
MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务;
实际开发中,通常只能是关系型数据库来控制事务,而数据库和应用通常在一个服务器上,所以也被称为本地事务;
事务只能用来管理 DML 语句;
特点(ACID)
-
A(Atomic)原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样,不可能出现部分成功部分失败的情况;
-
C(Consistency)一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。比如:a给b转账1000元,转账之前和转账之后,a和b的钱数一样多,数据状态一致,这就叫一致性;如果a给b转账1000元,a扣了1000,而b没有增加1000,这样数据出现了错误,没有达到一致性;
-
I(Isolation)隔离性:数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。即一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
-
D(Durability)持久性:事务处理结束后,对数据的修改就是永久的,不会回滚不会丢失。
事务简单的使用样例
-- 一:开启事务
set autocommit = 0; # 默认为1,自动提交事务,设置为0表示关闭自动提交
start transaction; # 可以省略;begin功能一样
-- 二:编写一组逻辑sql语句
sql语句1;
sql语句2;
......;
savepoint 回滚点名; # 设置回滚点,一个事务中可以有多个回滚点
release savepoint 回滚点名; # 删除回滚点,若回滚点不存在会报错
......;
sql语句N;
-- 三:结束事务
commit; # 提交
rollback; # 回滚
rollback to 回滚点名; # 回滚到指定的地方:
MySQL的四种隔离级别
一:read uncommitted(读未提交)
所有的事务可以看见其他未提交事务的执行结果;
二:read committed(读已提交)
大多数数据库默认的隔离级别,所有事务只可读取其他事务已提交的执行结果;
三:repeatable read(可重复读)
MySQL的默认隔离级别,会锁行,但不会锁表,可能会有新增的数据;
四:serializable(串行化)
最高隔离级别,理解为事务严格排队依次执行,但会造成大量的超时现象和锁竞争;
注:隔离级别越低的等级往往可以支持更高的并发处理,拥有更低的系统开销;
隔离级别可能会产生的问题
1)脏读:一个事务读取到了其他事务更新过程中,还没有commit的数据;
eg:事务B执行过程中修改了数据X,在事务B未提交前,事务A读取了数据X(事务B修改后的数据),而事务B却回滚了,这样事务A就形成了脏读。
2)不可重复读:一个事务两次查询的数据不一致;
eg:事务A首先读取了一条数据x,事务A执行逻辑的时候,事务B将数据x修改并且提交了,然后事务A再次读取的时候,发现数据不匹配了,这就是不可重复读。
3)幻读:一个事务的两次查询,数据笔数不一致;
eg:原本事务A需要根据条件得到N条数据,然而事务A的执行过程中,事务B增添了M条符合事务A搜索条件的数据并提交,导致事务A搜索发现有N+M条数据了,产生了幻读。
隔离级别与产生问题之间的关系对应表
脏读 | 不可重复读 | 幻读 | |
read uncommitted:读未提交 | × | × | × |
read committed:读已提交 | √ | × | × |
repeatable read:可重复读 | √ | √ | × |
serializable:串行化 | √ | √ | √ |
设置隔离级别
# 设置全局隔离级别
set global transaction isolation level REPEATABLE READ;
set global transaction isolation level READ COMMITTED;
set global transaction isolation level READ UNCOMMITTED;
set global transaction isolation level SERIALIZABLE;
#设置会话隔离级别
set session transaction isolation level REPEATABLE READ;
set session transaction isolation level READ COMMITTED;
set session transaction isolation level READ UNCOMMITTED;
set session transaction isolation level SERIALIZABLE;
锁
为了解决上述各种隔离级别可能产生的问题,可以使用合理的锁来完美解决;锁可以分为以下几种:
锁按使用方式分为乐观锁和悲观锁;
锁按作用域分为行锁、页锁和表锁;
锁按锁类型分为共享锁、排他锁和意向锁;
可参考如下图(楼主实在懒得画了,参考其他作者画的图)
乐观锁
一般是指用户自己实现的一种锁机制,假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式一般包括使用版本号和时间戳。
eg:在表里增加一个字段x(version或者timestamp),每次操作这条数据之前,都要更新并获取这个字段x,业务操作修改这个数据的时候,对比这个字段x,若这个字段x前后一致,则更新操作,若不一致,说明被其他线程操作过,跳过这修改;
悲观锁
数据库的锁机制,一般都是悲观锁,常见的有行锁、页锁、表锁;
行锁
顾名思义,锁行,避免其他线程查看需要锁的行,或者避免其他线程修改需要锁的行;行锁又包含共享锁、排他锁、更新锁;
共享锁
其他事务只能读取,不能更改,可以上锁;
排他锁
其他事务不能读取,也不能更改,也不能上锁;
更新锁
当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
页锁
有些人也叫间隙锁,锁某些条件的一系列数据,例如select * from tableOne where field1 < 100 for update;它会将field1小于100的数据全部锁住;
表锁
锁某一张表;
意向锁
解决表锁和行锁之间冲突,知乎上有解释如下:
eg:在mysql中有表锁,读锁锁表,会阻塞其他事务修改表数据。写锁锁表,会阻塞其他事务读和写。
Innodb引擎又支持行锁,行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。
这两中类型的锁共存的问题考虑这个例子:事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
锁的兼容性
锁和锁之间也存在兼容性,锁之间的兼容性如下表所示:
共享锁(S) | 排他锁(X) | 意向共享锁(IS) | 意向排他锁(IX) | |
共享锁(S) | 兼容 | 冲突 | 兼容 | 冲突 |
排他锁(X) | 冲突 | 冲突 | 冲突 | 冲突 |
意向共享锁(IS) | 兼容 | 冲突 | 兼容 | 兼容 |
意向排他锁(IX) | 冲突 | 冲突 | 兼容 | 兼容 |
锁的常用操作
LOCK TABLES table read local; # 本地可写锁
UNLOCK TABLES; # 释放当前会话持有的锁
SELECT * FROM table WHERE ... LOCK IN SHARE MODE #共享锁
SELECT * FROM table WHERE ... FOR UPDATE #排他锁
select * from table for update; # 锁表(排他)
select * from table where field = 1 for update; # 锁行(排他)
select * from table where field > 100 for update; # 锁多行(排他)
select * from table LOCK IN SHARE MODE; # 锁表(共享)
select * from table where field = 1 LOCK IN SHARE MODE; # 锁行(共享)
select * from table where field > 100 LOCK IN SHARE MODE; # 锁多行(共享)
内容有欠缺,东西很杂,感觉不太好写啊,容楼主好好思考一下