MySQL事务

事务概述

当多个用户访问同一份数据,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,
为保证数据库记录的更新从一个一致性状态变更为另外一个一致性状态,使用事务处理是非常必要的,事务具有以下4个特性

  • 原子性(Atomicity):事务中所有的操作操作视为一个原子单元,即对于事务所进行的数据修改等操作只能是完全提交或者完全回滚
  • 一致性(Consistency):事务在完成时,必须使所有的数据从一种一致性状态变更为另外一种一致性状态,所有的变更度必须应用于事务的修改,以确保数据的完整性
  • 隔离性(Isolation):一个事务中的操作语句所做的修改必须与其他事务所做的修改相隔离。在进行事务查看数据时数据所处的状态,要么是被另一并发事务修改之前的状态,要么是被另一并发事务修改之后的状态,即当前事务不会查看由另一个并发事务正在修改的数据。这种特性通过锁机制实现。
  • 持久性(Durability):事务完成之后,所做的修改对数据的影响是永久的,就是系统重启或者出现系统故障数据仍可以恢复。

为支持事务,InnoDB存储引擎引入了与事务处理相关的UNDO日志和REDO日志,同时事务依赖于MySQL提供的锁机制。

REDO日志

事务执行时需要将执行的事务日志写入到日志文件里,对应的文件为REDO日志。当每条SQL进行数据库更新操作时,首先将REDO日志写入到日志缓冲区。当客户端执行COMMIT命令提交时,日志缓冲区的内容将被刷到磁盘,日志缓冲区的刷新方式或者时间间隔可以通过参数innodb_flush_log_at_trx_commit控制。

REDO日志对应磁盘上的ib_logfileN文件,该文件默认为5MB,建议设置为512MB以便容纳较大的事务。在MySQL崩溃恢复时会重新执行REDO日志中的记录。

在这里插入图片描述

UNDO日志

UNDO日志主要用于事务异常时的数据回滚,具体内容就是复制事务前的数据库内容到UNDO缓冲区,然后再合适的时间将内容刷新到磁盘。
磁盘上不存在单独的UNDO日志文件,所有的UNDO日志存放在表空间对应的.ibd数据文件中。UNDO日志又被称为回滚段。

在这里插入图片描述

事务控制语句

MySQL中可以使用BEGIN开始事务,使用COMMIT结束事务,中间可以使用ROLLBACK回滚事务。语法如下

START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO]  CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO]  RELEASE]
SET AUTOCOMMIT = {0 | 1}

查看MySQL隔离级别

SHOW VARIABLES LIKE 'tx_isolation'

MySQL事务_第1张图片

事务隔离级别

MySQL定义了4中隔离级别。低级别的隔离级别可以支持更高的并发处理,同时占用的系统资源更少。

#未提交读
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

#提交读
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

#可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

#可串行化
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

