一、事务的基本操作
事务是指一系列有序的数据库操作,这些操作要么全部成功,要么全部失败,其中间的状态对于其他连接是不可见的。在mysql中事务功能的实现主要通过start transaction/begin、commit、rollback来实现。start transaction/begin是用于开启事务的、commit用于提交事务、rollback用于回滚事务。
举例而言,以测试表t为例,t表表结构如下:
create table `t`(
`a` int(11) not null default '0',
`b` int(11) default null,
`c` int(11) default null,
primary key(`a`)
)engine=innodb default charset=utf8;
首先用begin或
start transaction开启事务(注:标准sql中只有start transaction而没有begin,但在mysql中两者是等价的)
begin;
然后执行一些正常的数据操作语句,这里就简单的插入一条数据,如下。
insert into t values(1,1,1);
最后执行commit,如下:
commit;
通过查询语句,发现数据正常插入,如下图
再次打开事务,并插入数据,但是最后执行rollback命令,语句如下。
begin;
insert into t values(2,1,1);
rollback;
再执行查询,发现新插入的数据并没有生效,如下图。
下面再次打开事物,插入数据并查询,语句如下。
begin;
insert into t values(2,1,1);
insert into t values(3,1,1);
select * from t;
结果如下:
然后在windows的服务中重启mysql服务,如下图。
重新连接数据库,查询t表,结果如下:
二、保存点savepoint
mysql中可以给一些较长的事务设置保存点,使回滚操作回滚到保存点,依旧以t表为例,首先用truncate命令清空t表。
truncate t;
然后开启事务,插入一条数据并创建一个保存点a1,代码如下:
begin;
insert into t values(1,1,1);
savepoint a1;
然后再插入一条数据,再用rollback回滚到a1,最后查看t表中的结果,代码如下:
insert into t values(2,1,1);
rollback to a1;
select * from t;
结果如下,可以看出后面插入的数据被回滚掉了,而a1之前的数据没有被回滚掉。
三、事务的自动提交
mysql中默认是开启autocommit的,即当执行任何一个DML语句后mysql都会自动执行一个commit操作提交事务。autocommit的查看通过show variables like "%autocommit%";命令实现,如果需要关闭通过set autocommit=0;来实现。
另外,当执行DDL操作后,之前的所有操作都会被自动提交。例如,当在事务中插入了几条数据,然后修改了该表的结构,那么之前的插入操作将被自动提交,无法回滚。以t表为例,执行如下语句。
begin;
insert into t values(1,1,1);
alter table t add d int;
rollback;
select * from t;
结果如下,可以看出之前的插入操作没有被回滚。
四、事务的四个基本属性(ACID)
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
原子性是指包含在事务中的操作要么全部被执行,要么全部不执行,如果中途数据库或者应用发生异常,未提交的事务都应该回滚。
一致性是指数据的正确性、合理性和完整性。事务的执行结构应该满足数据的一致性约束。例如,银行系统中的账户余额不能为负数(可以用无符号数类型实现)、交易对象必须有账号(可以通过外键约束实现)、用户账号不能重复(可以用唯一索引实现)。
隔离性是指数据库事务在未提交完成前,中间的任何数据变化对其他的事务都是不可见的。
持久性是指提交完成的事务对数据库的影响必须是持久的,数据库的异常不会导致事务更新结果的丢失。一般认为成功写入磁盘的数据即为持久化成功。
五、持久化的实现
由于数据表中数据的存放位置是随机分散在各个磁盘的各个磁道上的,因此每次将数据写入磁盘往往要先寻道然后再写入,速度非常慢,如果在寻道的过程中数据库异常了将导致数据的丢失。因此mysql中采用事务日志的方式降低错误的概率。每次执行一个事务时mysql都将执行的行为记录到一个事务日志中,事务日志在磁盘中是顺序存储的,因此修改十分快,然后mysql的其他线程对修改的数据进行异步的修改,如果mysql突然宕机,恢复后mysql可以通过事务日志来回放之前事务中的操作,保证数据不会丢失,过程如下图所示。
六、数据库隔离等级与隔离现象
mysql中的隔离等级有四个,Read uncommitted(未提交读)、Read committed(已提交读)、Repeatable read(可重复读)、Serializable(可串行化)。
隔离现象是只在事务中出现的异常现象,有三种,分别是脏读、不可重复读和幻读。脏读是指事务B读到事务A尚未提交的数据。不可重复读是指由于事务A提交了数据更新,导致事务B中进行的两次查询的结果不一致。幻读是指由于事务A提交了新插入的数据,导致事务B在两次查询中出现了数据不一致的情况。
mysql的innodb存储引擎中隔离等级与隔离现象的对应关系是,Read uncommitted级会有脏读、不可重复读以及幻读的现象;Read committed级不会有脏读,但是会有不可重复读和幻读现象。Repeatable read和Serializable级不会存在上述的三个隔离现象(注:只有在innodb中Repeatable read级三种隔离现象都不存在,而别的数据库中的Repeatable read可能是不存在脏读、不可重复读但是会有幻读)。
mysql中查看和设置隔离等级的命令如下:
show variables like "%iso%";
set tx_isolation='READ-UNCOMMITTED';
一般在线上项目中推荐使用
Repeatable read或者Read committed,Read uncommitted不能使用(极易导致数据错误),Serializable影响效率。
下面举例说明三种隔离现象。
脏读:
首先将mysql的隔离等级设置为Read uncommitted,然后重新打开两个mysql连接A和B;
首先用begin命令在A和B中打开事务,然后再A中向t表插入一条数据,如下图:
然后在B连接的事物中查看t表,结果如下,可以在该事物中查看当新插入的数据。这就是脏读。
不可重复读与幻读
首先将隔离级别设为Read committed,在执行上述的操作,结果如下图。
可以看得脏读现象已经不存在了,但是如果A将事务提交,B在进行查询,结果如下。
可以看到两次查询的结果不一致,多出了一条之前不存在的记录,这就是幻读。
将B也commit,然后重新再A和B中打开事务,在B中查看t表,结果如下。
在A中更新这条记录并提交,再在B中查看t表,结果如下图。
可以看到,B中两次查询的结果中的b列的值不一致,这就是不可重复读。
七、并发写
当一个事务对一条记录进行写操作时,会锁定该记录,除非事务提交或回滚,然后释放锁,否则其他的事务是无法修改记录的。
八、回滚的实现
mysql中通过设置回滚段来实现回滚,回滚段中存放的是数据表再执行DML操作之前的数据前像,如果提交事务则清空回滚段,如果回滚则通过回滚段中的数据前像回滚数据。