1. 基本概念
事务:一组逻辑单元(增删改操作),使数据从一种状态切换到另一种状态。 事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交,这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚到最初状态。
2. 事务的ACID特性
- 原子性(atomicity): 原子性是指事务是一个不可分割的单位,要么全部提交,要么全部失败进行会滚。比如转账情况,要么转账成功,要么转账失败,不存在中间状态。如果原子性无法保证,则会出现数据不一致的情况,当A账户减去100元时,B账户增加100元操作失败,系统将无故丢失100元;
- 一致性 一致性是指事务执行前后,数据从一个合法状态变换到另外一个合法状态。此种状态是语义上的而不是语法上的,跟具体业务相关。满足预定的约束的状态叫做合法状态,通来讲,该状态是由你自己来定义的(比如满足现实世界中的约束)。当满足这个状态时,数据就是一致的,不满足这个状态,数据就是不一致的!如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回事务操作之前的状态。 举例1:A账户有200元,转账300元,此时A账户余额为-100元,此时数据是不一致的,因为规定了一个状态,余额必须>=0。 举例2:A账户200元,转账59元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加,此时数据就是不一致的,因为定义了一个状态,要求A+B的总余额必须不变。 举例3:在数据表中将姓名设置为唯一性约束,此时事务进行提交或者事务发生回滚的时候,如果数据表中的姓名不唯一,就破坏了事务的一致性要求。
- 隔离型
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。比如A账户有200元,B账户0元。A账户往B账户转账两次,每次金额为50元,分别在两个事务中执行。则有以下情况产生:
- 持久性 持久性是指一个事务一旦被提交,它对数据库中的数据修改就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。 持久性是通过事务日志来保证的。日志包括重做日志和回滚日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中的对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。
总结:ACID是事务的四大特性,原子性是基础,隔离性是手段,一致性是约束条件,持久性是目的。数据库事务,其实就是数据库设计者为了方便起见,把需要原子性、隔离性、一致性和持久性的一个或多个数据库操作称为一个事务。
3. 事务隔离级别
Mysl数据库是C/S架构的软件,对于同一个Server而言,可以有若干个Client与之连接,便可产生多个会话。每个Client都可以在自己的会话中向Server发起请求语句,一个请求语句可能是某个事务的一部分,对于Server而言可能需要同时处理多个事务。事务具有隔离性的特性,理论上在某个事务对数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对性能而言影响太大,当我们既想保持事务的隔离性,又想让服务器在处理访问统一数据的多个事务时性能尽量高些,就应在二者之间进行取舍。 (1)数据并发问题
- 脏写 对于两个事务SessionA,SessionB,如果SessionA中修改了另一个未提交事务SessionB修改过的数据,那就意味着发生脏写。
SessionA和SessionB各开启一个事务,SessionB中的事务先将studentno列为1的name修改为“李四”,然后SessionA中的事务紧接着又把这条studentno列为1的name更新为“张三”。如果之后SessionB中的事务进行了回滚,那么SessionA中的更新也将不复存在,此种现象便称为脏写。SessionA中的事务没有达到应有的效果,明明把数据更新了,最后提交了事务,但是数据没有发生变化。
- 脏读
对于两个事务SessionA、SessionB,SessionA读取了已经被SessionB更新但还没有被提交的字段。之后若SessionB回滚,SessionA读取的内容就是临时且无效的。
SessionA和SessionB各开启一个事务,SessionB中的事务先将studentno列为1的记录的name列更新为“张三”,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name值为“张三”,而SessionB中的事务稍后进行回滚,那么SessionA的事务相当于读到了一个不存在的数据,这种现象称之为脏读。
- 不可重复读
对于两个事务SessionA、SessionB,SessionA读取了一个字段,然后SessionB更新了该字段。之后SessionA再次读取同一个字段,值就不同了。意味着发生了不可重复读。
当我们在SessionB中提交几个隐式事务(意味着语句结束事务就提交了),这些事务都修改了studentno列为1的记录的name的值,每次事务提交之后,如果SessionA中的事务都可以查到最新的值,这种现象被称之为不可重复读。
- 幻读
对于两个事务SessionA、SessionB,SessionA从一个表中读取了一个字段,然后SessionB在该表中插入了一些新的行。之后,如果SessionA再次读取同一个表,就会多出几行。那就意味着发生了幻读。
SessionA中的事务先根据条件studentno > 0条件查询student,得到了name的列为“张三”的记录;之后Session B中提交了一个隐式事务,该事务向表student中插入了一条新的记录;之后SessionA中的事务再根据相同的条件studentno > 0 查询表student,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象被称为幻读。新插入的那些记录称之为幻影记录。
注意:如果Session B中删除了一些符合studentno > 0的记录而不是插入的记录,那么SessionA之后再根据studentno > 0的条件读取的记录变少了,这种现象不属于幻读,幻读强调一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。此种现象对每一条记录都发生了不可重复读的现象。幻读强调读取到了之前读取没有获取到的记录。 (2) SQL中的四种隔离级别
脏写 > 脏读 > 不可重复读 > 幻读
舍弃一部分隔离性来换取一部分性能,隔离级别越低,并发问题发生的就越多。SQL标准中设立了4个隔离级别:
- READ UNCOMMITTED:读未提交,在该隔离级别下,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
- READ COMMITTED:读已提交,一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统默认的隔离级别(但不是Mysql默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
- REPEATABLE READ:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读、但幻读问题仍然存在。MySQL默认隔离级别
- SERIALIZABLE:可串行化,确保事务可以从一个表中读取到相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。
SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
由于脏写问题太严重了,无论哪种隔离级别,都不允许脏写的情况发生。 不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4种事务隔离级别与并发性能的关系如下: