Mysql 的事务是什么,为什么它要支持事务,它的事务又是怎么实现;
事务是MySQL等其他关系型数据库中的一个重要概念。事务是一系列原子性的SQL查询,这些查询要么全部执行,要么全部不执行。事务特别适合处理财务信息或其他需要保持一致性的重要数据。
需要保证 数据一致性:事务可以保证一系列操作作为整体被处理,要么全部成功,要么全部失败,当发生错误时,可以将数据回滚到事务开始时的状态,保证了数据的正确性和一臀性。
需要考虑 并发控制:在多用户并发访问数据库时,事务可以提供必要的锁机制,防止用户之间数据访问发生冲突。
需要支持 错误恢复:当系统发生故障时,只需要回滚还未提交的事务,而不需要重新运行整个程序。
mysql 不仅支持了事务,而且还提供了事务的多种隔离级别可以供用户选择,隔离级别越高,能防止的并发问题越多,但相应的性能损耗也越大,因此需要根据实际情况进行选择:
READ UNCOMMITTED(RU读未提交): 最低级别的隔离,此级别可以读取到其他事务未提交的更改(即“脏读”),并且可能会产生幻读和不可重复读。
READ COMMITTED(RC 读已提交): 此隔离级别只允许读取其他事务已经提交的更改,避免了脏读的问题但可能会产生幻读和不可重复读。
REPEATABLE READ(RR 可重复读): 此级别在事务开始后,不再允许其他事务更改已经被读取的数据,可以避免不可重复读的问题。 MySQL 的 InnoDB 存储引擎的默认隔离级别就是 REPEATABLE READ,但由于在这个级别下 InnoDB 实施了额外的锁策,所以在对行进行“范围查询”的情况下,可以避免掉大部分的幻读。
SERIALIZABLE(串行化): 最高级别的隔离,此级别强制事务串行执行,牺牲了并发性能,但可以解决所有的隔离级别问题,包括幻读。
因为在2674 中未提交的修改,会被保存到buffer bool 脏页中,等待事务提交后的刷脏,所以此种读到了脏页的数据称之为 脏读;
在事务2673 中两次读取的数据不一致,不能重复读取,不可重复读;
MySQL的InnoDB存储引擎中,MVCC(MultiVersion Concurrency Control)多版本并发控制和锁机制一起协作实现各种事务隔离级别。
生成一个数据请求时间点的一致性数据快照 (Snapshot),并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取 (MVCC) MultiVersion Concurrency Control.
核心思想:建立了一个快照,同一个事务无论查多少次都是相同的数据;
InnoDB 引擎中,MVCC 借助于Read View实现,ReadView是InnoDB通过MVCC实现数据可见性的一个视图概念,表示在某个时间点上,对于同样的数据,由于并发事务的存在,不同的事务看到的可能是不同版本的数据,ReadView就是用来确定当前事务应该看到哪个版本的数据。
RR 的 Read View 是事务第一次查询的时候建立的,后续Read View不会被修改,所以不存在不可重复读和幻读的情况。
RC的 Read View 是事务每次查询的时候建立的,所以每次读取都重新按照读取的规则建立Read View 会出现不可重复读和幻读。
数据库的锁机制是一种保证同时多个用户并发读写数据时数据一致性和完整性的方法。根据其锁定的内容粒度不同,MySQL锁可以分为全局锁、表级锁、行级锁等。在MySQL数据库中,锁的主要作用包括:
保证事务的原子性:一个事务中包含的所有操作,要么全部成功,要么全部失败,保持数据一致性。
隔离性:多个用户并发地存取数据时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
防止数据不一致性利用锁机制,可以防止一个事务在读取一个数据行时,另一个事务却对此行进行修改,导致数据不一致。
防止更新丢失:当多个并发事务试图同时修改数据时,锁可以确保每次只有一个事务能够进行操作。例如,两个事务同时读取一个账户余额,并且都在其基础上减去10,如果没有排他锁,最终账户余额可能只会减去10。有了排他锁,另一个事务会等待,直到第一个完成,这样,账户余额将减去20,这才是正确的结果。
提高并发性能:适当的锁策略可以允许多个用户对数据库的同一部分进行操作,从而提高整体性能。
在MySQL数据库操作中,应用适当的锁策略,可以大大提高操作效率,保证数据的正确性,防止数据冲突。
行锁:行锁是MySQL InnoDB表类型提供的。当访问到一行记录时,就将这行记录锁定。这是最高的粒度,但是开销最大。如果启用了行锁,只有当前事务提交后,其他事务才能操作这行数据。
表锁:表锁在表级别上锁定整个表,一个事务如果获得了对一个表的锁,其他事务就不能对这个表进行操作。MyISAM存储引擎使用的就是表级锁。表锁的优势是开销小,加锁快;缺点是并发度最低,冲突最多,尤其是在写操作多的情况下,大部分读操作也会被堵塞。
间隙锁:InnoDB引擎中的一种锁,用于解决幻读问题。一个范围上添加一个锁,让这块范围内无法插入新的行。
共享锁(S锁):多个读操作之间不会互相阻塞,所以读操作会采取共享锁,允许一个事务进行读取操作,但是在该事务持有共享锁期间,阻止其他事务进行写入操作。
加锁释锁方式:
select * from student where id=1 LOCK IN SHARE MODE;
commit/rollback;
-- 自动,默认加上X锁::
delete /update /insert
-- 手动:
select * from student where id=1 FOR UPDATE;
commit/rollback;
Mysql的锁是通过索引来实现的,MySQL 的锁和索引之间存在密切的关联关系。
首先,行级锁的实现是基于索引的。也就是说,如果在执行查询语句时没有使用索引,那么MySQL会进行表级锁定。相反,如果使用了索引,MySQL将使用行级锁。这是因为MySQL只有通过索引才能定位到数据行,之后对这行数据加锁,完成数据的读取或者修改。
例如,对于一个UPDATE语句,如果条件中包含的列有索引,那么MySQL将使用行级锁。如果条件中的列没有索引,那么MySQL将用表级锁。这也就是为什么我们在实际应用中,经常要注意创建合适的索引,以提高查询性能的原因。
另外,在InnoDB存储引擎中,实际上行级锁分为两种类型:记录锁(Record Locks)和间隙锁(Gap Locks),它们的作用范围都和索引有关。
记录锁是单个行记录上的锁,含有索引的列可以很精确的选择哪一行,并对其加锁。
间隙锁是在索引的记录之间的锁,用来防止幻读(Phantom Read)现象。这个锁,锁定的并不是索引的记录,而是记录之间的“间隙”。
所以,从行级锁的设计和使用来看,它和索引的关系非常密切。为了更有效率地使用行级锁,我们必须对索引有深入的理解,合理的创建和使用索引。
MySQL的InnoDB存储引擎中的事务读操作可以分为两种,当前读(Current Read)和快照读(Consistent Read)。
当前读(Current Read):又称之为"锁定读",在进行读取的时候会加锁,并且读取的是最新的数据。INSERT、UPDATE、DELETE、LOCK IN SHARE MODE 和 SELECT FOR UPDATE 这些语句都属于当前读。当前读会读取最新的数据,适用于需要读取最新(current)数据版本的场合,例如更新操作,需要先查看记录的当前版本。
快照读(Consistent Read):又称之为一致性读,它读取的是行的快照版本,并不需要加锁,普通的SELECT操作就是快照读。快照读通过多版本并发控制(MVCC)获取数据在某一时刻的版本,而不是最新版本。这种方式尽可能地提高了数据的并发读取,保证在读取过程中不会被其他写入操作阻塞,确保了读的一致性。
简单来说,如果你的读取操作需要对数据进行更新,应当使用当前读;而如果你只是需要读取数据,而不进行修改,那就应该使用快照读。
既然有了MVCC 进行数据普通读取时 基于RR 隔离模式已经解决了不可重复读和幻读的问题,为什么还要用到锁,锁对MVCC 作了哪些补充呢:
在数据修改(如UPDATE, DELETE, INSERT等)时,MVCC是无法防止数据竞态条件的,这就需要使用锁来解决,以确保数据的一致性和完整性;
在某些场景下,我们可能需要更严格的数据控制。例如,一个线程读取了一条记录并对其进行了修改,我们需要防止在这个线程完成修改之前,其他线程读取或修改这条记录。这就需要显式地进行行级锁定;
在更高的隔离级别(如Serializable)下,仅仅依靠MVCC是无法满足隔离要求的,必须依靠更严格的锁策略;
在需要控制对整个表进行操作权限时,也需要用到表级锁。比如,一种策略是在进行对表的大批量写入操作时,先对整个表加锁,避免写入过程中读取到不完整的数据;
在一个事务中,如果使用普通的的select 读取,会使用到mvcc 来保证数据的一致性问题,但是如果使用LOCK IN SHARE MODE 和 SELECT FOR UPDATE 进行数据读取,这个时候需要使用到锁来保证改隔离级别数据的一致性问题;
总的来说,尽管MVCC可以解决一部分并发控制的问题,但是在必要的情况下,仍然需要数据库锁的参与,以保证数据的一致性和完整性;
在 RC 隔离级别下,既然当前读已经对读取到的数据进行了加锁,在事务未提交之前,其他的事务无法对改数据进行修改,那么为什么还会出现不可重复读和幻读?
不可重复读主要是由于在同一个事务中,多次读取同一数据时,因其他事务的提交,使得同一数据的内容发生了改变。在RC(Read Committed)隔离级别下,每次读取都是读取的最新已提交的数据,所以就可能出现不可重复读的问题。
以这样的例子来说明,假如有两个事务,事务1和事务2。
在这个过程中,虽然事务1每次读取数据时,由于读取的是当前版本(已提交的数据),会对该数据加锁,防止数据在读取过程中被修改。但是,这个锁是在读取期间存在的,读取结束后锁就被释放了。这就有可能出现在事务1第一次读取和第二次读取之间,事务2修改了这个数据并提交,所以事务1两次读取的数据就可能不一样,也就是出现了不可重复读和幻读的情况。
在RR(Repeatable Read)隔离级别下,当前读(例如 SELECT … FOR UPDATE, UPDATE, DELETE等)会在读取数据时,对数据行加上排他锁,防止其他事务对该行进行并发修改。
这个锁不会在读取结束后立即释放,而是在整个事务结束后才会释放。也就是说,只有当整个事务提交(commit)或者回滚(rollback)后,这个锁才会被释放。这样可以确保在一个事务中,一个数据行全程不会被其他事务改变,从而保证了可重复读的性质,因为在RR隔离级别下,会有间隙锁锁的存在,进行数据范围的锁定,这样也解决了幻读。
如果是普通读(不带FOR UPDATE的SELECT等),则会读数据在事务开始时的快照,而不是最新版本的数据。这也是RR级别下可以避免不可重复读和幻读的原因。
Mysql 的事务与锁本质上都是用来解决读到的数不一致性的问题: