本篇文章来讲讲事务是什么。
事务就是将一系列放在一起的操作,要么全部成功,要么全部失败。我们在实际的业务中,需要把有些 sql 语句放到一起,要求他们全部执行成功,如果有一个失败了,这个业务逻辑就失败了,其他的 sql 语句要回滚。
MySQL 中,事务功能是在引擎层实现的,但并不是所有引擎都支持事务。早期流行的 MyISAM 引擎就不支持事务,而后他就被支持事务的 InnoDB 取代了。
隔离性与隔离级别
事务有四个特性:原子性、一致性、隔离性、持久性。本文详细讲讲隔离性。
如果数据库同时有多个事务在执行,就可能导致数据错误,这就涉及到隔离级别的概念了。
MySQL 的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
- 未提交读:一个事务还没提交的时候,它里面做的数据更改就可以被其他的事务看到。
- 读提交:一个事务提交之后,它做的更改才能被其他事务看到。
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据一致。提交后,它做的变更才能被其他事务看到。
- 串行化:对于同一行数据,读写都会加锁。当出现锁冲突时,后者必须等前者执行完成释放锁。
事务隔离如何实现?
我们以可重复读为例子,讲解事务隔离是如何实现的。
MySQL 每次更新记录的时候,都会同时记录一条回滚操作,这样记录上的最新值,可以通过回滚操作,一步一步回去这个数据之前的数值。
举个例子
如果一个值是 1,之后更新操作按照顺序改成了 2、3、4,每一步操作都会记录一个回滚操作,回滚的日志并不会一直保留,在不需要的时候会删除。系统会判断,当没有事务再需要使用这些回滚日志,回滚日志会被删除。
如何判断出不需要了呢?当系统里没有比这个回滚日志更早的 read_view 的时候。
由此可见,长事务是不推荐使用的。长事务意味着系统中会存很长很老的事务视图,事务没提交前,所做的一切操作都要做到可回滚,就需要很多条回滚记录保留下。这就会占用非常大的存储空间。
在 MySQL 早期版本中,回滚日志是跟数据字典放在一起,放在 ibdata 文件中。长事务最终被提交了,回滚日志被清理,这个文件也不会变小。最终会导致资源占用过大,重建数据库才能恢复。
事务的启动方式
从上文我们知道了长事务是有风险的,那么如何避免错误代码的使用导致开启长事务呢?
MySQL 事务的启动方式有以下两种:
- 显式启动事务语句,使用
begin
或者start transaction
。提交语句使用commit
,回滚语句使用rollback
。 - 启动事务前,设置
set autocommit = 0
,这个语句的意思是关闭线程的自动提交,需要手动执行commit
或者rollback
才会结束。
有的代码框架默认使用第二种,而我们却不知道,也不手动提交,就会造成超级长的事务产生。
建议使用第一种方式,同时设置 set autocommit = 0,并显式启动事务,这样就不会有问题了。
好的本文就到这里。回顾下,本文讲解了事务的概念,隔离性和隔离级别,隔离的实现原理以及如何正确开启一个事务。在实战代码中,事务操作不当是非常容易造成严重的数据库错误的,如果不好好学习这块,就可能会闯祸了。