目录
前言
假设一个场景,对象今天突然发消息,说想要买新出的iPhone 16 PM,让我给他转8k,虽然我的余额只有8053.24,但是本着对他的爱掏出了手机,准备给他转米。当我向对象发起转账后,服务器那边的数据库刚刚执行完我的余额-8000的操作,还没来得及执行对象余额+8000的操作时,服务器挂掉了!于是8k就这样平白无故的消失了,还被对象数落了一句“没有实力”。
为了避免上述悲剧的发生,我们就需要使用数据库的事务来控制,保证以上两步操作要么全部执行成功,要么全部执行失败。
一、数据库事务的使用
- 开启事务:start transaction;
- 执行SQL语句
- 回滚或提交:rollback/commit;
start transaction;
-- 本人账户减少8000
update accout set money=money-8000 where name = '小刘要进步';
-- 对象账户增加8000
update accout set money=money+8000 where name = '宝宝';
commit;
二、事务的特性
原子性、一致性、持久性、隔离性。
1、原子性
通过回滚的方式,保证这一系列操作,都能执行正确,或者恢复如初。(一气呵成)
2、一致性
是指在一个事件开始时和结束时,数据状态是一致的。
3、持久性
事务做出的修改,都是在硬盘上持久保存的,重启服务器数据依然存在,事件的操作依然有效。
4、隔离性
4.1 并发执行事务可能遇到的问题
4.1.1 脏读问题
问题描述:事务A在写数据的过程中,另一个事务B读取了同一个数据,接下来事务A有修改了这个数据,导致事务B读到的是一个脏数据(无效数据/过时数据)。
解决方法:针对写操作加锁,等事务A写完数据后,事务B才能读数据。
影响:并发性降低-->效率降低,隔离性提高-->数据准确性提高。
4.1.2 不可重复读
问题描述:此时已经约定了写加锁,但是在事务B的两次读操作之间,事务A修改了数据,导致事务B两次读到的数据不一致。在并发执行事务过程中,如果某事务内部多次读取同一数据的时候,出现不同的情况,这就叫做不可重复读。
解决方法:针对读操作加锁。
影响:并发性进一步降低-->效率进一步降低,隔离性进一步提高-->数据准确性进一步提高。
4.1.3 幻读
问题描述:已经约定了写加锁和读加锁,事务B在读A文件时,虽然事务A不能修改A文件,但是事件A可以修改B文件。一个事务的执行过程中,两次的读操作,数据内容虽然没有变,但是结果集变了,这种称为幻读。(这种情况是不是问题,要看具体实际场景)
解决方法:引入串行化的方法,保持绝对的串行执行事务。
影响:从根本上解决了并发中涉及到的各个问题(釜底抽薪),此时的并发程度最低(没有并发),隔离性最高,运行效率最低,数据最准确。
4.2 隔离级别
为了解决上述的三个问题,MySQL提供了“隔离级别”的概念,可以直接在MySQL的配置文件中修改数据库的隔离级别,以应对不同需求场景的不同要求。
4.2.1 read uncommitted(读未提交)
未解决问题,并发程度最高,运行效率最快,隔离性最低,数据准确性最低。
4.2.2 read committed(读已提交)
引入了写加锁,解决了脏读问题。并发性降低了,运行效率变慢,隔离性提高了,数据准确性提高。
4.2.3 repeatable read(可重复读)
引入了写加锁和读加锁,解决了脏读问题和不可重复读。并发性进一步降低,运行效率进一步变慢,隔离性进一步提高,数据准确性进一步提高。
4.2.4 serializable(串行化)
严格按照串行的方式一个一个的执行事务,解决了所有并发执行可能面临的问题。并发性最低,运行效率最慢,隔离性最高,数据准确性最高。