事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交( commit ),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改 ,整个事务回滚( rollback )到最初状态。
原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。即要么转账成功,要么转账失败,是不存在中间的状态。如果无法保证原子性会怎么样?就会出现数据不一致的情形,A账户减去100元,而B账户增加100元操作失败,系统将无故丢失100元。
根据定义,一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。
那什么是合法的数据状态呢?满足预定的约束的状态就叫做合法的状态。通俗一点,这状态是由你自己来定义的(比如满足现实世界中的约束)。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回到事务操作之前的状态。
举例1:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须>=0。
举例2:A账户有200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时的数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的总余额必须不变。
举例3:在数据表中我们将姓名字段设置为唯一性约束,这时当事务进行提交或者事务发生回滚的时候,如果数据表的姓名不唯一,就破坏了事务的一致性要求。
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库 故障不应该对其有任何影响。
持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志 。当我们通过事务对数据进行修改 的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执 行,从而使事务具有持久性。
事务是一个抽象的概念,它对应着一个或多个数据库操作,MySQL根据这些操作所执行的不同阶段把事务大致划分成几个状态:
活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
失败的(failed)
当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
中止的(aborted)
如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事 务处在了中止的状态。
提交的(committed)
如果当一个处在部分提交的状态的事务将修改过的数据都 同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
步骤1: START TRANSACTION 或者 BEGIN ,作用是显式开启一个事务。
mysql> BEGIN;
#或者
mysql> START TRANSACTION;
START TRANSACTION 语句相较于 BEGIN 特别之处在于,后边能跟随几个 修饰符 :
① READ ONLY :标识当前事务是一个 只读事务 ,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
补充:只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用 CREATE TMEPORARY TABLE 创建的表),由于它们只能再当前会话中可见,所有只读事务其实也是可以对临时表进行增、删、改操作的。
② READ WRITE :标识当前事务是一个 读写事务 ,也就是属于该事务的数据库操作既可以读取数据, 也可以修改数据。
③ WITH CONSISTENT SNAPSHOT :启动一致性读。
比如:
START TRANSACTION READ ONLY; # 开启一个只读事务
START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT # 开启只读事务和一致性读
START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT # 开启读写事务和一致性读
注意:
步骤2: 一系列事务中的操作(主要是DML,不含DDL)
步骤3: 提交事务 或 中止事务(即回滚事务)
# 提交事务。当提交事务后,对数据库的修改是永久性的。
mysql> COMMIT;
# 回滚事务。即撤销正在进行的所有没有提交的修改
mysql> ROLLBACK;
# 将事务回滚到某个保存点。
mysql> ROLLBACK TO [SAVEPOINT]
其中关于SAVEPOINT相关操作有:
# 在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。
SAVEPOINT 保存点名称;
# 删除某个保存点
RELEASE SAVEPOINT 保存点名称;
对于两个事务 Session A、Session B,如果事务Session A 修改了另一个未提交事务Session B 修改过的数据,那就意味着发生了脏写。
Session A 和 Session B 各开启了一个事务,Sesssion B 中的事务先将studentno列为1的记录的name列更新为’李四’,然后Session A中的事务接着又把这条studentno列为1的记录的name列更新为’张三’。如果之后Session B中的事务进行了回滚,那么Session A中的更新也将不复存在,这种现象称之为脏写。这时Session A中的事务就没有效果了,明明把数据更新了,最后也提交事务了,最后看到的数据什么变化也没有。
对于两个事务 Session A、Session B,Session A 读取了已经被 Session B更新但还没有被提交的字段。 之后若 Session B 回滚 ,Session A 读取 的内容就是临时且无效 的。
Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新 为’张三’,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为’张三’,而 Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为脏读 。
对于两个事务Session A、Session B,Session A 读取了一个字段,然后 Session B 更新了该字段。 之后 Session A 再次读取同一个字段, 值就不同了。那就意味着发生了不可重复读。
Session B中提交了几个隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为不可重复读 。
对于两个事务Session A、Session B, Session A 从一个表中读取了一个字段, 然后 Session B 在该表中插入了一些新的行。 之后, 如果 Session A 再次读取同一个表, 就会多出几行。那就意味着发生了幻读。
Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为’张三’的记录; 之后Session B中提交了一个隐式事务,该事务向表student中插入了一条新记录;之后Session A中的事务 再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为幻影记录。
上面介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题有轻重缓急之分,我们给这些问题按照严重性来排一下序:
脏写 > 脏读 > 不可重复读 > 幻读
我们愿意舍弃一部分隔离性来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,并发问题发生的就越多。 SQL标准 中设立了4个隔离级别:
注意:
不论是哪种隔离级别,都不允许脏写的情况发生。
不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差。
通过下面的语句修改事务的隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别;
#其中,隔离级别格式:
> READ UNCOMMITTED
> READ COMMITTED
> REPEATABLE READ
> SERIALIZABLE
或者:
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
#其中,隔离级别格式:
> READ-UNCOMMITTED
> READ-COMMITTED
> REPEATABLE-READ
> SERIALIZABLE
关于设置时使用GLOBAL或SESSION的影响:
使用 GLOBAL 关键字(在全局范围影响):
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
#或
SET GLOBAL TRANSACTION_ISOLATION = 'SERIALIZABLE';
则:
使用 SESSION 关键字(在会话范围影响):
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
#或
SET SESSION TRANSACTION_ISOLATION = 'SERIALIZABLE';
则:
如果在服务器启动时想改变事务的默认隔离级别,可以修改启动参数transaction_isolation的值。比如,在启动服务器时指定了transaction_isolation=SERIALIZABLE,那么事务的默认隔离界别就从原来的REPEATABLE-READ变成了SERIALIZABLE。