众所周知,MySQL
数据库的核心功能就是存储数据,通常是整个业务系统中最重要的一层,可谓是整个系统的“大本营”,因此只要MySQL
存在些许隐患问题,对于整个系统而言都是致命的。那此刻不妨思考一个问题:
MySQL
在接受外部数据写入时,有没有可能会发生问题呢?
有人也许会笑着回答:“那怎么可能啊,MySQL
在写入数据时怎么会存在问题呢”。
的确,MySQL
本身在写入数据时并不会有问题,就算部署MySQL
的机器断电/宕机,其内部也有一套健全的机制确保数据不丢失。但往往风险并不来自于表象,虽然MySQL
写入数据没问题,但结合业务来看就会有一个很大的隐患,此话怎讲呐?先看案例:
-- 从库存表中扣减商品数量
UPDATE `zz_inventory` SET ......;
-- 向订单表、订单详情表中插入订单记录
INSERT INTO `zz_order` VALUES(....);
INSERT INTO `zz_order_info` VALUES(....);
-- 向物流表中插入相应的物流信息
INSERT INTO `zz_logistics` VALUES(....);
上述的伪SQL
中,描述的是一个经典下单业务,先扣库存数量、再增加订单记录、再插入物流信息,按照正常的逻辑来看,上面的SQL
也没有问题。但是请仔细想想!实际的项目中,这三组SQL
是会由客户端(Java
线程)一条条发过来的,假设执行到「增加订单记录」时,Java
程序那边抛出了异常,会出现什么问题呢?
乍一想似乎没问题,但仔细一想:Java线程执行时出现异常会导致线程执行中断。
因为Java
线程中断了,所以线程不会再向数据库发送「增加订单详情记录、插入物流信息」的SQL
,此刻再来想想这个场景,由于增加订单详情和物流信息的SQL
都未发送过来,因此必然也不会执行,但此时库存已经扣了,用户钱也付了,但却没有订单和物流信息,这引发的后果估计老板都能杀个程序员祭天了…
其实上面列举的这个案例,在数据库中被称之为事务问题,接下来一起聊一聊。
什么是事务呢?事务通常是由一个或一组SQL
组成的,组成一个事务的SQL
一般都是一个业务操作,例如前面聊到的下单:「扣库存数量、增加订单详情记录、插入物流信息」,这一组SQL
就可以组成一个事务。
而数据库的事务一般也要求满足
ACID
原则,ACID
是关系型数据库实现事务机制时必须要遵守的原则。
ACID
主要涵盖四条原则,即:
A/Atomicity
:原子性C/Consistency
:一致性I/Isolation
:独立性/隔离性D/Durability
:持久性那这四条原则分别是什么意思呢?接下来一起聊一聊。
原子性这个概念,在MySQL
中原子性的含义也大致相同,指组成一个事务的一组SQL
要么全部执行成功,要么全部执行失败,事务中的一组SQL
会被看成一个不可分割的整体,当成一个操作看待。
好比事务
A
由①、②、③
条SQL
组成,那这一个事务中的三条SQL
必须全部执行成功,只要其中任意一条执行失败,例如②
执行时出现异常了,此时就会导致事务A
中的所有操作全部失败。
一致性也比较好理解,也就是不管事务发生的前后,MySQL
中原本的数据变化都是一致的,也就是DB
中的数据只允许从一个一致性状态变化为另一个一致性状态。这句话似乎听起来有些绕,不太好理解对嘛?简单解释一下就是:一个事务中的所有操作,要么一起改变数据库中的数据,要么都不改变,对于其他事务而言,数据的变化是一致的,上栗子:
假设此时有一个事务
A
,这个事务隶属于一个下单操作,由「⓵扣库存数量、⓶增加订单详情记录、⓷插入物流信息」三这条SQL
操作组成。
一致性的含义是指:在这个事务执行前,数据库中的数据是处于一致性状态的,而SQL
执行完成之后事务提交,数据库中的数据依旧处于一个“一致性”状态,也就是库存数量+订单数量永远是等于最初的库存总数的,比如原本的总库存是10000
个,此时库存剩余8888
个,那也就代表着必须要有1112
条订单数据才行。
这也就是前面说的:“事务发生的前后,
MySQL
中原本的数据变化都是一致的”,这句话的含义,不可能库存减了,但订单没有增加,这样就会导致数据库整体数据出现不一致。
如果出现库存减了,但订单没有增加的情况,就代表着事务执行过程中出现了异常,此时MySQL
就会利用事务回滚机制,将之前减的库存再加回去,确保数据的一致性。
但来思考一个问题,如果事务执行过程中,刚减完库存后,
MySQL
所在的服务器断电了咋整?似乎无法利用事务回滚机制去确保数据一致性了撒?对于这点大可不必担心,因为MySQL
宕机重启后,会通过分析日志的方式恢复数据,确保一致性(对于这点稍后再细聊)。
简单理解原子性和一致性后,再来看看ACID
中的隔离性,在有些地方也称之为独立性,意思就是指多个事务之间都是独立的,相当于每个事务都被装在一个箱子中,每个箱子之间都是隔开的,相互之间并不影响,同样上个栗子:
假设数据库的库存表中,库存数量剩余
8888
个,此时有A、B
两个并发事务,这两个事务都是相同的下单操作,由「⓵扣库存数量、增⓶加订单详情记录、⓷插入物流信息」三这条SQL
操作组成。
此时A、B
两个事务一起执行,同一时刻执行减库存的SQL
,因此这里是并发执行的,那两个事务之间是否会互相影响,导致扣的是同一个库存呢?答案是不会,ACID
原则中的隔离性保障了并发事务的顺序执行,一个未完成事务不会影响另外一个未完成事务。
隔离性在底层是如何实现的呢?基于
MySQL
的锁机制和MVCC
机制做到的(后续《MySQL事务与锁原理篇》再详细去讲)。
相较于之前的原子性、一致性、隔离性来说,持久性是ACID
原则中最容易理解的一条,持久性是指一个事务一旦被提交,它会保持永久性,所更改的数据都会被写入到磁盘做持久化处理,就算MySQL
宕机也不会影响数据改变,因为宕机后也可以通过日志恢复数据。
也就相当于你许下一个诺言之后,那你无论遇到什么情况都会保证做到,就算遇到山水洪灾、地球毁灭、宇宙爆炸…任何情况也好,你都会保证完成你的诺言为止。
刚刚说到的ACID
原则是数据库事务的四个特性,也可以理解为实现事务的基础理论,那接下来一起看看MySQL
所提供的事务机制。在MySQL
默认情况下,一条SQL
会被视为一个单独的事务,同时也无需咱们手动提交,因为默认是开启事务自动提交机制的,如若你想要将多条SQL
组成一个事务执行,那需要显式的通过一些事务指令来实现。
在MySQL
中,提供了一系列事务相关的命令,如下:
start transaction | begin | begin work
:开启一个事务commit
:提交一个事务rollback
:回滚一个事务当需要使用事务时,可以先通过start transaction
命令开启一个事务,如下:
-- 开启一个事务
start transaction;
-- 第一条SQL语句
-- 第二条SQL语句
-- 第三条SQL语句
-- 提交或回滚事务
commit || rollback;
对于上述MySQL
手动开启事务的方式,相信大家都不陌生,但大家有一点应该会存在些许疑惑:事务是基于当前数据库连接而言的,而不是基于表,一个事务