数据库事务 原子性和隔离性的疑问

2022-08-08 12:59:43 +08:00
 chaleaochexist
请教问题
我现在有一个需求, 先清空一张表, 然后在同一场表 插入数据若干条

如果我用事务约束. 是否会有某个时刻, 读了一张空表?

事务的原子性是否指: "执行若干条 sql, 是原子的?"

在这里事务的隔离性如何体现? 可重复读 好像解决不了这个问题?
1709 次点击
所在节点    数据库
14 条回复
fkdog
2022-08-08 13:14:26 +08:00
RC 级别,一个事务里每次 select 可以获取到其他事务已经提交的数据。
RR 级别,一个事务里每次 select 获取的数据都是快照,可以重复读。
update 、delete 、select .... for update 里 where 后边的查询都是取得当前读,也就是最新的数据。

我觉得你的困惑是,RC 级别下,可以读取其他事务已经提交的数据是不是违反了数据库的隔离性。
其实数据库的隔离性不是 100%完全隔离的,规范里定了 4 中隔离级别,根据自己的业务需求和使用场景来选择。
实际上大部分的需求都是可以通过乐观锁、悲观锁等手段来实现数据隔离。
tairan2006
2022-08-08 13:27:57 +08:00
我觉得这里的问题其实是,你清空 db 用的是`delete from`还是`truncate`语句,后者在某些 db 里是不能放在事务里的,它会导致事务立刻提交。

如果确认可以放在事务里,那么另外一个读取的事务是不可能读到空表的,无论是 RR 还是 RC 。
7911364440
2022-08-08 13:47:23 +08:00
delete 语句会加行锁的,如果是 delete from t 会对所有行加锁,这个时候读操作应该会被阻塞吧,所以应该不会读到空表。
nothingistrue
2022-08-08 13:54:21 +08:00
原子性是针对当前事务的多个语句的,要么全部执行要么全部不执行,对于当前事务的多个语句执行过程中,是否有其他事务也在执行,它不管,即使是执行的同一条数据。隔离性是针对修改同一个地方数据的不同事务的,不同的隔离级别有不同的表现,最高的序列隔离级别,能保证同一条数据当前只能被最多一个事务读写。

当你是查询和修改已存在数据的时候,可重复读级别就能保证不发生并发不一致问题。但如果是查询和新增数据(比如值为 max + 1 的列)的时候,即使是最高级别的序列隔离性,也不能保证绝不发生并发不一致,因为这时候要想并发一致就得锁表,但为了性能基本上不会物理锁表只能是变通锁表,这个变通锁表就容易漏掉一些场景。

对于你的需求,事务是解决不了并发不一致的。既然你要清空表了,那么最简单的实现就是手动锁表。
yjhatfdu2
2022-08-08 14:21:43 +08:00
如果用的是 truncate ,那么有可能读到空表,因为 truncate 一般不支持事务。
如果使用的是 read uncommitted ( mysql 下是这样,pg 下不会),也可能是读到空表
其他情况不应该读到空表
CRVV
2022-08-08 14:41:49 +08:00
> 事务的原子性是否指: "执行若干条 sql, 是原子的?"

这个问题问得,原子性是不是指它是原子的。等于没问。
原子性是指执行若干条 SQL 只能都成功或者都不成功。


> 如果我用事务约束. 是否会有某个时刻, 读了一张空表?

如果 START TRANSACTION; DELETE FROM table; INSERT INTO table VALUES..; COMMIT;
那么空表的状态没有 commit. 如果你用的是 Read uncommitted ,就有可能读到,否则不会。
chaleaochexist
2022-08-08 14:51:40 +08:00
@CRVV 你说的和 4 楼有点不一致.

代码里的原子是指不会被中断.

sql 在一个事务中, 插入五条数据. 有没有可能某个瞬间实际插入了四条数据? 幻读是不是就是这个意思?
chaleaochexist
2022-08-08 14:52:54 +08:00
@nothingistrue 谢谢回复.学到很多.
序列隔离级别 难道不是锁表吗? 事务按照顺序执行.也就是说同时只有一个事务在执行.
exonuclease
2022-08-08 15:27:02 +08:00
@chaleaochexist 在 Read Committed 级别或者以上是不可能的 插入五条数据还没提交以前对别的事务不可见
yjhatfdu2
2022-08-08 15:32:59 +08:00
@chaleaochexist pg 下,序列化隔离等级,也不会锁表,依然是读写无冲突,但是会带来额外开销,降低性能。其他常用数据库不支持序列化隔离等级
yjhatfdu2
2022-08-08 15:34:37 +08:00
建议把 pg 的隔离等级文档看一下,就比较清楚了 http://www.postgres.cn/docs/12/transaction-iso.html
CRVV
2022-08-08 16:22:10 +08:00
@chaleaochexist

这些事情自己开两个 session 试一下就知道了,有现成的代码,https://github.com/ept/hermitage
nothingistrue
2022-08-09 09:27:59 +08:00
@chaleaochexist #8 不是同时只有一个事务在执行,而是针对一个数据主体同时只有一个事务在执行。如果操作不涉及插入行,那么数据主体通常是已存在的行,这时候只锁行就行了。如果操作涉及插入行,那么数据主体理论上是全表,但实际上可能不是,因为锁表开销太大了,这方面各数据库都有自己的实现。
qingtengmuniao
2022-08-11 15:04:45 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/871401

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX