Java知识点总结:想看的可以从这里进入
事务(Transaction)指作为一个逻辑工作单元执行一系列操作,这些操作要么全部成功,要么全部失败,确保对多个数据的操作当成一个单元来处理。
比如我们在银行转账,A给B转了1000,成功则A账户减少1000,B账户增加1000,在转的过程中如果失败,那么A和B的账户余额都不会改变,这就是事务成功。假设没有事务,如果A转账失败了,但是A的账户余额少了,而B却没有收到钱,那么就会乱套。
我们在处理SQL时,会根据情况将同一组的SQL语句放到一个事务中去处理,这样只要有一个语句执行失败,整组的SQL语句就全部失败。
事务的原则(ACID):
1、原子性(Atomicity):事务必须是原子工作单位,事务中的操作要么都要做,要么都不做。
2、一致性(Consistency):事务在执行完成时,必须使所有数据保持一致状态。(转账,双方的钱总数在转账前后是一致的)
3、隔离性(Isolation):事务独立运行,多事务间互相隔离,互不干扰。100%的隔离,会牺牲一部分的速度
4、持久性(Durability):事务执行完成后,对系统的影响是持久的,不会随外界原因导致数据丢失。
以下几个概念是事务隔离级别要实际解决的问题
1、脏读:一个事务读取了另外一个事务尚未提交的数据(事务A更新了数据,但还没有提交,这时事务B读取到事务A更新后的数据,然后事务A回滚并没有保存到数据库中,这时候事务B读取到的数据就成为脏数据了)2、不可重复读:一个事务在读取数据时,多次读取的结果不同(事务A对数据进行多次读取,事务B在事务A多次读取的过程中执行了更新操作并提交了,导致事务A多次读取到的数据并不一致)(update操作)3、幻读:幻读是针对数据delete、insert操作来说的。一个事务使用sql两次读取数据,第二次读取了新插入的数据(事务A在读取数据后,事务B向事务A读取的数据中插入了几条数据,事务A再次读取数据时发现多了几条数据,和之前读取的数据不一致)事务隔离级别:从上往下,隔离强度逐渐增强,性能逐渐变差。
1、读到未提交的数据(READ UNCOMMITTED):所有事务都能看到其他事务未提交的执行结果。不能解决脏读、不可重复读、幻读(基本不使用)
2、读到提交内容(READ COMMITTED):一个事务提交之后,它的修改才能被其他事务看到。可解决脏读,不能解决不可重复读、幻读
3、可重复读(REPEATABLE READ)( MySQL 的默认级别。):同一个事物的多个实例在并发读取时,会获得相同的数据。能解决脏读、不可重复读,不能解决幻读
4、串行化(SERIALIZABLE):需要加锁实现,会强制事务串行执行。能解决脏读、不可重复读、幻读。(但会导致大量的超时现象和锁竞争)
MySQL使用的事务隔离级别为可重复度,使用了以乐观锁为理论基础的MVCC 实现的,即 Mutil-Version Concurrency Control(多版本并发控制)。
InnoDB 在每行记录后面保存两个隐藏的列,分别保存了数据行的创建版本号(记录这行数据何时被创建)和删除版本号(记录这行数据何时过期)。实际保存的就是是事务的版本号,每开启一个新事务,事务的版本号就会递增。
在A事务开始时,系统会从操作数据中读取一个版本号,这个版本就作为A事务的版本号,A事务会读取版本号<=当前版本号的数据。如果一个B事务修改了这条数据,那么这条数据的版本号会+1,此时A事务再读取数据时,读取的依然是修改前的数据。
MVCC对增删改查的规定:
比如:插入一条数据,假设开始的版本号为1
![]()
修改数据
![]()
对这个数据进行修改
![]()
对数据进行删除
![]()
MVCC这种读取出来的数据其实是历史数据,而不是最新数据,我们称为快照读(snapshot read)。而读取数据库当前版本数据的方式,叫当前读(current read)。我们平时只用使用select就是快照读,这样可以减少加锁所带来的开销。对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式。
我们知道可重复读是无法解决幻读这个问题的,所以InnoDB 在MVCC机制上增加了版本链来解决幻读的问题。
InnoDB在每行数据后增加三个隐藏字段,
每次数据修改,旧数据会保存在 undo日志中,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表称为版本链,头节点就是最新的值。
同时InnoDB存在一个ReadView的概念,它帮助我们识别那些版本是当前事务可见的。每开启一个事务时,系统会给该事务会分配一个事务 Id,在事务进行select时,会生成当前时间点的事务快照 ReadView,它主要包含一下属性:
在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
在进行判断时,首先会拿记录的最新版本来比较,如果这个版本数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本,如果最后一个版本也不可见的话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。
删除并不是真的删除,可以把它当成一种特殊的更新,InnoDB 用一个额外的标记位 delete_bit 标识是否删除。当我们在进行判断时,会检查下 delete_bit 是否被标记,如果是,则跳过该版本,通过 DB_ROLL_PTR 拿到下一个版本进行判断。
(ReadView和版本链解决了快照读(普通 select 语句就是快照读)的幻读问题,对于当前读(读取数据的最新版本)的幻读,MySQL在 RR 级别下是默认开启Gap 锁,通过Gap锁可以防止幻读)。
数据库管理系统DBMS中并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性。主要手段是乐观控制和悲观控制
乐观锁:持乐观的态度,认为数据变动不会太频繁,因此允许多个事务同时对数据进行变动,仅在提交时检查是否违反数据的完整性。在修改数据时通过version的方式进行锁定。更新时带上版本号(cas更新)
悲观锁:持悲观的态度,认为数据容易发生冲突,所以屏蔽一切可能违反数据完整性的操作,在查询时就将其锁定,直到提交了事务(使用共享锁和排它锁)
MySQL是默认开启事务自动提交的。
-- 事务事务自动提交的开启和关闭
SET autocommit = 0 -- 关闭
SET autocommit = 1 -- 开启(默认的)
-- 事务流程
SET autocommit = 0 -- 设置自动提交关闭
START TRANSACTION -- 标记事务开启,以下的sql语句都在同一个事务内
SQL.....
SQL.....
...
COMMIT -- 提交成功,数据持久化
ROLLBACK -- 提交失败,数据回滚,回到原来的数据
SET autocommit = 1 -- 事务结束,开启自动提交-- 其中可以设置保存点,类似我们玩游戏时的存档功能
SAVEPOINT 名 -- 保存点
ROLLBACK TO SAVEPOINT 名-- 回滚到一个保存点
RELEASE SAVEPOINT 名 -- 撤销一个保存点