事务的特性(ACID):
- 原子性:构成事务的所有操作要么全部成功,要不全部不成功
- 一致性:事务执行之前和执行之后,数据始终保持一致
- 隔离性:并发执行的两个事物之间互不干扰
- 持久性:事物对数据的更改操作会被持久化到数据库中,并且不会被回滚
事物的类型:
- 偏平事物:操作中最常见、最简单、主流的数据库都支持
- 带有保存点的扁平事物:事务回滚能回滚到指定的保存点
- 链式事物:一个事务的提交操作和下一个事务的开始操作具备原子性,上一个事务的处理结果对下一个事务是可见的,事务与事务之间就像链条一样传递下去
- 嵌套事物:对个事务处于嵌套状态,共同完成一项任务的处理,整个任务具备原子性,主流关系型数据库中Mysql不支持原生的嵌套事物,而SQL Server 支持
- 分布式事务:在不同的环境(不同的数据库,不同的服务器)下运行的整体事务
本地事务的优缺点:
本地事务的优点
- 支持严格的ACID特性,这也是本地事务得以实现的基础
- 事务可靠,一般不会出现异常情况
- 本地事务的执行效率比较高
- 事务的状态可以只在数据库中进行维护,上层的应用不必理会事务的具体状态
- 应用的编程模型比较简单,不会涉及复杂的网络通信
本地事务的缺点
- 不具备分布式事务的处理能力
- 一次事务过程只能连接一个支持事务的数据库,即不能用于多个事务性数据库
并发事务带来的问题:
- 更新丢失(脏写):一个事务对该行数据的更新操作覆盖了其他事务对该行数据的更新操作,本质上是写操作的冲突,解决办法就是让每个事务按照串行的方式执行
- 脏读:一个事务读取了另外一个事务未提交的数据,本质上是读写操作的冲突,解决办法是先写后读
- 不可重复读:同一个事务中,用相同的查询语句,在不同时刻读取到的结果不一致,本质上也是读写操作的冲突,解决办法是先读后写,重点在于更新和删除操作
- 幻读:一个事务两次读取一个范围得数据记录,两次读取到的结果不同,本质上是读写操作的冲突,解决办法是先读后写,重点在于插入操作
不可重复读和幻读的区别:
- 不可重复读的重点在于更新和删除操作,而幻读的重点在于插入操作
- 不可重复读隔级别下,通过对数据进行加锁,可以避免其他事务对数据进行修改跟删除,但是无法对新增的数据进行加锁,所以无法避免幻读的出现
- 幻读无法通过行级锁来避免,需要使用串行化的事务隔离级别,但是这种事务隔离级别会大大降低数据库的并发性
- 可以使用悲观锁来避免不可重复读和幻读的问题外,还可以使用乐观锁MVCC(多版本并发控制)来处理
事务隔离级别:
- 读未提交:READ-UNCOMMITTED
- 读已提交:READ-COMMITTED
- 可重复读:REPEATABLE-READ
- 串行化:SERIALIZABLE
实现方式:可以在命令行中使用 --transaction-isolation 选项或在Mysql的配置文件my.cnf、my.ini里为所有的链接配置默认的隔离级别
Mysql事务隔离级别的区别:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
- 读未提交:允许脏读,可能读取到其他会话未提交事物的数据,存在脏读,不可重复读,幻读的问题
- 读已提交:只能读到已经提交的数据,oracle等数据库默认使用的事务隔离级别就是读已提交,存在不可重复读,幻读的问题
- 可重复读:同个事务内,无论任何时候查询的数据都与开始查询到的数据的一致,是Mysql中InnoDB存储引擎默认的事务隔离级别,存在幻读的问题
- 串行化:完全串行化读,每次读取都将获取表级别的共享锁,读和写都会阻塞,解决了并发事务带来的问题,但是降低了数据库的并发能力
Mysql中锁的分类:
本质上,锁是一种协调多个进程或多个线程对某一资源的访问的机制,Mysql使用锁和MVCC机制实现了事务隔离级别
性能:
- 悲观锁:加锁
- 乐观锁:加版本字段控制
操作类型
- 读锁:共享锁或S锁,可以加多个读锁而互不影响
- 写锁:排它锁或X锁,当前写锁未释放,他会阻塞其他的写锁跟读锁
数据粒度
- 表锁:表级锁(表共享锁、表独占锁),特点就是开销小,加锁速度快,不会出现死锁,锁粒度大,发生锁的冲突也概率最高,并发度最低
- 行锁:行级锁(共享锁、排它锁),特点就是开销比较大,加锁速度慢,可能出现死锁,锁粒度小,锁冲突概率最小,并发读最高
注意点:
- 行锁主要加载索引上,如果对非索引的字段设置条件进行更新,行锁可能变成表锁
- InnoDB的行锁是针对索引加锁,不是针对记录加锁,并且加锁的索引不能失效,否则行锁可能变成表锁
- 可以使用 lock in share mode 指定共享锁,使用 for update 指定排他锁
- 页面锁:页级锁,开销介于表锁和行锁之间,粒度也是在表锁和行锁之间,可能出现死锁,并发度一般
更细粒度
- 间隙锁:对两个值之间的间隙进行加锁,某种程度下可以解决幻读的问题(可重复读事务隔离级别下),间隙锁只有在可重复读的事务隔离级别下才会生效
- 临键锁:是行锁和间隙锁的组合
死锁的产生和预防:
死锁的必要条件:
- 互斥条件
- 不可剥夺条件
- 请求和保持条件
- 循环等待条件
处理死锁的方法:
- 预防死锁
- 避免死锁
- 检测死锁
- 解除死锁
Mysql中,通过通过以下几种方式来避免死锁:
- 尽量让数据表中的数据检索都通过索引来完成,避免无效索引导致的行锁升级到表锁
- 合理设计查询条件的范围,尽量避免锁的范围
- 尽量减少查询条件的范围,尽量避免间隙锁或缩小间隙锁的范围
- 尽量控制事务的大小,减少一次事务锁定的资源数量,缩短锁定资源的时间
- 如果一条SQL语句设计事务加锁操作,则尽量将其放在事务的最后执行
- 尽可能使用低级别的事务隔离级别
InnoDB中的MVCC原理
本质上,MVCC机制保存了数据库中数据在某个时间点上的数据快照,意味着同个读操作的事务,按照相同的条件查询数据,无论查询多少次,结果都是一样的,也意味着不同的事务在同一个时刻看到同一个表的数据可能是不同的。
MVCC机制通过在每行数据表记录的后面保存两个隐藏的列来实现的,一个列用来保存行的创建版本号,另外一个保存行的过期版本号,每当有新的事务执行时,版本号就会自动递增,事务开始时刻的版本号作为事务的版本号,用于和查询到的每行记录的版本号作对比
查询操作:
- InnoDB存储引擎只会查找不晚于当前事务版本的数据行,也就说,只会查找版本号小于或者等于当前事务版本号的数据行,这些数据行要么在事务开始前就存在,要不就是事务本身插入或者更新的数据行
- 数据行删除的版本要不还没被定义,要不大于当前事务的版本号,只有这样才能保证事务读取到的行,在事务开始之前没有被删除
插入操作:
- 在插入操作中,InnoDB存储引擎会将新插入的每一行记录的当前系统版本号保存为行版本号
更新操作:
- 在更新操作中,InnDB存储引擎会插入一条新的记录,并保存当前系统的版本号作为新记录的版本号,同时保存当前系统的版本号到原来的数据行作为删除标识
- MVCC机制是先将原来的数据复制一份,然后进行操作
删除操作:
- 在删除操作中,InnoDB存储引擎会保存删除的每一个行记录当前的系统版本号,作为行的删除标识