MySQL事务介绍
什么是事务
事务的概念
从业务层面上来说,事务就是一个最小的不可分割的单元,通常一个事务对应的是一个完整的业务(比如银行的转账操作)。
为什么要有事务
仍以银行转账为例加以说明,比如我要从账号a转账100元到账号b,现在数据库有一张表account,那么就意味着需要同时执行两条sql语句的更新:
update account set amt = amt-100 where acc_no = 'a'; update account set amt = amt+100 where acc_no = 'b';
以上两条sql,第一条表示账号a余额减少100元,第二条sql表示账号b余额增加100元,只有两条sql都执行成功,才能被认为是转账成功。
我们假设第一条sql执行成功了,第二条sql执行失败,于是a账户的钱少了100,但是b账户的钱并没有增加,对于银行来说,这100元相当于凭空消失了,所以对于转账这个业务来说,这两条sql是一个不可分割的整体,必须放在一块执行,要么都成功,要么都失败。
所以必须要由事务来进行控制。
如何开启/提交事务
每个事务都需要开启和提交(或回滚),在开启和提交(回滚)事务之间的操作,都被认为是一个事务的操作。也就是说,在同一个事务里的操作,如果该事务没有提交或回滚,那么其操作都只是在缓存里生效,而并没有在实际数据库中生效,只有当数据提交了之后,才会真正在数据库生效。
回滚事务是相对于提交事务而言的,提交事务是使事务中的操作在数据库生效,而回滚事务就是将数据库状态回滚到开启事务之前的状态。
开启事务
开启事务有以下几种不同的语法:
start transaction; begin; set autocommit =0;
以上三种语法都可以开启事务,其中第三种set autocommit =0;
含义是关闭自动提交,也就意味着需要手动提交,在事务没有提交之前,都不会在数据库生效,也相当于开启了事务。
提交事务
提交事务的语法也很简单,只需要执行以下语句就可以了:
commit; //或 commit work;
回滚事务
回滚语法也是简单的一句语法:
rollback;
接下来以一个具体的例子加以说明:
我在数据库里有这样一张表:
mysql> select * from test01; +------+------+--------+--------+ | id | name | passwd | inf | +------+------+--------+--------+ | 1 | zz | 123456 | asdfgh | +------+------+--------+--------+ 1 row in set (0.04 sec)
接下来我们需要开启一个事务,在事务里添加一条数据,再开启另外一个会话,在另一个会话里,食物提交前和事务提交后分别访问该表,看看区别在哪里。
--开启事务 mysql> start transaction; query ok, 0 rows affected (0.00 sec) --插入数据 mysql> insert into test01 values(2, 'aa', '123123', 'worktest'); query ok, 1 row affected (0.03 sec) --在当前事务中查询,有两条数据 mysql> select * from test01; +------+------+--------+----------+ | id | name | passwd | inf | +------+------+--------+----------+ | 1 | zz | 123456 | asdfgh | | 2 | aa | 123123 | worktest | +------+------+--------+----------+ 2 rows in set (0.00 sec)
此时在另外一个会话中查询:
--在另外的事务中查询,只有一条数据 mysql> select * from test01; +------+------+--------+--------+ | id | name | passwd | inf | +------+------+--------+--------+ | 1 | zz | 123456 | asdfgh | +------+------+--------+--------+ 1 row in set (0.00 sec)
可以发现,虽然在事务中已经执行了insert语句,但在其他的会话中,在没有提交之前,仍然没有生效,查看到的仍然只有一条。
但是当在事务中执行以下语句后:
mysql> commit; query ok, 0 rows affected (0.03 sec)
再次查询,发现insert的记录就能够查询到了:
mysql> select * from test01; +------+------+--------+----------+ | id | name | passwd | inf | +------+------+--------+----------+ | 1 | zz | 123456 | asdfgh | | 2 | aa | 123123 | worktest | +------+------+--------+----------+ 2 rows in set (0.01 sec)
回滚亦是同理,这里就不做演示了。不过仍然可以用转账的例子来说明一下,现在将转账执行的两条sql放在一个事务中执行,第一条a账户减去100元,第二条b账户增加100元,假设第一条语句执行成功,第二条语句执行失败,我们在代码的逻辑实现里,只需要加上当有任意一条sql语句执行失败,事务回滚,则不会出现凭空消失100元或凭空增加100元的情况了。
事务的四大特性(acid)
事务的四大特性,也就是俗称的acid,即原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
原子性
所谓原子性,就是一个事务中,所有的操作都是最小单元,不可分割的。要么全部完成,要么全部不完成,不会执行一半然后结束。
一致性
一致性是指事务开始之前和结束之后,数据库的完整性没有被破坏,即事务中的所有sql语句的dml操作,要么都成功,要么都失败,不会既有成功也有失败的情况出现。
隔离性
隔离性是指每个事务都是相对隔离的,即a事务不会对b事务造成影响。从上面的例子中也能看出这一点,a事务已经插入了数据,但是b事务中并不能访问到,也就是说每个事务之间是相互隔离开的。
持久性
持久性是说,事务一旦提交,对数据的修改就是持久有效的,和当前事务再没有任何关系。
事务隔离级别
事务隔离级别的提出,主要是为了解决脏读、幻读和不可重复读的问题。
脏读:
- 事务a读取了事务b的数据,然后b回滚,a读到的数据就是脏数据;
不可重复读:
- 事务a多次读取同一数据,但是事务b在事务a读取的过程中,对数据进行了操作,导致事务a前后两次读取到的数据不一致;
幻读:
- 事务a对数据进行dml操作,在这个过程中同时有事务b也对该数据进行了新增操作,导致事务a执行完后发现并不是所有的数据都改变了过来,就像出现了幻觉,因此叫幻读。
以例子来说明:
如上图所示,一开始账户金额为0,开启事务b后,执行更新操作,金额修改为1000,此时事务a去读到了这个1000,但是之后事务b将事务回滚了,实际上金额仍为0,所以其实事务a读到的事务是不准确的,属于“脏数据”,这就是脏读。
在这个例子中,事务b开启后,先将金额更新为1000,此时a去读,读到了1000,但是之后b又将金额更新为了500,然后提交数据,此时金额500 才是最终的数据,a再去读,发现金额已经变成了500,造成了前后两次读取不一致,这就是不可重复读。
上例中,同时开启了两个事物,事物a对account表中所有账户的金额进行了置0操作,但是在事务a开启的过程中,事务b在account表中插入了一条金额为1000的数据,然后,事务a先提交,再事务b提交,如果这个时候去查询account表,发现会有一条金额为1000的记录,因为该记录是由事务b插进去的,所以没有更新到,此时就会造成幻读。
所谓事务隔离级别,就是为了解决以上三种情况而提出的。
读未提交(read uncommitted)
最低的一种隔离级别,该隔离级别可能会造成脏读。当然也可能会出现不可重复读和幻读。
即:a事务未提交的数据,b可以读取到。
读已提交(read committed)
该隔离级别高于read uncommitted
,可以有效解决脏读,但是仍然不能避免不可重复度和幻读。
即:a未提交的数据,b读取不到,b只能读取得到a已经提交的数据。
可重复读(repeatable read)
从名字也知道,该隔离级别可以解决不可重复读。隔离级别要高于read committed
,但是不能解决幻读。这是mysql默认的隔离级别。
即:a提交后的数据,b还是读取不到。
串行化(serializable)
串行化是最高的隔离级别,可以有效解决“幻读”。这种隔离级别吞吐量太低,因为当前事务开启时,别的事务只能等待,非常影响效率,一般很少使用。
即:a开启事务,b只能排队等待。
总结起来,大约如下表所示:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | 可能 | 可能 | 可能 |
read committed | 不可能 | 可能 | 可能 |
repeatable read | 不可能 | 不可能 | 可能 |
serializable | 不可能 | 不可能 | 不可能 |
如何设置事务隔离级别
使用一句话即可:
set [global | session] transaction isolation level <isolation-level>
global
是指隔离级别全局有效,session
当前会话有效。
如当前会话设置为读未提交:
set session transaction isolation level read uncommitted;
当前会话设置为读已提交:
set session transaction isolation level read committed;
当前会话设置为可重复读:
set session transaction isolation level repeatable read;
当前会话设置为序列化:
set session transaction isolation level serializable;
如何查看事务隔离级别:
查看当前会话隔离级别:
select @@tx_isolation;
查看全局隔离级别:
select @@global.tx_isolation;
上一篇: 理解Go语言组件flag