目录
- 事务
- 案例场景
- 模拟实现转账:从张三的账户转账500元到李四的账户
- SQL示例
- 异常
- 什么是事务
- 事务的特性,简称ACID 属性
- 实现原理
- redo log
- undo log
- MySQL 中一条 SQL 更新语句的执行过程( InnoDB 存储引擎)
- 事务的提交流程
- 隔离性
- 并发事务产生的问题
- 事务隔离级别
- 读未提交:READ UNCOMMITTED
- 读已提交:READ COMMITTED
- 可重复读:Repeatable Read
- 序列化:Serializable(可串行化)
- MVCC工作模式
- 数据库锁
- 悲观锁
- 乐观锁
- 锁的粒度分类
- 行锁
- 表锁
- 页面锁
- 锁的性质分类
- 共享锁/读锁
- 排它锁/写锁
- 更新锁
- 其他锁
- InnoDB 间隙锁
- InnoDB 意向锁
- 死锁
- 产生原因
- 死锁发生的条件
- 处理策略:
- 预防
- 避免
- 检测与解除
- 案例
- InnoDB 行锁优化策略
事务
案例场景
假定给张三开卡,卡号4001,并在其账户上存1000元,给李四开卡,卡号4002,账户上存1元。
#创建表并添加数据
CREATE TABLE IF NOT EXISTS mybank(
cid CHAR(4), #卡号
cname VARCHAR(30),#用户名
balance DECIMAL(10,2) #当前余额
);
INSERT INTO mybank(cid,cname,balance)VALUES(4001,'张三',1000);
INSERT INTO mybank(cid,cname,balance)VALUES(4002,'李四',1);
SELECT * FROM MYBANK;
#转账的理想操作(张三账户少500,李四账户加500)
UPDATE MYBANK SET BALANCE = BALANCE - 500 WHERE CID = 4001;
UPDATE MYBANK SET BALANCE = BALANCE + 500 WHERE CID = 4002;
模拟实现转账:从张三的账户转账500元到李四的账户
SQL示例
/*--转账测试:张三希望通过转账,直接汇钱给李四500元--*/
#张三的账户减500元,李四的账户增500元
UPDATE `mybank` SET balance=balance-500 WHERE cid=4001;
UPDATE `mybank` SET balance=balance+500 WHERE cid=4002;
异常
假设在第一条SQL执行完毕后,因为某些原因(网络故障、服务器宕机…)导致第二条SQL没有执行…就会导致数据错误!
什么是事务
- 事务(TRANSACTION)是一种机制、一个操作序列,包含了一组数据库操作命令,并且把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库操作命令要么都执行,要么都不执行。
- 事务是一个不可分割的工作逻辑单元
- 转账过程就是一个整体
- 它需要两条UPDATE语句来完成,这两条语句是一个整体
- 如果其中任一条出现错误,则整个转账业务也应取消,两个账户中的余额应恢复到原来的数据,从而确保转账前和转账后的余额不变,即都是1001元
事务的特性,简称ACID 属性
- Atomicity(原子性):事务的各步操作是不可分的(原子的),要么都执行,要么都不执行,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作
- Consistency(一致性):当事务完成时,数据必须处于一致状态
,在事务开始之前和事务结束以后,数据库的完整性没有被破坏 - Isolation(隔离性):并发事务之间彼此隔离、独立,它不应以任何方式依赖于或影响其他事务。数据库允许多个并发事务同时对其数据进行读写和修改的能力,但是多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务的运行效果
- Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
实现原理
- 事务的隔离性是通过锁实现。
- 而事务的原子性、一致性和持久性则是通过事务日志实现
- 说到事务日志中的redo log 和 undo log 共同保障了事务的持久性、一致性和原子性
redo log
- InnoDB 的存储引擎中,事务日志通过重做(redo)日志和 InnoDB 存储引擎的日志缓冲(InnoDB Log Buffer)实现。事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是 DBA 口中的“日志先行”(Write-Ahead Logging)。当事务提交之后,在Buffer Pool中映射的数据文件才会慢慢刷新到磁盘.此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据redo log中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定
- redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
- 在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。
- 为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MariaDB/MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。
- redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)
- 在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录Redo Log,通过顺序IO来改善性能.所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起
undo log
- undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录
- 在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。
- undo log 主要为事务的回滚及 MVCC 服务。在事务执行的过程中,除了记录 redo log,还会记录一定量的 undo log。undo log是逻辑日志,它记录的是和原 sql 相反的 sql , 比如 insert 对应 delete, update 对应另外一个update。通过这种方式,undo log记录了数据在每个更新操作前的状态,如果事务执行过程中需要回滚,就可以根据 undo log 进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作
- undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。
- 当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取
- 另外,undo log也会产生redo log,因为undo log也要实现持久性保护
MySQL 中一条 SQL 更新语句的执行过程( InnoDB 存储引擎)
- 事务开始
- 申请加锁:表锁、MDL 锁、行锁、索引区间锁(看情况加哪几种锁)
- 执行器找存储引擎取数据。
- 如果记录所在的数据页本来就在内存(innodb_buffer_cache)中,存储引擎就直接返回给执行器;否则,存储引擎需要先将该数据页从磁盘读取到内存,然后再返回给执行器。
- 执行器拿到存储引擎给的行数据,进行更新操作后,再调用存储引擎接口写入这行新数据。
- 存储引擎将回滚需要的数据记录到 Undo Log,并将这个更新操作记录到 Redo Log,此时 Redo Log 处于 prepare 状态。并将这行新数据更新到内存(innodb_buffer_cache)中。同时,然后告知执行器执行完成了,随时可以提交事务。
- 手动事务 commit:执行器生成这个操作的 Binary Log,并把 Binary Log 写入磁盘。
- 执行器调用存储引擎的提交事务接口,存储引擎把刚刚写入的 Redo Log 改成 commit 状态。
- 事务结束
其中第 5 步,将这个更新操作记录到 Redo Log。生成的 Redo Log 是存储在 Redo Log Buffer 后就返回,还是必须写入磁盘后才能返回呢?
- 这就是 Redo Log 的写入策略,Redo Log 的写入策略由innodb_flush_log_at_trx_commit 参数控制,该参数不同的值对应不同的写入策略。
还有第 6 步,把 Binary Log 写入磁盘和 Redo Log 一样,也有相应的写回策略,由参数 sync_binlog 控制。
- 通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务提交前,需要等待两次刷盘,一次是 Redo Log 刷盘(prepare 阶段),一次是 Binary Log 刷盘。
事务的提交流程
在一次更改数据的事务提交中,以上几种 log 的刷盘流程如下,总结起来就是: 开启事务-> 找目标记录-> 生成 undo log 记录旧值-> 修改数据 -> 生成 redo log 并刷盘 -> 提交事务,生成bin log并刷盘 -> 事务完成,redo log 状态为已提交
- 分配事务ID ,开启事务,获取锁,没有获取到锁则等待
- 执行器先通过存储引擎找到的数据页,如果缓冲池有则直接取出,没有则去主键索引上取出对应的数据页放入缓冲池
- 在数据页内找到记录,取出数据生成 undo log 记录旧值,更改后写入内存
- 生成 redo log 到内存,redo log 状态为 prepare
- 准备提交事务,将 redo log 和 undo log 写入文件并调用 fsync
- 提交事务,server 层生成 bin log 并写入文件调用 fsync
- 事务完成,将 redo log 的状态改为 commited, 释放锁
隔离性
- 当数据库上有多个事务同时执行的时候,就可能出现脏读,不可重复读,幻读的问题,为了解决这些问题,就有了隔离级别的概念。
- 我们要注意,你隔离的越严实,效率就会越低。
- 数据库允许多个事务并发执行, 因此各个事务之间需要进行隔离,以避免一个事务对其他事务的运行产生影响.
- SQL 定义了 4个隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务间可见,哪些在事务间不可见. 低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销.
- 事务的实现是基于数据库的存储引擎,不同的存储引擎对事务的支持程度不一样,mysql中支持事务的存储引擎有InnoDB和NDB.
并发事务产生的问题
- 更新丢失:当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题 --最后的更新覆盖了由其他事务所做的更新
- 脏读:一个事务未提交的中间状态的更新数据被其他会话读取到.当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中(commit未执行),这时另一个会话也访问这个数据,因为这个数据是还没有提交, 那么它读到的这个数据就是脏数据,依据脏数据所做的操作也可能是不正确的
- 不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同.换句话说就是,后续读取到另一会话事务已提交的更新数据.“可重复读”就是在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一会话事务已提交的更新数据
- 幻读:会话T1事务中执行一次查询,然后会话T2新插入一行记录,这行记录恰好可以满足T1所使用的查询的条件.然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行.这个新行就称为“幻象”,因为对T1来说这一行数据就像是"幻觉"
幻读和不可重复读的区别
- 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样.(因为中间有其他事务提交了修改)
- 幻读的重点在于新增或者删除:在同一事务中,同样的条件, 第一次和第二次读出来的记录数不一样.(因为中间有其他事务提交了插入/删除)
事务隔离级别
读未提交:READ UNCOMMITTED
读写均不使用锁, 未提交读, 会读取未提交内容
- 所有事务都可以看到其他未提交事务的执行结果.
- 该隔离级别很少实际应用,因为它的性能并不比其他级别好多少.
- 该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据.
读已提交:READ COMMITTED
使用写锁, 提交读, 会读取其他事务已提交内容
- 大多数数据库系统的默认隔离级别(但不是MySQL默认的).
- 满足了隔离的简单定义:一个事务只能看见已经提交的事务所做的改变.
- 这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):不可重复读意味着在同一个事务中执行完全相同的select语句时可能看到不一样的结果。导致这种情况的原因有:
- 有一个交叉的事务有新的commit,导致了数据的改变;
- 一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit
可重复读:Repeatable Read
使用读锁和写锁, 可重复读
- MySQL的默认事务隔离级别
- 确保同一事务的多个实例在并发读取数据时,会看到同样的数据行
- 该级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行.
- InnoDB 和 Falcon 存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决快照读(普通 select 语句)幻读问题,InnoDB 还通过间隙锁解决当前读(select … for update,update,delete 等语句)幻读问题
序列化:Serializable(可串行化)
- 这是最高的隔离级别
- 通过强制事务按顺序执行,使之不可能相互冲突,从而解决幻读问题.它是在每个读的数据行上加上共享锁
- 这个级别可能导致大量的超时现象和锁竞争,性能表现不佳
MVCC工作模式
- InnoDB 的 MVCC 是通过在每行记录后面保存两个隐藏的列(其实是三列,第三列是用于事务回滚)来实现.这两个列一个保存了行的创建时间,一个保存行的过期时间(删除时间)。不过存储的并不是真实的时间而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动新增,事务开始时刻的系统版本号会作为事务的版本号,用来与undo中保存的行版本信息进行比较,从而确定每个事务自己的历史数据,实现非阻塞的读操作,读时不加锁,读写不冲突
- 保存这两个版本号使大多数操作都不用加锁, 数据操作更简单,性能很好,并且能保证只会读取到符合要求的行.不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作
- MVCC只在 COMMITTED READ和REPEATABLE READ两种隔离级别下工作,在 COMMITTED READ隔离级别下一致性非锁定读总是读取被锁定行最新的快照数据,而REPEATABLE READ隔离级别则总是读被锁定行事务开始时的版本号快照数据
数据库锁
- 当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。即锁的作用是解决并发问题
- 数据库锁按照是否加锁一般可以分为两类,一个是悲观锁,一个是乐观锁.
悲观锁
悲观锁就是我们通常说的数据库锁机制。数据库悲观锁按照锁粒度分类有表锁、行锁、页锁.在MyISAM中只用到表锁, 一次性获取SQL 语句所需要的全部锁,不会有死锁的问题,锁的开销很小,但相应的并发能力也很差. Innodb实现了行级锁和表锁,锁的粒度变小了,并发能力变强,但是相应的锁的开销变大,很有可能出现死锁.
乐观锁
乐观锁一般是用户自己实现的一种锁机制,假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做.乐观锁的实现方式一般包括使用版本号和时间戳.
表锁和行锁都分为共享锁和排他锁(独占锁),而更新锁是为了解决行锁升级(共享锁升级为独占锁)的死锁问题.InnoDB中表锁和行锁一起用,所以为了提高效率才会有意向锁(意向共享锁和意向排他锁)
锁的粒度分类
行锁
- InnoDB 存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁.
- 是锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。
- 行级锁开销大,加锁慢,且会出现死锁。但锁定粒度最小,发生锁冲突的概率最低,并发度也最高.
- 最大程度的支持并发,同时也带来了最大的锁开销.
- 在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,这就决定了在 InnoDB 中发生死锁是可能的.
- 行级锁只在存储引擎层实现,而Mysql服务器层没有实现.行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统.
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有真正通过索引条件检索数据,InnoDB才使用行级锁,否则 InnoDB 将使用表锁
表锁
MyISAM 和 MEMORY 存储引擎采用的是表级锁
- 是粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持
- 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低.
- 存储引擎通过总是一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁.
- 表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web 应用.
页面锁
- BDB 存储引擎采用的是页面锁,但也支持表级锁.
- 是粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折中的页级,一次锁定相邻的一组记录
- 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
锁的性质分类
共享锁/读锁
- S 锁,又称读锁,用于所有的只读数据操作。
- S 锁并非独占,允许多个并发事务对同一资源加锁,但加 S 锁的同时不允许加 X 锁,即资源不能被修改。S 锁通常读取结束后立即释放,无需等待事务结束
- 多个事务可封锁同一个共享页.但任何事务都不能修改该页.通常是该页被读取完毕,S锁立即被释放.
排它锁/写锁
- X 锁,又称写锁,表示对数据进行写操作。
- X 锁仅允许一个事务对同一资源加锁,且直到事务结束才释放,其他任何事务必须等到 X 锁被释放才能对该页进行访问
- 使用 select * from table_name for update; 语句产生 X 锁
更新锁
使用共享锁时,修改数据的操作分为两步:
- 首先获得一个共享锁,读取数据,
- 将共享锁升级为排他锁,再执行修改操作.
这样如果有两个或多个事务同时对一个事务申请了共享锁,在修改数据时,这些事务都要将共享锁升级为排他锁.这时,这些事务都不会释放共享锁,而是一直等待对方释放,这样就造成了死锁. 如果一个数据在修改前直接申请更新锁,在数据修改时再升级为排他锁,就可以避免死锁.
- U 锁,用来预定要对资源施加 X 锁,允许其他事务读,但不允许再施加 U 锁或 X 锁,当被读取的页要被更新时,则升级为写锁. 更新锁一直到事务结束时才能被释放.
其他锁
InnoDB 间隙锁
- 当前读 情况下, 对于键值在条件范围内但并不存在的记录(比如表中只有100行,而条件为 id >10 时还没有插入表中的 id > 100 的记录),叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁
- InnoDB 这种加锁机制会阻塞符合条件范围内键值的并发修改(插入、删除),往往会造成严重的锁等待,不过可以使用这种方式防止当前读的幻读,以满足相关隔离级别的要求
InnoDB 意向锁
- 为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁.
- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁.
- 当一个表中的某一行被加上排他锁后,显然这个表就不能再被加表锁.那么数据库程序如何知道该表不能被加表锁?
- 一种方式是逐条的判断该表的每一条记录是否已经有排他锁,另一种方式是直接在表这一层级检查表本身是否有意向锁,不需要逐条判断,显然后者效率高.
死锁
多数情况下如果一个资源被锁定,它总会在以后某个时间被释放.但当多个进程访问同一数据时,其中每个进程拥有的锁都是其他进程所需的,由此造成每个进程都无法继续下去.简单的说,进程A等待进程B释放他的资源,B又等待A释放他的资源,这样就互相等待就形成死锁
产生原因
- 系统资源不足.
- 资源分配不当.
- 进程运行推进的顺序不合适.
死锁发生的条件
- 互斥条件:在一段时间内一个资源只能被一个进程使用.
- 请求与保持条件:进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放.
- 不可强占:线程已获得的资源在使用结束之前不可被强行剥夺,只能由自己释放.
- 循环等待:若干进程形成一种循环等待资源的关系.
处理策略:
预防
- 打破互斥条件:
- 允许进程同时访问资源,(有些资源无法共享,实用价值较低)
- 打破不可强占条件:
- 给进程设置优先级,高优先级的可以抢占资源(实现困难,降低系统性能)
- 打破请求与保持条件:
- 实行资源预分配策略,即进程在运行前一次性向系统申请它所需要的全部资源(不可预测资源的使用,利用率低,降低并发性)
- 破坏循环等待条件:
- 把资源事先分类编号,按号分配,所有进程对资源的请求必须严格按资源序号递增的顺序提出,使进程在申请占用资源时不会形成循环。(限制和编号实现困难,增加系统开销,有些资源暂时不用也需要先申请,增加了进程对资源的占用时间)
避免
- 允许进程动态申请资源,但系统在进行资源分配前,应先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程,否则让进程等待
- 所谓安全状态,是指系统能按某种进程推进顺序,为每个进程分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可以顺序地完成。并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可以避免进入死锁状态。银行家算法是最著名的死锁避免算法
检测与解除
- 不采取任何限制措施来保证系统不进入死锁状态,也就是允许死锁发生,但操作系统建立资源分配表和进程等待表,不断监督进程的路径,判断死锁是否真的发生。一旦死锁发生,则采取专门的措施解除死锁,并以最小代价使得整个系统恢复正常。
- 死锁解除可使用以下方法:
- 剥夺资源:从其他进程强制剥夺资源给死锁进程
- 撤销进程:直接撤销死锁进程,或撤销代价最小的进程
案例
- 一个用户A 访问表A(锁住了表A),然后又访问表B
- 另一个用户B 访问表B(锁住了表B),然后企图访问表A
- 用户A由于用户B已经锁住表B, 必须等待用户B释放表B才能继续, 同样用户B要等用户A释放表A才能继续,这样死锁就产生了.
InnoDB 行锁优化策略
- 尽可能让所有的数据检索都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级为表级锁定.
- 合理设计索引,让 InnoDB 在索引键上面加锁尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他查询的执行.
- 尽可能减少基于范围的数据检索过滤条件,避免间隙锁带来的负面影响而锁定了不该锁定的记录.
- 尽量控制事务的大小,减少锁定的资源量和锁定时间长度.
- 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本.