事务是什么?
简单来说,事务是用来保证一组操作要么全部成功,要么全部失败的机制。
事务的四大特性
- A(Atomicity)原子性。一组操作要么全部成功,要么全部失败。
- C(Consistency)一致性。事务执行前后,数据的逻辑是保持一致的。
- I(Isolation)隔离性。事务并发执行期间,各事务之间不会相互影响。
- D(Durability)持久性。事务一旦提交,即使数据库发生崩溃,数据也不会丢失。
事务的隔离性
事务的隔离级别包括:
- 读未提交:一个事务可以读到其他事务未提交的数据。存在的问题:脏读、不可重复读、幻读。
- 读已提交:一个事务可以读到其他事务已经提交的数据。存在的问题:不可重复读、幻读。Orcale数据库的默认隔离级别。
- 可重复读:在同一个事务中,多次执行同一SQL,执行结果都是一致的。存在的问题:幻读。MySQL的默认隔离级别。
- 串行化:支持并发的读,不支持并发的读写,读、写都需要加锁。因为是串行化的执行,所以存在隔离性的问题,但是性能较差。
有人说,MySQL在可重复读的隔离级别下已经解决幻读问题了,那到底有没有解决呢?是如何解决的?先说结论,细节之后的文章进行讨论。
结论:MySQL在可重复读的隔离级别下没有彻底解决幻读问题,只是很大程度上避免了幻读现象的发生。MVCC只能在快照读的场景下可以解决幻读,在当前读场景下无法解决幻读。
如何开启和提交事务?
MySQL在执行SQL时,默认会开启事务,执行完成后会默认提交事务。不需要手动开启,自动提交事务是通过autocommit参数来控制的。
我们也可以通过begin/start transaction的方式手动开启事务,执行完成后通过commit/rollback的方式结束事务。
通过show variables like 'autocommit';命令可以查看是否开启自动提交事务。如果关闭后需要手动提交事务。
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
我们可以通过修改通过命令或mysql的配置文件的方式修改该配置。
-- 只在当前会话中生效
mysql> set session autocommit =0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
-- 全局会话生效
mysql> set global autocommit =0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
但是通过命令的方式修改,数据库重启后会失效。通过改配置文件的方式,可以永久生效。
[mysqld]
autocommit=0
执行SQL时,为什么要保证事务?
例如在银行转账的场景下,张三要给李四转100块钱,张三的账户应该扣100块钱,李四的账户应该加100块钱,那么对应的sql为:
update account set money=money-100 where name='张三';
update account set money=money+100 where name='李四';
如果没有事务,在执行完第一条SQL后,数据库异常重启了,重启之后发现,张三的账户少100块钱,但是李四的账户没有变,钱凭空少了100,这正是由于没有事务导致的问题。
MySQL是如何实现事务的?
MySQL是靠多个组件密切配合来实现事务的。
- 原子性:undo log。在事务的执行过程中,修改数据时会把修改前的数据记录到undo log中,如果修改过程中报错了,数据库会执行回滚操作,利用undo log中的日志恢复数据。
- 隔离性:数据库锁。修改数据时,需要先获取对应行上的锁,修改完成提交事务后,锁才能释放。所以在事务执行过程中,不会有事务并发的去修改同一行数据。
- 持久性:redo log。在修改数据前,先将修改内容写入redo log中,即使内存中的脏页还没来得及刷盘,数据库重启后还可以通过redo log中的日志恢复数据。
以上三种特性来共同保证数据的一致性。
MySQL中的日志有哪些?
undo log
是什么? undo log 页中记录的是执行语句前的数据和执行语句的类型,undo log语句类型可分为两大类,insert undo log和update undo log,后者又可以细分为三种。type_cmpl为11表示insert,为12表示non-delete,为13表示将delete标记为non-delete,为14表示delete。当需要进行回滚或通过MVCC读当前记录的旧值时,通过执行相反的语句来实现。 在哪里? 默认是以页的形式记录在共享表空间中,也可以通过配置修改到独立表空间中。 存多长时间? insert undo log因为只对当前事务可见,所以当前事务执行完commit操作后就可以删除了。update log不能在事务commit之后立即删除,因为这时可能还有其他事务需要利用update undo log来恢复旧的数据,实现MVCC快照读。当purge线程检测到update undo log的事务ID比当前所有活跃的事务ID都要小时,这些日志就可以被删除了。 补充:undo log也需要记录到redo log中。 扩展:为什么要避免大事务? 因为一个事务很大,事务执行期间会产生很多undo log,占用很多磁盘空间,而且由于undo log页不是立即被清理且可以复用,所以undo log的读取与清理涉及到随机IO,大量的随机IO会降低数据库的性能。
redo log
作用:用来保证事务的持久性,当事务提交后,脏页还没来得及刷盘时,如果服务器宕机,重启后可通过存储在磁盘中的redo log恢复数据。 性能:顺序写且是异步刷盘,性能还行。 原理:默认两个日志文件,ib_logfile0和ib_logfile1,写满后通过checkpoint机制进行删除旧日志、写新日志。checkpoint是通过LSN(Log suquce number)来实现的(存疑?),每个事务、数据页和redo log日志中都有LSN,checkpoint的目的是把脏页刷回到磁盘。 为了加快redo log的写入速度,引入了redo log buffer,默认当事务提交时,把缓存中的日志刷盘。
redo log存储的位置:当数据被修改时,redo log首先被写入redo log buffer中,当事务提交时会将redo log buffer中的日志刷盘到redo log file中。刷盘的时机是由innodb_flush_log_at_trx_commit来控制的,默认为1。0-事务提交时不对redo log操作,后台线程每秒会将redo log写入操作系统缓存并执行fsync将操作系统缓存中的数据刷到磁盘;1-事务提交会将数据写入操作系统缓存中,并立即执行fsync操作,将数据刷到磁盘。2-事务提交只写入操作系统缓存,后台线程每秒会将操作系统缓存中的数据刷盘到磁盘中。0和2的区别是,当Mysql数据库宕机,但是操作系统不宕机时,不会丢失已经写入操作系统缓存中的数据。redo log file默认的文件是ib_logfile0和ib_logfile1两个,这两个文件可以循环写,写满后可以重头写。
redo log什么时候可被删除?
当redo log没用时就可以被删除了,什么时候没用呢?redo log对应修改的数据页刷盘后,redo log就没用了。
bin log
是什么:Mysql服务器层面的用来记录数据修改情况的二进制日志。 作用:主从同步、数据恢复 格式:statement、row、mixed statement记录的是被执行的sql语句,占用空间小。 row格式记录的是被修改的行中每个字段修改后具体的值,可以解决statement中随机函数、时间函数在从库中执行,导致主从数据不一致的问题,占用空间大。 mixed是介于二者之间的一种格式,当sql中包含类似随机函数、时间函数等可能导致主从不一致的函数时,会采用row格式记录bin log 日志,否则采用statement格式记录,占用空间较小。 原理:当更新类语句执行时,数据库执行完提交事务时,bin log会被写入bin log buffer中,参数sync_binlog=[N] 表示每写N次缓冲写就进行一次刷盘。 和redo log undo log的区别: redo log记录的是修改之后的数据,记录的是数据页偏移量为xxx的地方被修改为yyy,是物理日志。 undo log记录的是修改前的数据,是逻辑日志 bin log记录的是修改的语句,也是逻辑日志。 和redo log的联系: 事务提交时,先写redo log,此时是prepare阶段,然后写bin log,最后再写redo lpg,此时是commit阶段,commit阶段写入很快。两阶段阶段提交可以保证数据库崩溃恢复后,redo log和bin log之间保证数据一致性。事务提交时,如果redo log的prepare还没有提交,此时bin log还没有写入,重启数据库后不需要进行恢复操作。如果redo log的prepare已经提交,但是bin log还没有写入,此时认为事务提交失败,删除redo log中prepare阶段提交的内容。如果redo log的prepare阶段和bin log都已经提交,但是redo log的commit阶段失败了,此时认为事务成功提交,重启后需要应用redo log日志。如果commit阶段也已经完成,重启后应用redo log即可。当然重启后是否真的应用redo log日志,还需要看脏页是否已经刷盘,如果已经刷盘则不需要应用redo log。
slow log
我们可以通过设置慢查询的时间配置,当sql执行的时间超过设置的时间时,就会把sql语句记录到slow log中,帮运维与开发人员记录问题。
error log
数据库在启动、运行过程中发生的异常、错误都会记录到这张表中,以供DBA排查问题。