V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
chaleaochexist
V2EX  ›  数据库

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

  •  
  •   chaleaochexist · 2022-08-08 12:59:43 +08:00 · 1845 次点击
    这是一个创建于 836 天前的主题,其中的信息可能已经有所发展或是发生改变。
    请教问题
    我现在有一个需求, 先清空一张表, 然后在同一场表 插入数据若干条

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

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

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

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

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

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

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

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


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

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

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

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

    这些事情自己开两个 session 试一下就知道了,有现成的代码,https://github.com/ept/hermitage
    nothingistrue
        13
    nothingistrue  
       2022-08-09 09:27:59 +08:00   ❤️ 1
    @chaleaochexist #8 不是同时只有一个事务在执行,而是针对一个数据主体同时只有一个事务在执行。如果操作不涉及插入行,那么数据主体通常是已存在的行,这时候只锁行就行了。如果操作涉及插入行,那么数据主体理论上是全表,但实际上可能不是,因为锁表开销太大了,这方面各数据库都有自己的实现。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1052 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 19:08 · PVG 03:08 · LAX 11:08 · JFK 14:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.