一、什么是事务
数据库事务(Database Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一系列对数据的操作组成。事务是数据库维护数据一致性、完整性和原子性的重要机制。以下是数据库事务的几个关键特性,通常被称为ACID属性
二、事务的四大特性(ACID)
-
原子性(Atomicity): 事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个点。如果事务中的某个操作失败,整个事务将被回滚到开始状态,就像这个事务从未发生过一样。
-
一致性(Consistency): 事务必须保证数据库从一个一致的状态转移到另一个一致的状态。在事务开始之前和事务结束之后,数据库的完整性约束都应该得到满足。
-
隔离性(Isolation): 并发执行的事务之间不会互相影响。数据库系统通常提供不同的隔离级别来平衡性能和隔离性,防止如脏读、不可重复读和幻读等问题。
-
持久性(Durability): 一旦事务提交,则其所做的修改将永久保存在数据库中,即使系统发生故障也不会丢失。
三、事务的隔离级别
- 未提交读(read-uncommitted)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为 脏读(Dirty Read)。
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读取数据,发现数据已经被修改了,这就是所谓的“脏读”
B:回滚事务
A:再次读数据,发现数据变回初始状态
经过上面的实验可以得出结论,事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录。
造成脏读现象。未提交读是最低的隔离级别。
- 已提交读(read-committed)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读数据,发现数据未被修改
B:提交事务
A:再次读取数据,发现数据已发生变化,说明B提交的修改被事务中的A读到了,这就是所谓的“不可重复读”
经过上面的实验可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,
即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。
已提交读只允许读取已提交的记录,但不要求可重复读。
- 可重复读(repetable-read)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读取数据,发现数据未被修改
B:提交事务
A:再次读取数据,发现数据依然未发生变化,这说明这次可以重复读了
B:插入一条新的数据,并提交
A:再次读取数据,发现数据依然未发生变化,虽然可以重复读了,但是却发现读的不是最新数据,这就是所谓的“幻读”
A:提交本次事务,再次读取数据,发现读取正常了
由以上的实验可以得出结论,可重复读隔离级别只允许读取已提交记录,而且在一个事务两次读取一个记录期间,
其他事务部的更新该记录。但该事务不要求与其他事务可串行化。
例如,当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题(注意是可能,
因为数据库对隔离级别的实现有所差别)。像以上的实验,就没有出现数据幻读的问题。
- 可串行化(serializable)
这是最高的隔离级别,它强制事务串行执行,避免了前面说的幻读现象,简单来说,它会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁争用问题。
A:启动事务,此时数据为初始状态
B:发现B此时进入了等待状态,原因是因为A的事务尚未提交,只能等待(此时,B可能会发生等待超时)
A:提交事务
B:发现插入成功
serializable完全锁定字段,若一个事务来查询同一份数据就必须等待,直到前一个事务完成并解除锁定为止。
是完整的隔离级别,会锁定对应的数据表格,因而会有效率的问题。
四、数据库的锁机制
1. 什么是数据库的锁?
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。
- 在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
2. 数据库锁分类
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
3. 全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML、DDL语句,已经更新操作的事务提交语句都将被阻塞
3.1 应用场景:
- 做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
- 如果不加全局锁,先后执行数据备份和业务的数据更新操作,会导致数据不一致
3.2 使用全局锁进行数据库逻辑备份的过程:
- 加全局锁
flush tables with read lock;
- mysqldump是数据库用于数据备份的工具,执行数据备份
- 注意:mysqldump是MySql提供的一个工具,不是sql语句,需要在windows命令行中执行
mysqldump -uroot -p123456 user>user.sql
- 在加锁后,DML和DDL被阻塞,其他客户端不能写入数据,但是DQL可以执行,其他客户端可以查找数据
- 备份结束,得到备份后的文件,释放锁
unlock tables;
3.3 全局锁的好处:
- 保证数据的完整性。
3.4 全局锁的弊端:
- 粒度很大,如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果业务数据库不是单机版而是主从结构,且做了读写分离,那么在从库上备份不会影响主库的读写操作,但是在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
3.5 其他实现一致性数据备份的方式:
在InnoDB引擎中可以在备份时加上参数 --single-transaction
参数来完成不加锁的一致性数据备份。其底层是通过快照读实现。
4. 表级锁
表级锁,顾名思义,在每次操作时锁住整张表。应用在MyISAM、InnoDB、BDB等存储引擎中
4.1 表级锁分类
- 表锁
- 元数据锁
- 意向锁
4.2 表锁
表锁分类:
- 表共享读锁(简称:读锁)
- 表独占写锁(简称:写锁)
加锁的语法:
lock tables tb1 , tb2... read / write
释放锁的语法:
unlock tables 释放锁
4.3 元数据锁
- 元数据锁(meta data lock,MDL),该锁是系统自动控制的,在访问一张表的时候自动上锁。
- 元数据可以简单理解为表结构,元数据锁的作用是维护表结构的数据一致性,避免DML和DDL之间发生冲突,保证读写正确性
- 在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL共享读锁和共享写锁(shared_read / shared_write);当对表结构进行变更操作的时候,加MDL排他锁(exclusive)。
- 共享锁之间相互兼容,表示可以边读边写;共享锁与排他锁互斥,表示在进行增删改查时,不能同时执行表结构的变更。
4.4 意向锁
- 为了避免DML在执行时,客户端A加的行锁与客户端B加的表锁的冲突,在InnoDB中引入了意向锁
- 意向锁使得客户端B在尝试加表锁时不用检查每行数据是否加了锁,直接根据是否有意向锁以及意向锁的类型来决定表锁是否可以添加成功,减少了表锁的检查。
意向锁的分类:
- 意向共享锁(IS):与表锁共享锁(shared_read)兼容,与表锁排他锁(write)互斥。 由语句
select ... lock in share mode
添加 。 - 意向排他锁(IX):与表锁共享锁(shared_read)及排他锁(write)都互斥,意向锁之间不会互斥。 由
insert、update、delete、select...for update
添加 。
我们可以通过以下语句查看意向锁是否添加成功:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
5 行级锁
- 行级锁:每次加锁锁住对应的数据行和行间的间隙,锁的粒度最小,并发度最高。
- InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。
5.1 行级锁的分类
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在Read Commit、Read Repeatable隔离级别下都支持。
- 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在Read Repeatable隔离级别下都支持。
- 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在Read Repeatable隔离级别下支持。
5.2 行锁
- 行锁有两种,分为共享锁和排它锁
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得同一数据集的排它锁(X)。即共享锁与共享锁之间兼容,共享锁和排它锁之间互斥。
- 排它锁(X):允许获取了排它锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排它锁。
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用临键锁进行搜索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,如果某字段没有创建索引,即不通过索引条件检索该字段的数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
5.3 以通过以下语句查看行锁是否添加成功
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
5.4 间隙锁和临键锁
- 默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用临键锁进行搜索和索引扫描,以防止幻读。
- 间隙锁唯一目的是防止其他事 务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。