事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
一个事务内的语句,要么全部执行成功,要么全部执行失败。
银行应用是解释事务必要性的一个经典例子。
假设一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户的支票账户转移200元到他的储蓄账户,那么至少需要下面三个步骤:
上述三个步骤必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。
1 START TRANSACTION; 开启事务
2 SELECT balance FROM checkking WHERE customer_id = 1;
3 UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 1;
4 UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 1;
5 COMMIT;提交事务
正常情况下,使用START TRANSACTION 开启事务,SQL语句全部执行成功后使用COMMIT提交事务。如果在执行SQL语句的过程中出现了错误,可以使用ROLLBACK回滚事务,这个命令可以撤销事务中所有的修改。
事务的ACID特性可以确保银行不会弄丢你的钱。
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,在哪些事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
MySQL可以通过执行 SET TRANSACTION ISOLATION LEVEL 命令来设置隔离级别。
新的隔离级别会在下一个事务开始的时候生效
可以在配置文件中设置整个数据库的隔离级别,也可以只改变单签会话的隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
MySQL能够识别所有的4个ANSI隔离级别,InnoDB引擎也支持所有的隔离级别。
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。
当多个事务试图以不同的顺序锁定资源时、多个事务同时锁定同一个资源时,都会产生死锁。
例如下面两个事务同时执行:
#事务1
START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 1 and date = '2018-12-27';
UPDATE StockPrice SET close = 55.50 WHERE stock_id = 2 and date = '2018-12-28';
COMMIT;
#事务2
START TRANSACTION;
UPDATE StockPrice SET close = 55.50 WHERE stock_id = 2 and date = '2018-12-28';
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 1 and date = '2018-12-27';
COMMIT;
如果两个事务都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条UPDATE语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外部因素介入才可能解除死锁。
InnoDB目前处理死锁的方法是:将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。
锁的行为和顺序是和存储引擎相关的。
死锁发生以后,只有部分或全部回滚其中的一个事务,才能打破死锁。
对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。
大多数情况下只需要重新执行因死锁回滚的事务即可。
事务日志可以帮助提高事务的效率。
使用事务日志,存储引擎在修改表的数据时字需要修改其内存拷贝,再把该修改行为持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回磁盘。
目前大多数存储引擎都是这样实现的,通常称之为预写式日志,修改数据需要写两次磁盘。
如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。
具体的恢复方式视存储引擎而定。
MySQL默认采用自动提交模式。
如果不是显式的开始一个事务,则每个查询都被当做一个事务执行提交操作。
在当前连接中,可以通过设置AUTOCOMMIT变量来启用或者禁用自动提交模式:
SHOW VARIABLES LIKE 'AUTOCOMMIT';
#1或者ON表示启用,0或者OFF表示禁用
SET AUTOCOMMIT = 0;
当AUTOCOMMIT=0时,所有的查询都是在一个事务中,知道显示地执行COMMIT提交或者ROLLBACK回滚,该事物结束,同时又开始了另一个新事务。
修改AUTOCOMMIT对非事务型的表,不会有任何影响。对于这类表来说,没有COMMIT或者ROLLBACK的概念,也可以说相当于一直处于AUTOCOMMIT启用模式。
**有一些命令在执行之前会强制执行COMMIT提交当前的活动事务。**比如ALTER TABLE,LOCK TABLES等。
MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。
所以在同一个事务中,使用多种存储引擎是不可靠的。
如果在事务中混合使用了事务型和非事务型的表,在正常提交的情况下不会有什么问题。
如果改事务需要回滚,非事务型的表上的变更就无法撤销,这会导致数据库处于不一致的状态,这种情况很难修复,事务的最终结果将无法确定,所以为每张表选中合适的存储引擎非常重要。
在非事务型的表上执行事务相关操作的时候,MySQL通常不会发出提醒,也不会报错。
有时候只有回滚的时候才会发出一个警告:“某些非事务型的表上的变更不能被回滚”。但大多数情况下,对非事务型表的操作都不会有提示。
InnoDB采用的是两阶段锁定协议。
MySQL也支持LOCK TABLES和UNLOCK TABLES语句,这是在服务器层实现的,和存储引擎无关。
它们有自己的用途,但并不能替代事务处理。
如果应用需要用到事务,还是应该选择事务型存储引擎。