事务
当涉及到数据库操作时,事务是一种重要的概念,它确保了数据库的数据一致性、可靠性和完整性。MySQL作为一种流行的关系型数据库管理系统(RDBMS),也支持事务的特性。以下是MySQL中事务特性的介绍:
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚。如果事务的任何部分失败,那么整个事务将回滚到事务开始前的状态,保证数据库的一致性。
- 一致性(Consistency):事务在执行前后,数据库必须保持一致状态,数据从一个合法状态到另一个合法状态。这意味着事务的操作不会破坏数据库的完整性约束,如唯一性、外键等。
- 事务前后业务整体要一致
- 例如:转帐前A:100元,B:100元,转账后A:80元,B:120元
- 隔离性(Isolation):多个并发的事务可以同时执行,但每个事务都感觉自己在独立操作数据库,不会受到其他事务的干扰。隔离性确保了并发事务之间的数据不会相互影响。
- 持久性(Durability):一旦事务提交成功,其所做的修改将永久保存在数据库中,即使发生系统崩溃或重新启动,数据也不会丢失。
MySQL提供了以下几种事务的隔离级别,可以通过设置来控制事务之间的隔离性:
-
读未提交(Read Uncommitted):最低的隔离级别,允许事务读取其他事务未提交的数据。可能会导致脏读、不可重复读和幻读的问题。
-
读已提交(Read Committed):事务只能读取已经提交的数据。这可以避免脏读,但仍可能遇到不可重复读和幻读。
-
可重复读(Repeatable Read):事务开始后,保证在事务结束前,同一查询读取的数据是一致的,即使其他事务进行了修改。可以避免不可重复读,但仍可能遇到幻读。
-
串行化(Serializable):最高的隔离级别,通过强制所有事务串行执行来避免并发问题。可以避免所有并发问题,但性能开销较大。
在MySQL中,可以使用以下方式来控制事务:
- 使用
BEGIN
或START TRANSACTION
来开始一个新的事务。 - 使用
COMMIT
提交事务,将之前的操作永久保存到数据库。 - 使用
ROLLBACK
回滚事务,取消之前的操作。 - 使用
SET TRANSACTION
来设置事务的隔离级别和其他属性。
事务的状态
在数据库管理系统中,事务具有不同的状态,用于描述其在生命周期中所处的状态。一个事务从开始到最终提交或回滚都会经历一系列状态。以下是事务的常见状态:
-
活动(Active):事务刚刚开始并且正在执行数据库操作。在这个阶段,事务可以执行读取和写入操作。
-
部分提交(Partially Committed):事务执行了最后的操作,但尚未完全提交。在这个阶段,事务的修改已经应用到数据库中,但仍未最终确认。
-
失败(Failed):如果事务在执行过程中遇到错误,比如违反了完整性约束或其他问题,它将进入失败状态。在这种情况下,事务不能继续执行,必须回滚或者重新尝试。
-
中止(Aborted):事务在失败后可以选择回滚,这会将所有的操作撤消,使数据库回到事务开始前的状态。这个状态也可以称为中止状态。
-
提交(Committed):如果事务成功完成并且通过提交操作确认了其所有操作,那么它将进入提交状态。在这个阶段,事务的修改已经永久应用到数据库中。
-
挂起(Suspended):事务可以在活动状态时被挂起,然后稍后恢复执行。这可以用于处理一些特殊情况,例如暂时让出资源给其他事务。
事务的分类
这些术语描述了不同类型的事务或事务管理机制。让我为您解释一下每种类型:
-
扁平事务(Flat Transaction): 扁平事务是一种最基本的事务类型,它包含一系列数据库操作,这些操作被当作一个单一的事务来执行。扁平事务没有嵌套结构,所有操作在同一个事务中执行,并在最后进行提交或回滚。
-
带有提交点的事务(Savepoint Transaction): 带有提交点的事务是一种允许在事务执行过程中设置临时保存点的事务。这些保存点允许事务在发生错误时部分回滚,而不需要完全回滚整个事务。这对于长事务或需要部分撤销的情况非常有用。
-
链事务(Chained Transaction): 链事务是一种将多个事务链接在一起执行的机制。每个事务在一个事务结束后启动,并且在之前事务的结果上继续操作。链事务通常用于处理需要分阶段执行的操作,例如在一个事务成功后启动下一个事务。
-
嵌套事务(Nested Transaction): 嵌套事务是指一个事务包含了另一个或多个事务,形成一个层次结构。在嵌套事务中,内部事务可以作为外部事务的一部分执行。然而,嵌套事务的支持和语义在不同的数据库系统中可能会有所不同。
-
分布式事务(Distributed Transaction): 分布式事务涉及到多个不同的数据库或资源,这些数据库或资源可能位于不同的物理位置。分布式事务需要协调机制来确保所有相关数据库或资源在事务执行过程中的一致性,以防止数据不一致问题。
事务的隔离级别
-
读未提交(Read Uncommitted): 在这个隔离级别下,一个事务的修改对其他事务是可见的,即使这些修改尚未提交。这会导致脏读(读取未提交的数据)、不可重复读(同一查询在不同时间读取到不同数据)、幻读(查询的结果行数不一致)等问题。
-
读已提交(Read Committed): 这是许多数据库系统的默认隔离级别。在这个级别下,一个事务的修改只对其他事务在其提交后可见。这可以避免脏读问题,但仍可能遇到不可重复读和幻读问题。
-
可重复读(Repeatable Read): 在这个隔离级别下,一个事务在执行期间多次读取相同数据时,保证读取的数据是一致的,即使其他事务进行了修改。这可以避免不可重复读问题,但仍可能遇到幻读问题。
-
串行化(Serializable): 这是最高的隔离级别,它确保事务之间完全隔离,一个事务执行期间其他事务无法修改相同数据。这可以避免脏读、不可重复读和幻读等所有并发问题,但会降低并发性能。
以下是对四个隔离级别进行的简要比较,以表格形式呈现:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 |
---|---|---|---|---|
读未提交(Read Uncommitted) | 是 | 是 | 是 | 高 |
读已提交(Read Committed) | 否 | 是 | 是 | 中 |
可重复读(Repeatable Read) | 否 | 否 | 是 | 低 |
串行化(Serializable) | 否 | 否 | 否 | 最低 |
这些术语都是与数据库事务的隔离性和并发性有关的问题:
-
脏读(Dirty Read): 脏读指的是在一个事务中读取了另一个事务尚未提交的数据。当一个事务读取了另一个事务的未提交数据时,如果后者在随后回滚,那么前者读取的数据就是无效的。脏读可能会导致不准确的结果和错误的决策。
-
不可重复读(Non-Repeatable Read): 不可重复读是指在同一个事务中,同一查询多次读取同一行数据,但由于其他事务的修改,每次读取的数据不同。这可能会导致事务内部的数据不一致问题,影响一致性。
-
幻读(Phantom Read): 幻读是在同一个事务中多次查询时,发现之前不存在的数据行出现了。这通常是由于其他事务插入、更新或删除了数据,导致前后两次查询结果不一致。幻读问题在某些情况下比不可重复读更严重,因为它涉及到数据行的新增或减少。
-
脏写(Dirty Write): 脏写是指一个事务对某个数据进行修改,但在事务提交之前,另一个事务也修改了同一份数据,并在后者提交之前先提交了自己的事务。这可能导致前者的修改被后者的修改覆盖,从而引发数据不一致问题。
日志
在MySQL中,"redo"日志和"undo"日志是两种重要的日志类型,用于确保事务的持久性、一致性和并发性,两者都是恢复操作。以下是关于MySQL中redo日志和undo日志的详细信息:
-
Redo日志(重做日志):
- Redo日志是MySQL中的事务日志,它记录了对数据的修改操作,例如插入、更新和删除操作。
- 这些修改操作被记录在redo日志中,以便在数据库崩溃或异常关闭后,可以通过重放这些操作来恢复数据的一致性。
- Redo日志的记录是顺序的,这意味着每次操作被添加到日志中时,它们按照特定的顺序进行,以确保恢复时的正确性。
- 用来保持持久性
-
Undo日志(撤销日志):
- Undo日志记录了事务执行前的数据状态,或者说是操作前的旧值。它主要用于回滚事务、维护事务的隔离性,以及支持多版本并发控制。
- 当一个事务进行修改时,MySQL将之前的数据值记录在Undo日志中,以便在事务回滚或发生崩溃时可以将数据恢复到操作前的状态。
- Undo日志也对数据库中的多版本并发控制机制(MVCC)起着关键作用。MVCC允许事务在并发执行时看到不同的数据版本,而Undo日志可以用于提供旧版本的数据。
- 用来保持原子性和一致性
这两种日志类型一起支持MySQL的事务特性和并发性能。它们确保了事务的持久性、一致性和隔离性,以及在数据库崩溃或异常关闭时的数据恢复能力。在数据库系统中,redo日志和undo日志是不可或缺的组成部分,它们使得MySQL可以在各种情况下保障数据的完整性和可靠性。
下面是一个表格,详细比较了MySQL中的redo日志和undo日志的主要区别:
特征 | Redo日志 | Undo日志 |
---|---|---|
记录的操作 | 记录已提交事务的修改操作 | 记录事务之前的旧值 |
目的 | 保证事务的持久性和恢复 | 支持回滚、隔离性和MVCC |
用途 | 数据库崩溃恢复和持久性 | 事务回滚、维护隔离性、MVCC |
记录的顺序 | 顺序记录,确保恢复的正确性 | 顺序记录,支持回滚和恢复 |
数据恢复 | 通过重放操作实现数据恢复 | 通过恢复旧值实现数据恢复 |
对性能的影响 | 增加了写入的开销,降低了性能 | 增加了写入的开销,降低了性能 |
并发控制的支持 | 不直接用于并发控制 | 支持多版本并发控制(MVCC) |
段落 | 物理日志 | 逻辑日志 |
Redo日志
InnoDB和磁盘每次是以页为单位进行数据交换的,MySQL引入Redo日志的主要目的是为了优化数据库系统的性能和数据完整性,并在数据库崩溃等情况下提供更好的恢复能力。每次更新数据时不直接将数据写入磁盘的原因:
-
性能优化: 直接将每次更新的数据写入磁盘可能会导致大量的随机I/O操作,这会降低写入性能。Redo日志的引入允许数据库系统将修改操作追加到顺序的日志中,从而减少随机I/O,提高写入性能。随后,系统可以在适当的时候将这些修改持久化到磁盘。
-
数据完整性: 即使数据库发生崩溃或异常关闭,未持久化的数据修改也可以通过重放Redo日志来恢复,确保数据的一致性和完整性。这样可以避免数据丢失或损坏。
-
写入策略: MySQL采用了“write-ahead logging”(WAL,日志优先写)策略,即在修改数据之前,首先将修改操作写入Redo日志。这样,在数据库崩溃时,可以通过重放Redo日志来重新应用修改,而不是直接将修改写入磁盘。这样的策略有助于维护数据的一致性。
-
并发性能: 引入Redo日志还有助于支持并发事务。多个事务可以并行地写入Redo日志,因为这些操作是顺序追加的,从而提高了并发写入性能。
Redo日志通常是顺序I/O。在数据库系统中,Redo日志被设计为顺序写入的,这意味着每次写入Redo日志时,数据被追加到现有的日志末尾,而不是随机地写入磁盘的不同位置。
顺序I/O对于性能是非常重要的,因为现代磁盘和存储系统通常会对连续的数据块进行优化,以提供更高的吞吐量。相比之下,随机I/O(在不同位置的读写操作)通常会导致更多的寻道和旋转延迟,降低了性能。
通过将Redo日志设计为顺序I/O,数据库系统可以实现以下优势:
-
写入性能: 顺序I/O操作比随机I/O操作更高效,因此将修改操作追加到Redo日志中可以提供更好的写入性能。
-
持久性: 由于Redo日志的顺序写入特性,即使数据库崩溃,也可以通过重放Redo日志中的操作来恢复未持久化的数据修改,从而保证数据的持久性。
-
并发性能: 多个事务可以并行地写入Redo日志,因为它们都是追加操作,这提高了并发写入性能。
更新流程
Redo日志(重做日志)是数据库管理系统中用于记录事务修改操作的日志。下面是Redo日志更新流程的简要描述:
-
事务开始: 当一个事务开始时,它执行修改操作(例如插入、更新、删除)来修改数据库中的数据。
-
重做操作生成: 在每个修改操作执行时,数据库管理系统会生成相应的重做操作。这些操作描述了对哪些数据进行了怎样的修改。
-
重做操作写入Redo Log Buffer: 生成的重做操作首先会被写入一个称为Redo Log Buffer的内存区域。这个缓冲区保存了最新的重做操作,但还没有被写入到磁盘上的Redo日志文件。
-
事务提交: 当事务提交时,数据库将会把Redo Log Buffer中的重做操作写入到Redo日志文件。
-
持久化到磁盘: 为了保障数据的持久性,数据库系统会在适当的时机将Redo日志文件中的内容持久化到磁盘上。
-
提交完成: 一旦Redo日志文件中的内容已经被持久化到磁盘上,这个事务就被认为是提交完成,即事务的修改已经被安全地记录下来。
事务开始后,每一条更新语句都会写入Redo Log Buffer中,事务提交时将Redo Log Buffer中的内容写入到redo log日志
刷盘策略
innodb_flush_log_at_trx_commit
是MySQL中InnoDB存储引擎的一个配置参数,用于控制事务提交时何时将Redo日志刷新到磁盘。这个参数的取值可以影响数据库的性能和数据持久性。以下是不同取值的含义:
-
取值为0:
- 表示在每次事务提交时,InnoDB并不会强制立即将Redo日志写入磁盘。而是在一些特定情况下才进行刷新,例如检查点(checkpoint)操作。
- 这种设置可以提高性能,因为避免了频繁的磁盘写入操作。但是,如果数据库发生崩溃,可能会丢失一些未刷新的日志,从而可能影响到事务的持久性。
-
取值为1(默认值):
- 表示在每次事务提交时,InnoDB都会强制立即将Redo日志写入磁盘。
- 这种设置确保了事务的持久性,即使在数据库崩溃时也能够进行恢复。但是,频繁的磁盘写入可能会对性能产生一定的影响。
-
取值为2:
- 表示在每次事务提交时,InnoDB会将Redo日志写入磁盘,但不会等待磁盘写入的确认。相当于在提交后,将日志放入操作系统的缓冲区,然后由操作系统决定何时将其刷写到磁盘。
- 这种设置在持久性和性能之间做了一定的平衡,因为不需要等待磁盘确认,可以提高性能,但仍然保留了一定的持久性。
参数取值 | 写入性能 | 持久性 | 适用场景 |
---|---|---|---|
0 | 最高 | 低,可能丢失未刷新的Redo日志 | 针对性能要求高,可以接受一定数据风险的场景 |
1 | 较低 | 高,保障事务持久性 | 数据完整性要求高,需要确保数据不丢失的场景 |
2 | 中等 | 中等,相对风险较低 | 在性能和持久性之间需要平衡的场景 |
Undo日志
Undo日志和Redo日志在数据库事务中是相互关联的。当执行事务中的插入、删除和更新等操作时,不仅会生成Undo日志,还会生成对应的Redo日志。这两种日志在数据库的事务处理过程中发挥不同但关键的作用。
Undo日志(回滚日志)是数据库管理系统用于支持事务原子性的重要组成部分。它通过记录事务的逆操作,即回滚操作,来确保事务的原子性。下面是Undo日志如何保证事务原子性的工作原理:
-
回滚操作记录: 在事务执行期间,更新数据之前记录日志,数据库会为每个修改操作生成相应的回滚操作记录,并将这些记录写入Undo日志中。回滚操作记录描述了如何撤消相应的数据修改,从而回到事务开始前的状态,严格来说并不是回滚操作,而是一种弥补操作。
-
撤消操作: 如果在事务执行过程中发生了错误、回滚或者事务被撤销,数据库会使用Undo日志中的回滚操作记录,执行相应的撤消操作。这些撤消操作将修改的数据恢复到事务开始前的状态。
-
保证原子性: 如果事务的某个部分操作失败,数据库可以使用Undo日志中的回滚操作记录,将之前已经执行的操作撤消。这确保了事务的原子性,即要么所有操作都成功并提交,要么所有操作都被撤销,不会留下部分执行的操作。
-
Undo段: 数据库维护一个称为Undo段的区域,用于存储Undo日志记录所需的回滚数据。每个事务在开始时都会分配一个Undo段,事务中的修改操作会在Undo段中生成相应的回滚数据。
总而言之,Undo日志通过记录回滚操作来支持事务的原子性。它允许数据库在事务执行过程中出现错误或中断时,使用回滚操作记录来撤消已经执行的操作,从而确保事务要么全部提交,要么全部撤销,保障了数据的一致性和完整性。
在数据库管理系统中,当执行插入记录、删除记录和更新记录等操作时,Undo日志记录会根据不同的操作类型发生变化。以下是针对每种操作类型的Undo日志变化的解释:
-
插入记录:
- 当执行插入记录操作时,数据库会为插入的数据生成相应的Undo日志记录。这个Undo日志记录包含了插入操作的逆操作,即删除操作。
- 这是为了保证事务的原子性。如果事务在插入数据后回滚或出现错误,数据库可以使用Undo日志中的记录执行相应的删除操作,将插入的数据删除,从而回到事务开始前的状态。
-
删除记录:
- 当执行删除记录操作时,数据库会为被删除的记录生成相应的Undo日志记录。这个Undo日志记录包含了删除操作的逆操作,即插入操作。
- 与插入操作类似,这个Undo日志记录确保了在事务回滚或出现错误时,可以撤消删除操作,重新插入被删除的记录,从而保持事务的原子性。
-
更新记录:
- 当执行更新记录操作时,数据库会为被更新的记录生成相应的Undo日志记录。这个Undo日志记录包含了更新前的数据,以及更新操作的逆操作,即将更新的数据恢复到原始状态。
- 如果事务回滚或出现错误,数据库可以使用Undo日志中的记录将被更新的数据恢复到更新前的状态,从而维护事务的原子性。
总的来说,Undo日志在插入、删除和更新记录操作中的作用是保障事务的原子性。通过记录操作的逆操作,即回滚操作,Undo日志确保了事务的一致性和完整性,即使在事务回滚、错误或中断的情况下,也能够正确恢复数据的状态。
作用
Undo日志(回滚日志)在数据库管理系统中具有重要的作用,主要用于支持事务的原子性、隔离性和一致性,以及恢复和并发控制等方面。以下是Undo日志的主要作用:
-
事务回滚: Undo日志记录了事务执行过程中的回滚操作,即逆操作。如果事务执行过程中发生错误,或者事务被显式回滚,数据库可以使用Undo日志中的回滚操作来撤消已经执行的操作,从而将数据库恢复到事务开始前的状态,保证事务的原子性。
-
并发控制: 在并发执行多个事务的情况下,Undo日志用于支持多版本并发控制(MVCC)机制。每个事务在执行过程中,生成的回滚操作记录可以用于构建数据的不同版本,从而实现事务之间的隔离性。
-
防止脏读: 在某个事务执行过程中,如果另一个事务读取了未提交的数据,可以使用Undo日志中的回滚操作将这些未提交的数据恢复到原始状态,避免了脏读的发生。
-
崩溃恢复: Undo日志对数据库的崩溃恢复至关重要。在数据库崩溃或异常关闭后,通过重放Undo日志中的回滚操作,可以确保未提交的事务所做的修改被正确地撤销,保障数据库的一致性和完整性。
-
历史版本查询: Undo日志记录了事务执行过程中的历史状态变化,使得数据库可以支持历史版本查询,即查询数据在不同时间点的状态。
总而言之,Undo日志是数据库系统中的重要组成部分,它通过记录回滚操作,支持了事务的原子性、隔离性和一致性,以及并发控制、崩溃恢复和历史版本查询等多个关键功能。
InnoDB 3个事务隐藏列
InnoDB存储引擎在实现事务和多版本并发控制(MVCC)时,使用了一些隐藏列来维护事务的相关信息。这些隐藏列在表的物理存储结构中存在,但不会在逻辑模型中显式显示出来。下面是一些与事务和MVCC相关的InnoDB隐藏列:
-
隐藏的事务ID列(Hidden Transaction ID Column): InnoDB使用隐藏的事务ID列来标识事务的唯一标识符。每个事务在执行时会被分配一个唯一的事务ID,这个事务ID用于记录数据的创建和修改时间戳。这些时间戳信息是多版本并发控制(MVCC)的关键部分,用于支持不同事务之间的隔离。
-
隐藏的回滚指针列(Hidden Rollback Pointer Column): 在InnoDB的MVCC实现中,每个数据行都会有一个隐藏的回滚指针列,用于指向Undo日志中的相应记录。这个回滚指针列在数据库需要执行回滚操作时,用于定位Undo日志中的回滚记录,以便撤消数据的修改。
-
隐藏的版本号列(Hidden Version Number Column): InnoDB使用隐藏的版本号列来标识数据的版本号。每次对数据进行修改时,版本号会递增,这个版本号也是MVCC机制的一部分,用于确定数据在事务的时间线上的位置。
这些隐藏列对于InnoDB存储引擎内部实现事务和多版本并发控制非常重要,但在一般情况下,用户不需要直接操作或管理这些列。它们确保了事务的隔离性、一致性和回滚能力,同时支持并发控制和多版本查询等功能。
锁
在数据库中,锁(Lock)是用于控制并发访问数据的机制。锁可以分为不同类型,包括读锁(Shared Lock)和写锁(Exclusive Lock),它们在不同的情况下用于控制读取和写入操作的并发性。以下是读读、写写和读写的锁模式的介绍:
-
读读(Read-Read)锁:
- 读读锁是一种共享锁,也称为共享读锁或共享锁。多个事务可以同时持有读读锁,允许它们以并发方式读取相同的数据。
- 读读锁之间互不阻塞,这意味着多个事务可以同时持有读读锁,不会互相影响。
- 读读锁用于实现读取操作的并发性,多个事务可以同时读取数据,而不会破坏数据的一致性。
-
写写(Write-Write)锁:
- 写写锁是一种排他锁,也称为独占写锁或排他锁。只有一个事务可以持有写写锁,该事务可以对数据进行修改。
- 写写锁之间会互斥,即如果一个事务持有写写锁,其他事务无法同时获取写写锁,防止多个事务同时修改数据,从而保证数据的一致性。
- 解决方案是排队等待
-
读写(Read-Write)锁:
- 读写锁是共享锁和排他锁的组合。一个事务持有读写锁时,其他事务可以持有读读锁,但无法持有写写锁。
- 读写锁用于实现读取和写入操作之间的平衡。多个事务可以同时读取数据,但只有一个事务可以修改数据。
这些锁模式在数据库的并发环境中非常重要,用于确保数据的一致性、隔离性和并发控制。根据事务的操作类型和并发需求,数据库系统会选择适当的锁模式来实现并发操作。在实际应用中,合理使用这些锁模式可以提高数据库的性能和数据的完整性。
读写锁
S锁(Shared Lock)和X锁(Exclusive Lock)是数据库中的两种常见的锁类型,用于控制并发访问数据的方式。它们在多用户环境中协调事务的读写操作,以确保数据的一致性和隔离性。
-
S锁(Shared Lock):
- 也称为共享锁或读锁。
- 允许多个事务同时持有共享锁,并且不会互相阻塞。
- 当一个事务持有S锁时,其他事务也可以持有S锁,但不能持有X锁。
- S锁主要用于读操作,多个事务可以同时读取相同的数据,不会破坏数据的一致性。
- 在事务持有S锁期间,其他事务可以继续持有S锁,但不能获得X锁,以保证并发读取的情况。
-
X锁(Exclusive Lock):
- 也称为排他锁或写锁。
- 只允许一个事务持有排他锁,其他事务无法同时持有S锁或X锁。
- 当一个事务持有X锁时,其他事务无法持有任何锁,包括S锁和X锁。
- X锁用于写操作,确保在一个事务修改数据时,其他事务无法读取或修改相同的数据,保证数据的一致性和隔离性。
- 通过X锁,数据库防止多个事务同时对同一数据进行写操作,避免了数据冲突和不一致的情况。
S锁和X锁是数据库并发控制的基础,它们在不同的事务操作中提供了合适的锁定机制,确保了数据的正确性和事务的隔离性。通过适当的锁策略,数据库可以平衡读写操作的并发性和数据的一致性。
显式的使用锁
在MySQL中,可以通过显式地添加锁来控制查询操作的并发性和隔离性。通过添加锁,可以在事务中明确指定需要的锁类型,以满足特定的业务需求。以下是一些在MySQL中显式加锁的方法:
-
FOR SHARE(共享锁):
使用FOR SHARE
关键字可以在查询中显式地添加共享锁。共享锁允许其他事务持有共享锁,但不允许持有排他锁,从而保护数据免受写操作的干扰。示例:
SELECT * FROM table_name FOR SHARE;
-
FOR UPDATE(排他锁):
使用FOR UPDATE
关键字可以在查询中显式地添加排他锁。排他锁会阻塞其他事务的共享锁和排他锁,确保在事务内进行更新操作时的数据一致性和独占性。示例:
SELECT * FROM table_name FOR UPDATE;
-
LOCK IN SHARE MODE(共享锁):
使用LOCK IN SHARE MODE
关键字也可以在查询中显式地添加共享锁,与FOR SHARE
类似。示例:
SELECT * FROM table_name LOCK IN SHARE MODE;
这些显式的锁操作可以在事务中使用,以满足特定的业务需求。需要注意的是,显式加锁可能会影响并发性能,因此应该谨慎使用,并根据具体的情况进行权衡。在使用显式锁的时候,务必确保锁的获取和释放逻辑正确,避免造成死锁或长时间的锁等待。
粒度
在MySQL中,锁是用于控制并发访问数据的机制。MySQL提供了不同层次的锁,包括表锁、页锁和行锁,以适应不同的并发需求。以下是MySQL中这些不同级别的锁的介绍:
-
表锁(Table Lock):
- 表锁是一种最粗粒度的锁,用于锁定整个表。当一个事务持有了表锁,其他事务无法对同一表进行任何读写操作,甚至是读操作也会被阻塞。
- 表锁对并发性能影响较大,因为锁定整个表会限制其他事务的操作,尤其是写操作。
- 表锁适用于对整个表进行大规模的维护操作,但在大多数情况下,由于过于粗粒度,不推荐使用表锁。
-
页锁(Page Lock):
- 页锁是介于表锁和行锁之间的一种锁,用于锁定数据库的一页数据。每页数据通常包含多行记录。
- 页锁对并发性能仍然有一定影响,因为多个事务无法同时修改同一页的数据。
- 页锁在一些特定场景下可能有用,但也因为锁粒度较大,有时可能导致一些不必要的锁等待。
-
行锁(Row Lock):
- 行锁是最细粒度的锁,用于锁定表中的单行数据。每个数据行都可以有自己的行锁。
- 行锁提供了最好的并发性能,允许多个事务同时操作不同的数据行,从而避免了锁的争用。
- 行锁适用于大多数的读写场景,特别是需要高并发性能的应用。
在InnoDB存储引擎中,行级锁是默认的锁级别,也是最常用的锁级别。使用行级锁可以更好地平衡并发性能和数据一致性,减少了不必要的锁冲突和锁等待。不过,在某些情况下,页锁和表锁仍然可以作为某些操作的合理选择,需要根据具体的业务需求来决定使用哪种级别的锁。
表锁
在MySQL中,表锁是一种粗粒度的锁,用于控制整个表的并发访问。当一个事务持有了表级锁时,其他事务无法同时获取相同表的锁,从而限制了对表的并发操作。MySQL支持不同类型的表级锁,包括读锁和写锁,以及不同的锁级别。下面是MySQL中的一些常见表级锁类型:
-
读锁(Read Lock):
- 也称为共享锁,允许多个事务同时持有读锁,这些事务可以并发读取数据,但不能同时持有写锁。
- 读锁通常用于保护读操作,允许多个事务同时读取数据,不会互相影响。
-
写锁(Write Lock):
- 也称为排他锁,只允许一个事务持有写锁,当一个事务持有写锁时,其他事务无法同时持有读锁或写锁。
- 写锁用于保护写操作,确保只有一个事务可以进行写操作,防止并发写入和数据的不一致性。
MySQL中的表锁可以通过不同的语句来获取,包括:
LOCK TABLES
:通过LOCK TABLES
语句可以显式地锁定一个或多个表,可以指定读锁或写锁。UNLOCK TABLES
:用于释放之前通过LOCK TABLES
获取的表锁。
需要注意的是,表锁是一种较为粗粒度的锁,会对整个表的并发操作产生影响。因此,在使用表锁时需要谨慎考虑,并根据业务需求选择合适的锁级别。为了更细粒度地控制并发,推荐使用行级锁,如使用InnoDB存储引擎提供的行级锁机制,以避免锁对并发性能的不利影响。
查看所有使用表锁的表
show open tables where in_use > 0;
加锁
lock tables 表名 write;
lock tables 表名 read;
解锁
lock tables;
意向锁
表级别的意向锁(Intention Lock)是MySQL中的一种锁机制,用于协调并管理在不同级别锁之间的关系,以避免锁冲突。意向锁并不是用来保护数据的,而是用来向其他事务表明,某个事务即将在数据行上设置锁,可以快速的判断一个表上有没有加行锁。
应用场景:给一个表加表锁前,需要判断一下这个表有没有行锁,在没有意向锁之前,可能需要一行一行遍历,性能比较差,而有了意向锁之后,看一下意向锁只能知道这个表有没有行锁
MySQL中的意向锁分为两种:
-
意向共享锁(Intention Shared Lock,IS):
- 意向共享锁表示事务打算在某个数据行上设置共享锁。当事务在某个数据行上持有共享锁时,会为该数据行设置一个意向共享锁,以通知其他事务,该数据行上可能有共享锁存在。
- 意向共享锁不会阻止其他事务获得共享锁,但会阻止其他事务获得排他锁。
-
意向排他锁(Intention Exclusive Lock,IX):
- 意向排他锁表示事务打算在某个数据行上设置排他锁。当事务在某个数据行上持有排他锁时,会为该数据行设置一个意向排他锁,以通知其他事务,该数据行上可能有排他锁存在。
- 意向排他锁会阻止其他事务获得任何类型的锁,包括共享锁和排他锁。
意向锁的主要作用是协调不同级别锁的获取,以避免死锁的发生。事务在获取锁之前,会首先尝试获取适当级别的意向锁,以确保没有其他事务正在获取更高级别的锁。这种机制可以帮助事务更好地协同工作,避免锁争用和死锁。
需要注意的是,意向锁是MySQL内部使用的机制,通常不需要手动操作意向锁。数据库会根据事务的锁请求自动管理意向锁,以保证并发性能和数据的一致性。
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
意向共享锁(IS) | 兼容 | 兼容 |
意向排他锁(IX) | 兼容 | 兼容 |
select * from table where id = 1 for update;
select * from table where id = 2 for update;
以下是表级共享锁、排他锁和意向锁的兼容情况
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
共享锁(S) | 兼容 | 互斥 |
排他锁(X) | 互斥 | 互斥 |
行锁
粒度最小的锁。
InnoDB存储引擎会在一定情况下自动加行锁。当在事务中执行查询、插入、更新或删除操作时,InnoDB会根据操作的类型和语句的隔离级别自动加上适当的行级锁,以保证数据的一致性和隔离性。这种自动的行级锁机制使得你不必显式地编写加锁的语句,数据库会根据需要自动管理锁的获取和释放。
以下是一些情况下InnoDB会自动加行锁的示例:
-
查询操作(读操作): 执行一个查询操作,根据事务的隔离级别,InnoDB会在需要的数据行上加上共享锁(S锁),以允许其他事务并发地读取同一数据行。
-
插入、更新、删除操作(写操作): 执行插入、更新或删除操作时,InnoDB会在被操作的数据行上加上排他锁(X锁),以确保只有当前事务可以修改这些数据行,防止其他事务的并发写操作。
-
死锁检测: InnoDB会根据需要在事务之间自动加行锁,以帮助检测和解除死锁情况,从而保证事务的正常执行。
需要注意的是,虽然InnoDB会自动管理行锁的获取和释放,但在一些复杂的并发场景中,你可能需要考虑显式地使用锁来控制并发操作,以避免不必要的锁冲突和性能问题。
间隙锁
间隙锁(Gap Lock)是InnoDB存储引擎中的一种特殊类型的锁,用于在多个事务并发执行范围查询时保护数据的一致性。间隙锁用于锁定数据范围之间的间隙,防止其他事务在该范围内插入新数据,从而确保查询的一致性和防止幻读现象的发生,所以说SQL标准中的可重复读没有解决幻读问题,而MySQL的可重复读解决了幻读问题。
下面是间隙锁的一些关键特点和工作原理:
-
范围查询: 间隙锁通常在执行范围查询时产生,例如使用
WHERE
子句指定了一个数据范围的查询。这样的查询涉及到范围内的已有数据行,以及范围之间的未存在数据(间隙)。 -
避免幻读: 间隙锁的主要目的是防止幻读现象。幻读指的是在一个事务内多次执行相同查询,但在查询过程中其他事务插入了新数据,导致查询结果发生变化。通过使用间隙锁,InnoDB可以锁定范围内的间隙,阻止其他事务在该间隙内插入新数据,从而避免幻读的问题。
-
间隙的锁定: 在间隙锁的范围查询中,InnoDB会在范围内的已有数据行上加上共享锁(S锁),并在范围之间的间隙上加上间隙锁。这样其他事务就无法插入新数据到被间隙锁保护的范围内。
-
兼容性: 间隙锁与行级锁兼容,意味着事务可以同时持有行级锁和间隙锁,以保护数据行和范围。
需要注意的是,间隙锁虽然可以解决幻读的问题,但也会影响并发性能。因为其他事务无法插入新数据到被间隙锁保护的范围内,可能会引起锁等待。因此,在使用间隙锁时,需要在保证数据一致性的基础上权衡性能,根据具体的业务需求和查询情况来选择是否需要间隙锁。
Next-key锁
Next-Key Lock(临键键锁)是InnoDB存储引擎中的一种锁类型,它是为了解决幻读问题而引入的。Next-Key Lock实际上是对行锁和间隙锁的组合,用于在范围查询中同时锁定已有数据行和范围间隙,以保护数据的一致性。
next-key锁 = 记录锁 + 间隙锁
具体来说,Next-Key Lock的工作方式如下:
-
行锁: 对于范围查询中的已有数据行,InnoDB会在这些行上加上适当类型的行级锁(共享锁或排他锁),以防止其他事务对这些行进行并发的读写操作。
-
间隙锁: 对于范围查询的范围间隙,InnoDB会在这些间隙上加上间隙锁,以防止其他事务在这些间隙内插入新数据,从而防止幻读问题。
通过行锁和间隙锁的组合,Next-Key Lock可以在范围查询中实现更严格的隔离性,避免了幻读问题的发生。这种机制确保了查询过程中没有新的数据插入或删除,保护了查询结果的一致性。
需要注意的是,Next-Key Lock的引入会增加锁的粒度和锁的数量,可能会导致更多的锁等待情况,从而影响并发性能。在使用Next-Key Lock时,需要权衡隔离性和性能,并根据业务需求来选择合适的隔离级别和锁策略。
例如开启事务后:
select * from ttt where id >= 5 and id <= 8 for update;
即使5-8
之间有不存在的id
,此时其他事务也无法插入成功,需要等待锁的释放
insert into ttt(id, name) values (6, '44444');
悲观锁和乐观锁
悲观锁和乐观锁是并发控制的两种不同策略,用于在多个事务并发访问数据时保证数据的一致性和正确性。
悲观锁:
悲观锁是一种较为保守的锁策略,它假设在事务执行期间会发生并发冲突,因此在事务访问数据之前就会尝试获取锁,以防止其他事务对数据进行修改。悲观锁的典型应用是通过数据库中的行级锁实现。
应用场景:
- 在高并发环境下,当多个事务同时访问或修改同一条数据时,使用悲观锁可以确保只有一个事务能够修改数据,防止数据的不一致性。
- 在需要保证数据完整性和隔离性的场景中,比如订单、库存等涉及重要数据的操作。
乐观锁:
乐观锁是一种更加乐观的锁策略,它假设并发冲突发生的可能性较低,因此不会在事务访问数据之前获取锁。相反,它会在事务提交时检查数据是否被其他事务修改过,如果没有被修改,就会成功提交事务,否则会产生冲突并处理。
应用场景:
- 在并发冲突相对较少的情况下,使用乐观锁可以避免频繁地加锁和解锁,提高了性能。
- 乐观锁常用于基于版本号(Versioning)或时间戳(Timestamp)的机制。在更新数据时,事务会检查数据的版本号或时间戳,如果与预期不符,则认为发生了冲突。
总结:
悲观锁适用于高并发、数据更新频繁,且需要保证数据一致性的场景。乐观锁适用于并发冲突较少、对性能要求较高的场景。选择适合的锁策略要根据具体应用需求来决定,有时也可以结合两种锁策略的特点来进行更有效的并发控制。
全局锁
全局锁仅允许读取数据库中的记录,不允许写,经常用于数据库备份时开启
flush table with read lock;
# 解锁
unlock tables;
MVCC
多版本并发控制(MVCC,Multi-Version Concurrency Control)是一种在数据库管理系统中用于处理并发访问数据的技术。MVCC允许多个事务在同一时间并发地读取和修改数据,同时保持事务之间的隔离性和一致性,从而提高数据库的并发性能。
MVCC的核心思想是为每个事务创建一个数据的“快照”或“版本”,使得每个事务看到的数据是一致的,而不受其他并发事务的影响。每个事务可以独立地访问自己的版本,从而避免了锁的争用和等待。
在MVCC中,每个数据行都会包含多个版本,每个版本都有一个时间戳,表示事务的提交时间。下面是MVCC的一些关键特点和工作原理:
-
读操作: 当事务执行读操作时,系统会根据事务的时间戳选择合适的版本返回给事务。事务只能看到在其开始时间之前提交的版本,从而实现了事务的隔离性,也就是快照读。
-
写操作: 当事务执行写操作时,系统会创建一个新的数据版本,将修改写入到新版本中,并为该版本分配一个新的时间戳。这个新版本会对正在执行的事务不可见,直到该事务提交。
-
并发操作: 不同事务的读写操作可以并发执行,因为它们访问的是不同版本的数据。这样,多个事务可以同时访问相同的数据行,而不会产生锁等待。
-
数据的保留: 系统通常会保留一定时间内的历史版本,以便其他事务可以读取旧版本的数据。一段时间后,旧版本的数据可以被清理,以释放存储空间。
MVCC的好处在于它提供了高度的并发性能和隔离性,减少了锁的冲突和等待,从而提高了数据库的吞吐量和响应速度。不过,MVCC也需要消耗额外的存储空间来存储历史版本的数据,而且需要在查询时进行版本的选择和判断,可能对查询性能产生一定的影响。
普通的select
语句是快照读,添加了锁(共享锁、排他锁)的select
是当前读
通用查询日志
MySQL中的通用查询日志(General Query Log)是一种日志记录机制,用于记录所有客户端连接到MySQL服务器并执行的SQL查询语句。通用查询日志记录的是完整的SQL语句,包括读取操作和写入操作,以及系统内部的一些操作。这对于调试、性能分析和安全审计非常有用。
以下是通用查询日志的一些关键特点和作用:
-
记录查询操作: 通用查询日志会记录所有与MySQL服务器建立的连接,并记录每个连接执行的SQL查询语句。这包括
SELECT
、INSERT
、UPDATE
、DELETE
等各种SQL语句。 -
调试和排错: 通用查询日志对于调试和排错非常有用。开发人员可以通过查看日志来分析查询语句的执行情况、性能瓶颈以及潜在的问题。
-
性能分析: 通过分析通用查询日志,可以了解数据库中的查询模式、热点查询,以及执行频率较高的操作。这有助于优化查询性能。
-
安全审计: 通用查询日志可以用于安全审计,跟踪哪些用户在何时执行了哪些查询。这对于满足安全合规性要求非常重要。
-
开销: 启用通用查询日志会产生额外的I/O开销和日志文件大小。因此,在生产环境中启用时需要谨慎考虑,以免对数据库性能产生不利影响。
-
格式和使用方式: 通用查询日志可以配置为输出到文件,也可以输出到系统日志(例如系统日志守护进程)。你可以通过MySQL的配置文件或动态修改配置参数来启用或禁用通用查询日志。
set global general_log = on;
show variables like '%general_log%';
Binlog日志
即二进制日志,binlog
(Binary Log)是MySQL数据库中的一种日志文件,用于记录数据库中发生的更改操作,包括对数据的插入、更新和删除操作。binlog
的主要作用是用于数据备份、数据恢复、主从复制以及数据库的高可用性配置。
以下是binlog
的一些关键特点和作用:
-
记录数据更改操作:
binlog
以二进制格式记录数据库中的更改操作,包括对数据的插入、更新、删除等操作,没有查询操作。 -
用于数据备份和恢复: 数据库管理员可以使用
binlog
来进行数据备份和恢复操作。通过重放binlog
中记录的操作,可以将数据库恢复到指定时间点的状态。 -
主从复制:
binlog
在主从复制中扮演重要角色。主数据库将更改操作记录到binlog
中,从数据库可以读取并应用binlog
中的操作,从而保持与主数据库的同步。 -
数据库的高可用性配置: 在数据库高可用性配置中,主从架构和读写分离的实现都依赖于
binlog
。主数据库记录更改操作到binlog
,从数据库或只读节点通过读取并应用binlog
来保持与主数据库的同步。 -
格式和使用方式:
binlog
有不同的格式,包括STATEMENT
、ROW
和MIXED
等。不同的格式记录更改操作的方式不同,适用于不同的场景。binlog
可以通过MySQL的命令行工具或API来查询和管理。 -
存储和清理:
binlog
文件会持续增长,因此需要定期进行清理。MySQL提供了参数配置来控制binlog
文件的生成和清理策略。
set global general_log = on;
show variables like '%bin%';
查看:
show binary logs;
查看日志内容:
# 显示原始格式
mysqlbinlog ./binlog文件
# 显示伪sql
mysqlbinlog -v ./binlog文件
查看事件:
show binlog events in 'DEVICE-bin.000067'
show binlog events in 'DEVICE-bin.000067' from 100
show binlog events in 'DEVICE-bin.000067' from 100 limit 10, 2
生成新的binlog
:
flush logs;
Q.E.D.