一、什么是事务
事务是数据库操作的最小工作单位,一个事务可以是一条sql语句,也可以是一组sql语句。
事务有四大特征:原子性,持久性,隔离性,一致性,就是我们常说的ACID
二、原子性
原子性的意思是说一个事务里的操作会组成一个最小的执行单位,不可再分割,在一个事务中,要么操作都成功,要么都失败。比较经典的例子就是我们的转账,A向B转账,A要完成减钱,B要完成加钱,这些是都需要完成才能算一个事务完成。如果在其中发生了错误那么就需要回滚。回滚的时候就需要用到我们回滚日志,undo log。因为我们目前常用的存储引擎都是Innodb,在Innodb中还使用undo log + mvcc来实现事务的可重复读和读已提交这两个隔离级别。
undo log分为两种,一种是insert undo log,用来记录插入操作产生的回滚日志,一种是update undo log,用来记录更新,删除操作的时候产生的回滚日志。
当我们要操作数据之前,就先把数据备份到undo log中,然后我们再对数据进行修改,在这个过程中如果出现了错误,那么mysql就会利用undo log中的数据来恢复。
undo log的工作方式可以简单的理解成记录相反的操作到日志中,比如我们更新的时候,他就记录一条之前更新的状态,我们删除数据的时候他就会添加一条数据,我们添加数据的时候他就会删除一条数据。
三、持久性
持久性的意思是说当我们事务提交之后,他形成的数据应该被永久化为数据库中。也就是应该持久化到磁盘上,因为在内存中一断电就没了。
持久性实现的原理是redo log,前滚日志。
因为我们在更新完一条记录的时候,我们数据是先在内存中的,再从内存持久化到我们的磁盘。
因为数据一开始在内存中,如果断电肯定数据就没了,所以redo log就是为了解决这个问题。
当我们每次执行完DML操作之后,也就是当我们事务commit的时候,这里其实有一个异步的操作。首先执行过程会写入Buffer Pool,mysql会优先写日志,先保证日志写成功,写日志会有三种情况,0.写到Log Buffer,1.直接写到磁盘里,然后每秒为单位写到磁盘里 2.写到OS Buffer,然后每秒为单位写到磁盘。这里有一个参数innodb_flush_log_at_trx_commit
来控制,默认是直接写到磁盘里。而对于日志的提交频率都是每秒为单位,而我们真实的数据库的数据,可能五秒再持久化一次到磁盘。
对于0和2这两种方式,虽然都是写到内存,但是还有有所差别的,因为Log Buffer属于数据库服务,而OS Buffer属于操作系统,所以如果mysql服务挂了,那么不会影响OS Buffer把数据写到磁盘。
四、隔离性
隔离性是说事务之间的执行不应该产生相互影响,他们对数据库的影响应该和他们串行执行的结果一样。
但是如果是完成遵守隔离性,对并发的性能影响会比较大,所以一般来说我们工作中会对隔离性有所放宽。正是因为没办法两全,所以产生了一些数据的问题,也就是脏读,不可重读,幻读。sql的标准定义了四个隔离级别,分别是读未提交,读已提交,可重复读,串行化。mysql默认隔离级别是可重复度。四个隔离级别分别是来解决这三种数据问题的。串行化是隔离级别是最高的隔离界别,这种隔离级别情况下,每个读的数据行上都会加锁,所以效率很慢,会导致大量的超时现象。可重复读会出现幻读,读已提交会出现不可重读和幻读,读未提交会出现脏读,不可重读,幻读。
实现mysql的隔离级别主要利用的是锁机制,利用锁来解决了脏读幻读,不可重读。mysql主要有三种级别的锁,分别是行锁,表锁,页锁。表锁不会出现死锁,行锁和页锁都有可能出现死锁。
因为我们大部分是使用Innodb的引擎,所以我也说一下Innodb的锁,就是两种,共享锁和排它锁,他们都是行级别的锁。共享是说同一行的数据,可以被多个事务获取共享锁,但是只可以读,不能修改。排他锁是指一行数据只能被一个事务获得排他锁,获取排他锁的事务可以修改数据。
Innodb还支持行锁和表锁并存。他这里的实现原理是利用意向锁,意向锁也是两种,意向共享锁和意向排他锁。意向共享锁就是说在事务准备给数据行加共享锁前,先给这个表加一个意向共享锁,意向排他锁也是相同的意思。各个意向锁之间是兼容的,他设计这个的目的是:当事务想去进行锁表时,可以先判断意向锁是否存在,存在时则可快速返回该表不能启用表锁,否则就需要等待。
我们使用Innodb主要是因为他支持行锁,支持事务,Innodb的行锁是给索引加锁实现的,只有通过索引检索数据,才能使用行锁,不然就是会使用表锁。这里又要提到一个临键锁(next-key),其实临键锁就相当与记录锁(Record Locks)加上间隙锁(Gap Locks)。记录锁很简单是单条记录的锁,是单个的,间隙锁也是见名知义,表示我们数据之间的空隙。就是我们的索引项有时候是不连续的,比如id有1,3,7,9这些id中间就有空隙,间隙锁就是当我们进行范围查询的时候,会把这些空隙也锁住。
通过上面的排他锁,解决了脏读,共享锁解决了不可重复读,临键锁解决了幻读。
1.脏读,幻读,不可重读
脏读的意思是说多个事务在执行过程中,A事务读取到了B事务还没commit的数据,然后B事务又rollback回滚了,那么A读到的数据就是脏数据。
不可重读是指在同一个事务A中,我们对同一组数据进行两次一模一样的查询,但是因为在这两次查询中间,有事务B修改了数据,导致事务A两次查询结果不一样,这就出现了不可重复读。
幻读一般是我们在进行范围查询的时候,事务A对id大于10的数据进行查询,同样也是两次查询,在这个过程中,事务B新增或者删除了数据,导致事A在查询的时候两次结果不一致。也就是是幻读。
五、一致性
事务的一致性可以说是最重要的。即事务执行前后数据应该是一致的。还是那个转账的例子,当A向B转钱,那么最终结果A减钱B加钱,这两个的和应该还是一样的。事务的一致性实现的原理其实就是之前是三个特性,原子性,隔离性,持久性共同保证了一致性。