READ UNCOMMITTED(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。因为其性能也不比其他级别高很多,因此此隔离级别实际应用中一般很少使用,读取未提交的数据被称为脏读(Dirty Read)。
MySQL事务_第2张图片
在一个事务中做了数据更新
MySQL事务_第3张图片

在另一个新的事务中,读到了未提交的数据
MySQL事务_第4张图片
在这个事务中使用ROLLBACK,并查询该数据未更新
MySQL事务_第5张图片
新的事务也是未更新的数据
MySQL事务_第6张图片
READ UNCOMMITTED就是A事务更新了但是未提交,但是B事务可以读取到A事务的数据。

READ COMMITTED(读取提交内容)

其满足了隔离的简单定义:一个事务从开始到提交钱所做的任何改变都是不可见的,事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理期间可能会有新的数据提交导致数据改变,所以同一查询可能返回不同结果。

事务进行数据更新但是未提交
MySQL事务_第7张图片
新事务还是未能读取到修改的数据
MySQL事务_第8张图片

事务提交
MySQL事务_第9张图片

事务提交后,就能在另外一个事务读取到新数据了
MySQL事务_第10张图片
READ COMMITTED的隔离级别,首先开启A和B两事务,在B事务更新并提交后,A事务读取到了更新后的数据,此时处于同一A事务的中查询出现了不同的查询结果,即不可重复读现象。

MySQL的不可重复读现象(Non-repeatable read)是指在同一个事务中,多次读取同一行数据时,得到的结果可能不一致的情况。

不可重复读的现象可以通过以下示例来说明:

  • 开启一个事务,并读取一行数据:SELECT * FROM table WHERE id = 1; 得到结果 A。
  • 在同一个事务中,另外一个操作修改了这一行数据:UPDATE table SET column = ‘new value’ WHERE id = 1;
  • 在同一个事务中,再次读取这一行数据:SELECT * FROM table WHERE id = 1; 得到结果 B,与之前读取到的结果 A 不一致。

造成不可重复读的原因是,在读取数据时事务并不会对数据进行加锁,所以其他事务可以在同一时间修改数据。这导致在同一事务中,多次读取同一行数据时,得到的结果可能不一致。

为了解决不可重复读的问题,可以使用以下方法之一:

  • 通过锁机制,在读取数据时对数据进行加锁,确保其他事务不能修改数据,从而保证读取一致性。
  • 使用数据库的事务隔离级别。MySQL提供了多个事务隔离级别,例如读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。不同的事务隔离级别会有不同的读取一致性保证机制。

REPEATABLE READ(可重读)

MySQL的默认事务隔离界别,确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。理论上会导致另外一个问题:幻读(Phantom Read)。

MySQL的幻读是指在一个事务中,两次查询同一个范围的数据时,第二次查询获得了新的数据行,导致第一次查询的结果不一致的现象。

幻读的产生是因为MySQL的默认事务隔离级别是可重复读(REPEATABLE READ),该隔离级别使用了MVCC(多版本并发控制)来保证并发事务的一致性,但是无法完全解决幻读问题。

幻读是在可重复读隔离级别下,一个事务在相同的查询条件下,两次查询结果不一致的情况。这是因为在可重复读隔离级别下,事务在读取数据时会使用锁来保证一致性,但是对于新插入的数据行,锁的范围无法确定,因此在第一次查询之后,可能有新的数据行插入,导致第二次查询时结果不一致。

解决幻读的方式有两种:

    1. 将事务隔离级别调整为串行化(SERIALIZABLE),这样可以完全避免幻读的发生。但是串行化会大大降低并发性能,因为它会对所有读取和写入操作进行锁定,只允许一个事务访问数据库。
    1. 使用锁定(LOCK)语句来显式地锁定需要操作的数据行,从而避免幻读的发生。但是使用锁定语句需要谨慎,过多或不恰当的使用锁定可能会导致死锁和性能问题。

总结来说,幻读是在可重复读隔离级别下,一个事务在相同的查询条件下,两次查询结果不一致的现象。要避免幻读可以将隔离级别调整为串行化或者使用锁定语句来显式地锁定数据行。

Serializable(可串行化)

这是最高的隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简而言之,是在每个读的数据行上加上共享锁实现。在这个级别,可能会导致大量的超时现象和锁竞争,一般不推荐使用。

注意

  • 脏读(Drity Read),某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个ROLLBACK了操作,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read),在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据
  • 幻读(Phantom Read),在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另外一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

InnoDB锁机制

为解决数据库并发控制问题,如在同一时刻,客户端对于同一个表做更新或者查询操作,为保证数据的一致性,需要对并发操作进行控制,因此产生了锁,同时为实现MySQL的各个隔离级别,锁机制为其提供了保证。

锁类型

共享锁

MySQL的共享锁(Shared Lock)是一种读取锁。当一个事务获取到共享锁后,其他事务也可以获取到共享锁,允许多个事务同时读取同一份数据,但不允许有事务对数据进行修改。

SELECT * FROM table_name LOCK IN SHARE MODE;

获取共享锁后,其他事务也可以使用相同的SELECT语句获取共享锁。共享锁不会阻塞其他事务获取共享锁,但会阻塞其他事务获取排他锁。

共享锁不会阻塞其他事务的读操作,但会阻塞其他事务的写操作。因此,共享锁适用于并发读取数据的场景,可以提高系统的读取性能。

共享锁和排他锁(Exclusive Lock)是互斥的,即同一份数据不能同时有事务获取共享锁和排他锁。如果一个事务已经获取了共享锁,其他事务想要获取排他锁时,必须等待当前事务释放共享锁。

