当前位置:首页 > 问答 > 正文

MySQL里头事务那些事儿,搞清楚了数据库才不乱套

主要基于对MySQL官方文档、常见数据库教程以及技术社区如Stack Overflow中相关讨论的普遍理解)

咱们今天就来聊聊MySQL里一个顶顶重要的概念——事务,你要是想把数据库玩得转,不让数据乱成一锅粥,那事务这块儿必须得整明白,它就像是数据库世界的“交通警察”和“保险柜”,确保数据的安全和有序。

事务是个啥?为啥需要它?

想象一个场景:你去银行转账,要给你朋友转500块钱,这个操作在数据库里可不是一步完成的,它至少需要两步:

  1. 从你的账户里扣掉500块。
  2. 往你朋友的账户里加上500块。

现在问题来了,如果刚执行完第一步,你的账户钱少了,突然数据库服务器断电了,第二步没来得及执行,那你朋友没收到钱,你的钱却没了,这岂不是乱套了?

事务就是为了解决这种“半拉子”操作而生的,你可以把上面转账的两个步骤打包成一个整体,这个整体就是一个“事务”,事务的核心要求是:要么两个步骤都成功,要么两个步骤都失败回滚,绝对不会出现只完成一步的尴尬情况,用行话讲,这叫“原子性”,但咱们就理解成“打包执行,同生共死”就行了。

事务的四大护法(ACID)

事务之所以能担此大任,是靠四个核心特性撑腰的,合起来叫ACID,别被缩写吓到,咱们用大白话解释:

  1. 原子性(Atomicity):就是刚才说的“打包”概念,事务里的所有操作是一个不可分割的整体,要么全部成功,数据库状态就更新;要么中间出了任何差错,就全部撤销,数据库回到事务开始前的样子,就像什么都没发生过。

    MySQL里头事务那些事儿,搞清楚了数据库才不乱套

  2. 一致性(Consistency):这是事务的终极目标,意思是事务执行前后,数据库都必须处于一种“合理”的状态,比如转账前后,你和朋友的总金额应该是不变的,不能你扣了钱,他没收到的钱就凭空消失了,这就不一致了,事务靠着原子性、隔离性和持久性来共同保证最终的一致性。

  3. 隔离性(Isolation):当多个事务同时操作数据库时,隔离性确保它们“各干各的”,互相不干扰,比方说,你正在转账(事务A),同时你老婆在查账(事务B),隔离性要保证,在你转账这个“打包”操作彻底完成(要么成功要么失败)之前,你老婆查到的余额应该是转账前的数额,而不是中间那个你钱已扣、对方钱未加的混乱数据,这个特性后面会细说,因为它有不同的级别。

  4. 持久性(Durability):这个最好理解,一旦事务成功提交,它对数据库的修改就是永久的了,即使之后系统崩溃、断电,重启后数据依然是更新后的状态,这就好比你用钢笔在纸上写字,写上去就擦不掉了(不考虑特殊手段),MySQL是通过一种叫“重做日志”(redo log)的机制来实现的,先把操作记下来,再慢慢写回磁盘,保证数据不丢。

在MySQL里怎么玩转事务?

MySQL中,默认情况下,你每执行一条SQL语句,比如UPDATEINSERT,它自己就被当作一个事务自动提交了,这适合简单操作,但对付不了我们开头说的转账这种复杂情况。

MySQL里头事务那些事儿,搞清楚了数据库才不乱套

这时候就需要手动控制事务了,主要用三个命令:

  • START TRANSACTION;BEGIN;:这标志着“嗨,我要开始一个事务了,接下来的几条SQL语句都是一个包里的”。
  • COMMIT;:如果所有操作都检查无误,你就发这个命令,意思是“搞定!把我这个包里的所有操作正式生效,永久保存”。
  • ROLLBACK;:如果中途发现不对(比如你转错人了,或者账户余额不足),你就发这个命令,意思是“算了算了,刚才这个包里的操作全部作废,恢复到打包之前的状态”。

还是用转账的例子,安全的做法应该是这样的:

START TRANSACTION; -- 开始事务
UPDATE accounts SET balance = balance - 500 WHERE user_id = '你';
UPDATE accounts SET balance = balance + 500 WHERE user_id = '朋友';
-- 到这里,可以加一些检查,比如检查你的余额是否足够、朋友账户是否存在等
-- 如果一切正常
COMMIT; -- 提交事务,转账真正完成
-- 如果发现有问题
-- ROLLBACK; -- 回滚事务,所有修改取消

事务的隔离级别和“坑”

前面提到的隔离性,在实际应用中为了平衡性能和准确性,是有不同等级的,MySQL提供了四种隔离级别,级别越低,并发性能越好,但可能遇到问题;级别越高,数据越准,但性能可能下降,常见的“坑”有:

  • 脏读:事务A读到了事务B还未提交的修改,如果事后B回滚了,那A读到的就是无效的“脏”数据,这就像你看到同事在写报告,你以为他写完了,结果他只是打了个草稿后来又全删了。
  • 不可重复读:在同一个事务A里,两次读取同一行数据,结果不一样,这是因为在两次读取之间,另一个事务B提交了修改(比如改了某个字段的值),这让你在同一个事务里对自己读到的数据都产生了怀疑。
  • 幻读:事务A在查询一批符合条件的数据时,另一个事务B提交了新的操作,插入或删除了一些行,导致A两次查询得到的行数不一样,就像你第一次数会议室有10个人,中间进来俩人,你再数就变成12个了,像变魔术一样“幻”出来了。

MySQL的四种隔离级别(读未提交、读已提交、可重复读、串行化)就是用来解决这些问题的,设置不同的级别,就能避免不同的问题。“可重复读”是MySQL InnoDB引擎的默认级别,它能避免脏读和不可重复读,并通过一种叫“间隙锁”的机制在很大程度上避免幻读。

总结一下

事务是数据库保证数据准确性和业务逻辑正确的基石,你把一系列操作用BEGINCOMMIT包起来,形成一个“同生共死”的单元,依靠ACID特性,尤其是在并发环境下选择合适的隔离级别,就能确保即使很多人同时操作,数据库也不会“乱套”,下次当你需要执行多个关联的数据库操作时,别忘了请出“事务”这个法宝。