高性能MySQL-事务

高性能MySQL-事务

1.概念

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。

一个事务内的语句,要么全部执行成功,要么全部执行失败。

2.示例

银行应用是解释事务必要性的一个经典例子。

假设一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户的支票账户转移200元到他的储蓄账户,那么至少需要下面三个步骤:

  1. 检查支票账户的余额大于200元。
  2. 从支票账户余额中减去200元。
  3. 在储蓄账户余额中增加200元。

上述三个步骤必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。

3.操作

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回滚事务,这个命令可以撤销事务中所有的修改。

4.特征

  1. 原子性(atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部执行成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
  2. 一致性(consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。在前面的例子中,确保了一致性,即使在执行第三、第四条语句之间系统崩溃了,支票账户也不会损失200元,因为出现错误后,事务不会提交,所以事务中所作的修改也不会保存到数据库中。
  3. 隔离性(isolation):通常来说,一个事务所作的修改在最终提交之前,对其他事务是不可见的。之所以说是“通常来说”,是因为在有的隔离级别下是可见的。
  4. 持久性(durability):一旦事务提交,则其所做的修改就会永久的保存到数据库中。

事务的ACID特性可以确保银行不会弄丢你的钱。

5.隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,在哪些事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

  1. READ UNCOMMITTED(未提交读):在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,性能不会比其他级别好太多,但却缺乏其他级别的很多好处,一般不建议使用。
  2. READ COMMITTED(提交读):这个级别满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所作的修改。也就是说,一个事物从开始直到提交之前,所做的修改对其他事务都是不可见的。这个级别有时候也叫作不可重复读,因为执行两次同样的查询,可能会得到不一样的结果。
  3. REPEATABLE READ(可重复读):这个级别解决了脏读的问题。该级别保证了再同一个事务中多次读取同样的记录的结果是一致的。但是理论上,这个级别还是无法解决另一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。这个级别是MySQL默认的事务隔离级别。
  4. SERIALZABLE(可串行化):这个级别是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,这个级别会在读取的每行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少使用这个级别,只有再非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

5.1设置隔离级别

MySQL可以通过执行 SET TRANSACTION ISOLATION LEVEL 命令来设置隔离级别。

新的隔离级别会在下一个事务开始的时候生效

可以在配置文件中设置整个数据库的隔离级别,也可以只改变单签会话的隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

MySQL能够识别所有的4个ANSI隔离级别,InnoDB引擎也支持所有的隔离级别。

6.死锁

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。

当多个事务试图以不同的顺序锁定资源时、多个事务同时锁定同一个资源时,都会产生死锁。

例如下面两个事务同时执行:

#事务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语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外部因素介入才可能解除死锁。

6.1解决方案

  1. 数据库系统实现了各种死锁检测和死锁超时机制。越是复杂系统,比如InnoDB存储引擎,越是能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。
  2. 当查询时间达到锁等待超时的设定后放弃锁请求,这种方式通常来说不太好。

InnoDB目前处理死锁的方法是:将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

锁的行为和顺序是和存储引擎相关的。

6.2产生的原因

  1. 有些是因为真正的数据冲突,这种情况通常很难避免
  2. 有些则完全是由于存储引擎的实现方式导致的

死锁发生以后,只有部分或全部回滚其中的一个事务,才能打破死锁。

对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。

大多数情况下只需要重新执行因死锁回滚的事务即可。

7.事务日志

事务日志可以帮助提高事务的效率。

使用事务日志,存储引擎在修改表的数据时字需要修改其内存拷贝,再把该修改行为持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。

事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。

事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回磁盘。

目前大多数存储引擎都是这样实现的,通常称之为预写式日志,修改数据需要写两次磁盘。

如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。

具体的恢复方式视存储引擎而定。

8.MySQL中的事务

8.1自动提交(AUTOCOMMIT)

MySQL默认采用自动提交模式。

如果不是显式的开始一个事务,则每个查询都被当做一个事务执行提交操作。

在当前连接中,可以通过设置AUTOCOMMIT变量来启用或者禁用自动提交模式:

SHOW VARIABLES LIKE 'AUTOCOMMIT';

高性能MySQL-事务_第1张图片

#1或者ON表示启用,0或者OFF表示禁用
SET AUTOCOMMIT = 0;

高性能MySQL-事务_第2张图片

当AUTOCOMMIT=0时,所有的查询都是在一个事务中,知道显示地执行COMMIT提交或者ROLLBACK回滚,该事物结束,同时又开始了另一个新事务。

修改AUTOCOMMIT对非事务型的表,不会有任何影响。对于这类表来说,没有COMMIT或者ROLLBACK的概念,也可以说相当于一直处于AUTOCOMMIT启用模式。

**有一些命令在执行之前会强制执行COMMIT提交当前的活动事务。**比如ALTER TABLE,LOCK TABLES等。

8.2事务中混合使用存储引擎

MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。

所以在同一个事务中,使用多种存储引擎是不可靠的。

如果在事务中混合使用了事务型和非事务型的表,在正常提交的情况下不会有什么问题。

如果改事务需要回滚,非事务型的表上的变更就无法撤销,这会导致数据库处于不一致的状态,这种情况很难修复,事务的最终结果将无法确定,所以为每张表选中合适的存储引擎非常重要。

在非事务型的表上执行事务相关操作的时候,MySQL通常不会发出提醒,也不会报错。

有时候只有回滚的时候才会发出一个警告:“某些非事务型的表上的变更不能被回滚”。但大多数情况下,对非事务型表的操作都不会有提示。

8.3隐式和显式锁定

InnoDB采用的是两阶段锁定协议。

  1. **隐式锁定:**在事务执行过程中,随时都可以执行锁定,锁只有再执行COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放。InnoDB会根据隔离级别在需要的时候自动加锁。
  2. **显示锁定:**使用命令 SELECT … LOCK IN SHARE MODE 和 SELECT … FOR UPDATE进行显示锁定,这些语句不属于SQL规范。

MySQL也支持LOCK TABLESUNLOCK TABLES语句,这是在服务器层实现的,和存储引擎无关。

它们有自己的用途,但并不能替代事务处理。

如果应用需要用到事务,还是应该选择事务型存储引擎。

你可能感兴趣的:(MySQL)