在MySQL中,共享锁使用完后,可以通过COMMIT或ROLLBACK语句来释放锁。如果使用共享锁的事务没有提交或回滚,锁将一直保持,其他事务将无法获取到排他锁。

需要注意的是,共享锁只能保证其他事务不会修改数据,但不能保证其他事务不会读取已经加锁的数据。如果需要保证数据的一致性,可以使用排他锁。

排他锁

排他锁可以防止其他事务同时对数据进行写操作,从而确保数据的一致性和完整性。

使用排他锁时,事务需要先获取锁才能对数据进行写操作。其他事务在该数据上获取排他锁时会被阻塞,直到当前事务释放锁为止。这样可以防止并发写入数据,避免数据的修改冲突。

在MySQL中,可以使用以下语句来获取排他锁:

LOCK TABLES table_name WRITE;

这将会锁定指定的表,其他事务将无法对该表进行写操作,直到当前事务释放锁。

另外,可以使用以下语句来释放锁:

UNLOCK TABLES;

需要注意的是,使用排他锁需要谨慎使用,过多的排他锁可能会导致性能问题。因此,在使用排他锁时,应该尽量缩小锁的范围,只针对必要的数据进行加锁。

如有两个事务A和B,如果事务A获取了一个元组的共享锁,事务B还可以立即获取这个元祖的共享锁,但不能立即获取这个元素的排他锁,必须等到事务A释放共享锁之后。

如果事务A获取了一个元组排他锁,事务B不能立即获取这个元组的共享锁,也不能理解获取这个元祖的排他锁,必须等到A释放排他锁之后。

意向锁

MySQL的意向锁(Intention Lock)是一种数据库锁机制,用于在并发事务执行过程中管理资源锁定。意向锁是一种表级锁,用于表示一个事务意图在某个表或页上进行锁定操作,而不是真正的锁定该表或页。

意向锁有两种类型:意向共享锁(Intention Shared Lock,IS锁)和意向排他锁(Intention Exclusive Lock,IX锁)。IS锁表示一个事务意图在某个表或页上进行共享锁定操作,IX锁表示一个事务意图在某个表或页上进行排他锁定操作。

意向锁的作用是为了提高并发性能和减少锁冲突。当一个事务要锁定某个表或页时,需要先获取该表或页的意向锁,表示该事务有意向对该表或页进行锁定。其他事务在锁定该表或页之前,会先查询意向锁的状态,根据意向锁的状态来判断是否允许进行锁定。如果一个表或页已经被获取了意向锁,其他事务只能等待或进行相应的等待操作。

意向锁的使用可以有效避免事务之间的锁冲突,提高并发性能。但同时也需要注意,在并发事务执行过程中,意向锁的管理需要消耗一定的系统资源,因此需要根据实际情况合理使用意向锁,避免资源的浪费。

锁粒度

锁粒度主要分为表锁和行锁。

表锁管理锁的开销最小,同时允许的并发量也是最小的锁机制。MyISAM存储引擎使用该锁机制。当要写入数据时,把整个表记录被锁,此时其他读、写动作一律等待。同时一些特定的动作,如ALTER TABLE执行时使用的也是表锁。

行锁可以支持最大的并发。InnoDB存储引擎使用该锁机制。如果要并发读/写,建议采用InnoDB存储引擎。

以下是MySQL中一些语句执行时锁的情况:

select ... lock in share mode

此操作会加上一个共享锁。若会话事务中查找的数据已经被其他会话事务加上排他锁的话,共享锁会等待其结束再加,若等待时间过长就会显示事务需要的锁等待超时。

select ... for update

此操作加上一个排他锁,其他会话事务将无法再加其他锁,必须等待其结束。

insert 、update 、delete

会话事务会对DML语句操作的数据加上一个排他锁,其他会话的事务都将会等待其释放排他锁。

InnoDB引擎会自动给会话事务中的共享锁、更新锁以及排他锁,需要加到一个区间值域时,再加上个间隙锁或称为范围锁,对不存在的数据也锁住,防止出现幻写。

你可能感兴趣的:(mysql,数据库)