一、概念
1.1 简介
事务就是针对数据库的一组操作。由一条或者多条SQL语句组成,同一个事务的操作具备同步的特点,如果其中的一条语句无法执行,那么所有的语句都不会执行。
重点:
- 也就是说事务中的语句要不都执行,要不都不执行。
- 在MySQL中直接书写的SQL语句都是自动提交的。
ROLLBACK
语句,只能针对未提交的事务执行回滚操作。已提交的事务是不能回滚的。- 事务的4个特性:原子性、一致性、隔离性、持久性
1.2 事务的4个特性
1 原子性
-
是指一个事务必须被视为一个不可分割的最小工作单元,只有事务中所有的数据库操作都执行成功,才算整个事务都执行成功,事务中如果有任何一个SQL执行失败,已经执行成功的也必须要被撤销,数据库的状态要退回到执行事务前的状态。
-
⼀个事务的执⾏被视为⼀个不可分割的最⼩单元。事务⾥⾯的操作,要么全部成功执⾏,要么全部失败回滚,不可以只执⾏其中的⼀部分。
2 一致性
⼀个事务的执⾏不应该破坏数据库的完整性约束。如果上述例⼦中第2个操作执⾏后系统崩溃,保证A和B的⾦钱总计是不会变的。
3 隔离性
还可以被称为并发控制、可串行化、锁等。当多个用户访问数据库时,数据库为每一个用户开启的事务,不能被其它事务的操作数据所干扰,多个并发事务之间要相互隔离。
4 持久性
事务一旦提交,其所作的修改就会永久的保存在数据库中,即使数据库发生故障也不应该对其有任何影响。
事务提交之后,需要将提交的事务持久化到磁盘。即使系统崩溃,提交的数据也不应该丢失。
二、事务的提交、回滚
- 事务的操作是手动开启的,操完完成后,一定要提交事务。
- 没有提交事务
COMMIT
,重新连接后,数据库结果将回到开启事务前,也就是更新操作未保存。
开启事务
START TRANSACTION;
提交事务
COMMIT;
回滚事务:
- 在操作一个事务时,假如发现不合理可以,未提交之前
COMMIT
可以回滚事务ROLLBACK
。
ROLLBACK;
案例:
START TRANSACTION;
UPDATE account SET money = money - 100 WHERE name = 'zhangsan';
COMMIT;
三、事务的隔离级别
数据库是多线程并发访问的,很容易出现多个线程同时开启事务的情况,会出现脏读、重复读和幻读等,为避免这些情况,需要设置一定的事务隔离级别。
3.1 并发问题
1.脏读
脏读是指在⼀个事务处理过程⾥读取了另⼀个未提交的事务中的数据。
也就是不能读到前⼀个事务未完成,未commit的数据。只有commit后才能读到更新后的数据,没有commit,则还是之前的数据。
2. 不可重复读
线程1:在当前线程内,多次读,结果不⼀致(读到了另⼀事务update
的数据)
不可重复读是指在对于数据库中的某个数据,⼀个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另⼀个事务修改并提交了,主要是针对update。
3.幻读
在当前线程内,多次读,结果不⼀致(读到了另⼀事务insert
的数据)
例如事务T1对⼀个表中所有的⾏的某个数据项做了从“1”修改为“2”的操作,这时事务T2⼜对这个表中插入了⼀⾏数据项,⽽这个数据项的数值还是为“1”并且提交给数据库。⽽操作事务T1的⽤户如果再查看刚刚修改的数据,会发现还有⼀⾏没有修改,其实这⾏是从事务T2中添加的,就好像产⽣幻觉⼀样,这就是发⽣了幻读。幻读主要是针对insert与delete。
3.3 事务隔离级别
READ UNCOMMITTED(读未提交):
事务中的修改,即使没有提交,在其他事务也都是可⻅的。事务可以读取未提交的数据,这也被称为脏读。
READ COMMITTED(读已提交):
⼀个事务从开始直到提交之前,所做的任何修改对其他事务都是不可⻅的。这个级别有时候也叫做不可重复读,因为两次执⾏相同的查询,可能会得到不⼀样的结果。因为在这2次读之间可能有其他事务更改这个数据,每次读到的数据都是已经提交的。
REPEATABLE READ(可重复读):
解决了脏读,也保证了在同⼀个事务中多次读取同样记录的结果是⼀致的。但是理论上,可重读读隔离级别还是⽆法解决另外⼀个幻读的问题,指的是当某个事务在读取某个范围内的记录时,另外⼀个事务也在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产⽣幻⾏。
SERIALIZABLE(可串⾏化):
它通过强制事务串⾏执⾏,避免了前⾯说的幻读的问题。
3.3 查询/设置事务隔离级别
脏读:(脏读是指在⼀个事务处理过程⾥读取了另⼀个未提交的事务中的数据。)
查看事务隔离界别
select @@tx_isolation;
设置事务隔离级别
set session transation isolation level read uncommitted;
四、InnoBD 可重复读隔离级别下如何避免幻读
- 表象:快照读(非阻塞读)–伪MVCC
- 内在:next—key锁(行锁+gap锁)
当前读和快照读:
-
当前读:select…lock in share mode ,select … for update
-
当前读:update,delete,insert 加了锁的增删改查语句。
-
快照读:不加锁的非阻塞读,select
当前读:
当前读就是加了锁的增删改查语句,不管上的是共享锁还是排它锁上的都是当前读,因为它读取的是最新版本,读取后还保证其它并发事务不能读取当前记录。对读取的记录加锁,除了select lock… 会加共享锁之外,其它的操作加的都是排它锁。
update、delete、insert 也都是当前读,RDBMS主要由程序实例和存储组成,如图所示。程序实例在这里指的是mysqlServer的程序实例,存储就是InnoDB。
拿update来举个例子:
当update发送给mysql之后,mysqlServer会根据where读取第一条满足where的条件记录,InnoDB会将第一条数据返回并加锁。mysqlServer收到加锁的记录后会发起一个update操作,去更新这条记录,一条记录读取完成后再去读取下一条记录,直至没有满足条件的记录出现。
update操作就包括一个当前读来获取数据的最新版本,就如之前在已提交读的隔离级别下出现的幻读的情况一样,由于先前事务新提交了一个数据,当前事务update全表的时候就莫名其妙多了一条数据,即读取到了数据的最新版本,同理delete也一样,insert会稍有不同,简单来说insert会触发唯一键的检查,也会进行一个当前读。
快照读:
快照读与当前读不太一样,它就是简单的select操作,不加锁,是在隔离级别不在串行化的条件下实现的,在serializable下由于是串行读,所以快照读也退化成当前读的lock in share mode的模型。
之所以出现快照读是基于提升并发性能的考虑,快照读的实现是基于多半版并发控制即MVCC,可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此:开销更低,但是快照读读取的可能不是最新版本,是历史版本。
在Read committed(读已提交)情况下当前读和快照读读到的数据是一样的。
在Repeatable read(可重复读)的情况下
情况1:
session1、session2都开启事务,先在session1中读取账户余额发现是600,在session2中修改账户余额为300,再在session1中用当前读查看账户余额为300,用快照读查询账户余额还是600。图中第一条语句为快照读,第二条语句为当前读。这里快照读读到的就是历史版本。
情况2:
session1、ession2都开启事务,我们在session2中更新账户余额,在session1中当前读与快照读查询到的都是最新版本。
在RR级别下可以让我们看不到幻读,是因为采用了伪MVCC机制,关于伪MVCC机制更多的可以去看第五章,其实伪MVCC机制有一些掩耳盗铃的感觉,已经做了更改就是看不见,真正实现避免幻读的还是使用了间隙锁。