一、先搞懂MariaDB的事务到底是啥
很多人第一次听到“事务”这个词,可能会觉得很复杂,其实换个生活化的例子就懂了:你在银行要把100块钱从自己账户转到朋友账户,银行必须保证两个操作同时成功或者同时失败——也就是你的账户扣了100,朋友的账户必须加100;如果中间出了问题,比如网络断了,那你的账户绝对不能被扣钱,朋友也绝对不能收到钱,这就是事务最核心的要求。
1.1 事务的四个核心特性(ACID)
咱们不用记拗口的英文缩写,用实际场景拆解就好: 原子性:就是刚才说的转账要么全成要么全不成,一步都不能少,也不能多,就像你买奶茶,要么拿到奶茶付了钱,要么啥都没发生; 一致性:转账前后两个账户的总金额必须不变,比如你和朋友的账户总共有1000块,转完之后还是1000,不能出现900+100或者1000+0的情况; 隔离性:当你在转钱的时候,另一个人查你的账户,不能看到你中间改了一半的数字,比如不能看到“你的账户现在是-100(只扣了没加)”,必须要么看到原来的1000,要么转完后的900; 持久性:转账成功之后,就算银行停电、服务器重启,这个转账记录也不会消失,数据会永久存在磁盘里。
1.2 MariaDB里事务的“载体”
不是随便建个表就能用事务的,得看你用的存储引擎——MariaDB里常用的InnoDB是支持事务的,而旧的MyISAM不支持。建表的时候如果指定engine=InnoDB,才会开启事务的能力,比如你建账户表的时候,要这么写:
-- 技术栈:MariaDB 10.5
-- 示例:创建支持事务的账户表
CREATE TABLE accounts (
user_id INT PRIMARY KEY AUTO_INCREMENT,
balance DECIMAL(10,2) COMMENT '账户余额',
user_name VARCHAR(50) COMMENT '用户名'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、事务处理的常见坑
2.1 自动提交的小陷阱
MariaDB默认是“自动提交”模式,每执行一条SQL,就相当于一个独立的事务,执行完就自动提交了。比如你要同时执行两个更新操作,本来想做成一个完整的转账,但因为自动提交,第一条更新执行完就提交了,第二条如果出错,就会出现一半改动的情况。所以必须手动控制事务,例子:
-- 技术栈:MariaDB 10.5
-- 示例:手动管理事务的正确转账步骤
-- 1. 关闭自动提交,让我们手动控制事务的开始和结束
SET autocommit = 0;
-- 2. 正式开启事务(部分版本中SET autocommit=0已隐含开启事务,写START TRANSACTION更稳妥)
START TRANSACTION;
-- 3. 转出账户(user_id=1)减100块
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
-- 4. 转入账户(user_id=2)加100块
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
-- 5. 关键操作:若两条SQL都成功,就提交;若中间出错,执行ROLLBACK
-- 这里假设所有操作都成功,执行COMMIT,数据才会真正写入磁盘
COMMIT;
-- 最后记得把自动提交改回默认状态,避免影响后续单条SQL操作
SET autocommit = 1;
这里要注意,如果你忘了写COMMIT,那事务里的改动不会真正保存,别的会话也看不到,而且会一直占用锁,导致性能下降;如果出错了没写ROLLBACK,也会留下半完成的事务,占着资源。
三、性能优化策略
3.1 合理设置隔离级别,不要追求“最安全”
隔离级别就是控制不同事务之间的可见程度,越高的级别越安全,但性能越低。MariaDB默认用的是“可重复读”,这个级别能解决大部分常见问题,但在高并发场景下,可以改成“读已提交”,减少锁的等待时间。比如在统计订单数量的时候,不需要看到别人没提交的修改,用读已提交就足够,例子:
-- 技术栈:MariaDB 10.5
-- 示例:修改会话的隔离级别为读已提交,提升高并发场景的性能
-- 查看当前会话的隔离级别
SELECT @@transaction_isolation;
-- 修改为读已提交(READ-COMMITTED),适合以查询为主的高并发业务
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
这里要说明,隔离级别从高到低是:串行化→可重复读→读已提交→读未提交,一般业务场景用读已提交或者默认的可重复读就够了,不用用最安全的串行化——那个级别会把所有事务排队,性能极低,只有在特别严格的财务对账场景才用。
3.2 控制事务的大小,别把“无关操作”带进来
很多新手会犯的错误:把查询数据、打印日志这些和“数据修改”无关的操作都放进事务里,比如事务里先查用户的信息,再改账户余额,这会让事务的时间变长,占着锁。正确的做法是:只把必须同时成功或失败的操作放进事务,其他操作(比如查询、计算、日志)都放在事务外面。举个反例:
-- 技术栈:MariaDB 10.5
-- 反例:事务里加了无关的查询,会增加事务时间,降低性能
START TRANSACTION;
-- 查询用户1的信息(无关操作,不该放事务里)
SELECT user_name FROM accounts WHERE user_id=1;
-- 再做余额修改(和查询是独立的,不该放同一个事务)
UPDATE accounts SET balance=balance-100 WHERE user_id=1;
COMMIT;
正确写法是把查询放在事务外面,这样事务的时间就短很多,锁占用的时间也少,性能自然提升。
3.3 用好索引,避免全表锁
InnoDB的事务默认用行锁,只有当无法用索引定位行的时候,才会用表锁——表锁会把整个表锁起来,性能超级差。所以给WHERE条件的字段加索引是必须的,比如刚才的转账操作,要给user_id加索引,这样UPDATE的时候才会用行锁,而不是全表扫。例子:
-- 技术栈:MariaDB 10.5
-- 示例:给账户表的user_id加索引(建表时已加主键,主键会自动生成索引)
-- 若表已存在,补建普通索引的语句
CREATE INDEX idx_user_id ON accounts(user_id);
这里要注意,要是UPDATE的时候没加索引,比如用WHERE balance>500,而且balance没索引,那InnoDB会扫所有行,把所有符合条件的行都锁了,要是数据量很大,整个表都会被锁住,别人根本操作不了。
3.4 批量操作,用事务减少开销
如果要批量插入或者修改数据,不要每条都自动提交,用一个事务包起来,这样能大大减少磁盘IO和锁的开销,比如插入1000条数据,用一个事务提交比每条都提交快几十倍。例子:
-- 技术栈:MariaDB 10.5
-- 示例:批量插入的优化写法,用单个事务提交降低开销
SET autocommit=0;
START TRANSACTION;
-- 批量插入5条用户数据,实际可以插入更多,这里演示结构
INSERT INTO users (name, age, phone) VALUES
('李小明',22,'13800138001'),
('王小红',25,'13800138002'),
('张小刚',28,'13800138003'),
('赵小丽',30,'13800138004'),
('刘小强',32,'13800138005');
-- 提交,整个批量操作一次完成,减少磁盘IO和锁占用
COMMIT;
-- 恢复自动提交,避免影响后续操作
SET autocommit=1;
这里要注意,批量操作也不能一次性插入太多条,比如一次性插10万条,还是会占很多资源,一般建议1000-5000条一组,分多次事务提交,避免一次性锁太多资源。
四、应用场景与利弊分析
4.1 必须用事务的场景
所有“要么全成要么全不成”的业务,比如:
- 转账:A转B钱,必须同时改两个账户;
- 订单支付:用户付钱,要同时减库存、生成订单、扣余额,三个操作必须一起成功,否则就回滚;
- 红包拆分:发红包的时候,要把总金额拆成多个小金额,每个红包都要生成,不能少一个; 这些场景如果不用事务,就会出现数据不一致的问题,比如付了钱但没减库存,用户就会投诉。
4.2 不需要用事务的场景
有些场景对数据一致性要求不高,或者是只读的,比如:
- 日志记录:记录用户的操作日志,哪怕丢了一条也没关系;
- 统计报表:统计当月的订单数量,误差一两条不影响;
- 临时数据测试:建个表测试功能,随便改,不用考虑一致性; 这些场景可以用MyISAM存储引擎,或者降低隔离级别,提升性能。
4.3 技术优缺点总结
优点:保证数据的一致性和完整性,避免脏数据、不可重复读等问题,是数据库的核心保障; 缺点:会占用锁资源,增加数据库的开销,尤其是长事务,会导致其他操作等待,降低系统的吞吐量;
五、注意事项
5.1 避免死锁,锁的顺序要统一
死锁是指两个事务互相占用对方需要的锁,都等对方释放,导致两个事务都无法完成。最常见的例子就是两个账户互相转账: 事务1:先锁A账户,再等B账户; 事务2:先锁B账户,再等A账户; 这样就互相等,导致死锁,MariaDB会自动检测到死锁,回滚其中一个事务,解决方法是:所有事务都按相同的顺序锁,比如先锁user_id小的账户,再锁user_id大的,这样事务1和事务2都会先锁user_id=1的账户,不会循环等待。
5.2 避免长事务,缩短事务时间
长事务就是执行时间超过1秒的事务,比如事务里加了复杂的计算、大量的查询,或者等待用户输入,这些都会占着锁,让其他操作无法进行,比如你在事务里写个循环,等待用户确认,等了10秒,那期间所有涉及到的行都会被锁,别的用户根本操作不了。所以要把复杂的操作移到事务外面,只把数据修改的部分放进事务,让事务的时间尽量短,比如几百毫秒以内最好。
5.3 正确使用ROLLBACK和COMMIT,不要忘写
很多新手会忘写COMMIT,导致数据没保存,或者忘写ROLLBACK,导致半完成的事务,占着资源。还有一种情况:如果事务里出错了,比如某条SQL执行失败,MariaDB不会自动回滚所有操作,最好的做法是在事务里加错误处理,比如用存储过程的话,出错就执行ROLLBACK,正常就COMMIT。
六、文章总结
MariaDB的事务处理机制是保证数据一致性的核心,不用把它想的太复杂,只要记住“要么全成要么全不成”的核心要求,结合实际场景控制事务的大小、隔离级别,用好索引,就能避免大部分问题。性能优化的关键不是一味的追求更快的代码,而是合理的使用事务,减少不必要的开销,比如不要用高隔离级别,不要把无关操作放进事务,给表加合适的索引,这些小细节能让数据库的性能提升很多,同时保证数据的安全。
Comments