文章插图
在同一个事务内,你可以运行多个SQL查询来读取、创建、更新和删除数据 。当两个事务使用相同的数据,麻烦就来了 。经典的例子是从账户A到账户B的汇款 。假设有2个事务:
- 事务1(T1)从账户A取出100美元给账户B
- 事务2(T2)从账户A取出50美元给账户B
- 原子性确保不管 T1 期间发生什么(服务器崩溃、网络中断…),你不能出现账户A 取走了100美元但没有给账户B 的现象(这就是数据不一致状态) 。
- 隔离性确保如果 T1 和 T2 同时发生,最终A将减少150美元,B将得到150美元,而不是其他结果,比如因为 T2 部分抹除了 T1 的行为,A减少150美元而B只得到50美元(这也是不一致状态) 。
- 持久性确保如果 T1 刚刚提交,数据库就发生崩溃,T1 不会消失得无影无踪 。
- 一致性确保钱不会在系统内生成或灭失 。
现代数据库不会使用纯粹的隔离作为默认模式,因为它会带来巨大的性能消耗 。SQL一般定义4个隔离级别:
- 串行化(Serializable,SQLite默认模式):最高级别的隔离 。两个同时发生的事务100%隔离,每个事务有自己的『世界』 。
- 可重复读(Repeatable read,MySQL默认模式):每个事务有自己的『世界』,除了一种情况 。如果一个事务成功执行并且添加了新数据,这些数据对其他正在执行的事务是可见的 。但是如果事务成功修改了一条数据,修改结果对正在运行的事务不可见 。所以,事务之间只是在新数据方面突破了隔离,对已存在的数据仍旧隔离 。
举个例子,如果事务A运行”SELECT count(1) from TABLE_X”,然后事务B在 TABLE_X 加入一条新数据并提交,当事务A再运行一次 count(1)结果不会是一样的 。
这叫幻读(phantom read) 。 - 读取已提交(Read committed,Oracle、PostgreSQL、SQL Server默认模式):可重复读+新的隔离突破 。如果事务A读取了数据D,然后数据D被事务B修改(或删除)并提交,事务A再次读取数据D时数据的变化(或删除)是可见的 。
这叫不可重复读(non-repeatable read) 。 - 读取未提交(Read uncommitted):最低级别的隔离,是读取已提交+新的隔离突破 。如果事务A读取了数据D,然后数据D被事务B修改(但并未提交,事务B仍在运行中),事务A再次读取数据D时,数据修改是可见的 。如果事务B回滚,那么事务A第二次读取的数据D是无意义的,因为那是事务B所做的从未发生的修改(已经回滚了嘛) 。
这叫脏读(dirty read) 。
默认的隔离级别可以由用户/开发者在建立连接时覆盖(只需要增加很简单的一行代码) 。
并发控制
确保隔离性、一致性和原子性的真正问题是对相同数据的写操作(增、更、删):
- 如果所有事务只是读取数据,它们可以同时工作,不会更改另一个事务的行为 。
- 如果(至少)有一个事务在修改其他事务读取的数据,数据库需要找个办法对其它事务隐藏这种修改 。而且,它还需要确保这个修改操作不会被另一个看不到这些数据修改的事务擦除 。
最简单的解决办法是依次执行每个事务(即顺序执行),但这样就完全没有伸缩性了,在一个多处理器/多核服务器上只有一个核心在工作,效率很低 。
理想的办法是,每次一个事务创建或取消时:
- 监控所有事务的所有操作
- 检查是否2个(或更多)事务的部分操作因为读取/修改相同的数据而存在冲突
- 重新编排冲突事务中的操作来减少冲突的部分
- 按照一定的顺序执行冲突的部分(同时非冲突事务仍然在并发运行)
- 考虑事务有可能被取消
推荐阅读
- 如何挑选黑豆
- 不是夫妻合租房犯罪吗
- 医保断缴三个月余额清零?员工可自愿放弃社保?这些谣言别再信了
- 手串颗数大有讲究,千万别戴错了
- “禁止长时间停车”,到底指的是几分钟?交警:最后再说一遍
- 太热了!别再披头散发了,这4款发型够美够清凉
- 没钱还信用卡有什么解决办法
- 信用卡过期卡怎么处理
- 信用卡逾期被注销怎么还钱
- 如何找回抖音私聊记录