事务回滚
事务特性:
原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
注意:
按照事务的原子性,事务要么全部完成,要么全部失败,但如果在一个事务中存在多条执行语句,在失败语句的执行之前有其他语句执行成功,那么我们应该在事务中所有sql语句执行正确则需要自己手动提交commit;否则有任何一条执行错误,需要自己提交一条rollback,这时会回滚所有操作,而不是commit会给你自动判断和回滚,所以我们在应用事务的时候,会将其放在try catch中。
事务的使用建议
- 控制事务大小,减少锁定的资源量和锁定时间长度。
- 所有的数据检索都通过索引来完成,从而避免因为无法通过索引加锁而升级为表锁。
- 减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
- 在业务条件允许下,尽量使用较低隔离级别的事务隔离。减少隔离级别带来的附加成本。
- 合理使用索引,让innodb在索引上面加锁的时候更加准确。
- 在应用中尽可能做到访问的顺序执行(串行)
- 如果容易死锁,就可以考虑使用表锁来减少死锁的概率
行锁与表锁的转变
InnoDB 行级锁是通过给索引上的索引项加锁来实现的,InnoDB行级锁只有通过索引条件检索数据,才使用行级锁;否则,InnoDB使用表锁
在不通过索引(主键)条件查询的时候,InnoDB是表锁而不是行锁。也就是说,在没有使用索引的情况下,使用的就是表锁。
间隙锁
间隙锁可以理解为是对于一定范围内的数据进行锁定,如果说这个区间没有这条数据的话也是会锁住的;主要是解决幻读的问题,如果没有添加间隙锁,如果其他事物中添加id在1到100之间的某条记录,此时会发生幻读;另一方面,视为了满足其恢复和赋值的需求(幻读的概念在事务隔离文章中有提到)。
默认情况下,innodb_locks_unsafe_for_binlog是0(禁用),这意味着启用了间隙锁定:InnoDB使用下一个键锁进行搜索和索引扫描。若要启用该变量,请将其设置为1。这将导致禁用间隙锁定:InnoDB只使用索引记录锁进行搜索和索引扫描。
innodb自动使用间隙锁的条件:
- 必须在RR级别下
- 检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)
间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:
- 防止间隙内有新数据被插入
- 防止已存在的数据,更新成间隙内的数据(例如防止numer=3的记录通过update变成number=5)
下面将通过例子来详细了解一下间隙锁的出现场景:
create table y (
id int primary key ,
num int
);
其中id为主键索引,a为二级索引。
数据如下:
id | num |
---|---|
1 | 2 |
3 | 4 |
5 | 5 |
7 | 5 |
9 | 8 |
场景:
session1 | session2 |
---|---|
start transaction ; | start transaction ; |
select * from y where num=4 for update ; | insert into y value(2,4) 失败 |
insert into y value(2,2)失败 | |
insert into y value(4,4)失败 | |
insert into y value(6,5)成功 |
间隙区间:从查找的字段向上和向下去找。
通过上面的场景,我们可以先找到间隙区间(2,4)(4,5),因此我们可以确定 id 在 1-3,3-5之间,也就是为id为2,4的记录,number在上述间隙区间的值不能够插入。
死锁
为什么会产生死锁
两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁。
Mysql死锁的两种处理方式
- 相互等待,直到时间超时(设置死锁超时时间innodb_lock_wait_timeout)
- 发起死锁检测,主动回滚一条事务,让其他事务继续执行(innodb_deadlock_detect=on)
死锁检测的原理
是构建一个以事务为顶点、锁为边的有向图,判断有向图是否存在环,存在即有死锁。
处理死锁的方式
通常我们会选择第二种方式处理死锁,选择insert或delete最少的事务进行回滚。
减少死锁的方法
- 使用事务,不使用 lock tables 。
- 保证没有长事务。
- 操作完之后立即提交事务,特别是在交互式命令行中。
- 如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE
MODE),尝试降低隔离级别。 - 修改多个表或者多个行的时候,将修改的顺序保持一致。
- 创建索引,可以使创建的锁更少。
- 最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
- 如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表