事务就是一组原子性的SQL查询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组的全部语句,如果其中有任何一条语句因为崩溃或者其他原因无法执行,那么所有的语句都不会执行。(事务内部中的语句要么全执行,要么全不执行)
除非系统通过严格的ACID测试,否则空谈事务的概念是不够的。
- 原子性(Atomicity)一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
- 一致性(Consistency) 数据库总是从一个一致性的状态转换到另一个一致性的状态
- 隔离性(Isolation) 一个事务所做的修改在最终提交以前,对其他事务是不可见的。
- 持久性(Durability) 一旦事务提交,则其所做的修改就会永久保存到数据库中。
就像锁粒度的升级会增加系统开销一样,这种事务处理过程中额外的安全性,也会需要数据库做更多的额外工作。通常需要更强的CPU处理能力,更大的内存和更多的磁盘空间。用户需要根据不同的业务来选择是否需要事物处理,选择何种存储引擎。对于一些不需要事务的查询类应用,选择一种非事务型存储引擎可以获得更高的性能。即使是存储引擎不支持事务,也可以通过LOCK TABLES提供一定程度的保护。
隔离级别
- READ UNCOMMITITED 未提交读 事务中的修改,即使没有提交,对其他事务都是可见的。事务可以读取没有提交的数据(脏读,Dirty Read)
- READ COMMITTED 提交读 大多数数据库系统的默认隔离级别都是READ COMMITTED(MySQL不是)。一个事务开始时,只能看见已经提交的事务所做的修改。这个级别有时候也叫做不可重复读(nonrepeatable read),因为在同一事务中的两次执行同样的查询,可能会得到不一样的结果。
- REPEATABLE READ 可重复读 保证了在同一个事务中多次读取同样记录的结果是一致的。但无法解决幻读(Phantom Read)。幻读指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围插入了细腻的记录,当之前的事务再次读取该范围的记录,会产生幻行(Phantom Row)。可重复读是MySQL的默认事务隔离级别
- SERIALIZABLE 可串行化 通过强制事务串行执行,避免了前面说的幻读的问题。SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发情况下,才考虑采用该级别。
死锁
为了解决死锁问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。
InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。锁的行为和顺序是和存储引擎相关的。以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。
事务日志
事务日志可以帮助提高事务效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。目前来说,大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。
MySQL提供两种事务型的存储引擎:InnoDB和NDB Cluster。另外还有第三方存储引擎也支持事务,比较知名的包括XtraDB和PBXT。
自动提交(AUTOCOMMIT)
MySQL默认采用自动提交(AUTOCOMMIT)模式,也就是说,如果不是显式地开始一个事务,则每个查询都被当作一个事务执行提交操作。当我们禁用AUTOCOMMIT时,所有的查询都是在一个事务中,直到显式地执行COMMIT或者ROLLBACK,让事务结束。
修改AUTOCOMMIT对非事务类型的表,譬如MyISAM或者内存表,不会有任何影响。
还有一些命令在执行之前会强制执行COMMIT提交当前活动的事务(譬如DDL)
MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL 命令来设置隔离级别。新的隔离级别会在下一个事务开始的时候生效。
在事务中混合使用存储引擎
MySQL服务器层不管理事务,事务是由存储引擎实现的。所以在同一个事务中使用多种存储引擎是不可靠的。
如果在事务中混合使用了事务型的表和非事务型的表,在正常提交的情况下不会有什么问题,但是如果该事务需要回滚,非事务型的表上的变更就无法撤销,这会导致数据库处于不一致的状态,故而难以修复。
隐式和显式锁定
InnoDB采用的是两段锁协议,在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT或者ROLLBACK的时候才会被释放,并且所有的锁是在同一时刻被释放。(隐式锁定)
InnoDB也支持通过特定的语句进行显式锁定(不属于SQL语句规范)
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE
MySQL也支持这两种语句,这是在服务器层实现的,和存储引擎无关。但不能代替事务。 如果应用将表从MyISAM转换到InnoDB,但是还是显式地使用LOCK TABLES语句,不但没有必要,还会严重影响性能。