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

数据库事务隔离级别问题

  •  
  •   leichnX · 61 天前 · 2200 次点击
    这是一个创建于 61 天前的主题,其中的信息可能已经有所发展或是发生改变。
    可能我这个问题提的有点儿肤浅,大佬们别嘲笑。我有个疑问,可重读级别下,读的数据可能是个假的,是个老版本的,这到底有啥作用。
    第 1 条附言  ·  60 天前
    我再把这个疑问从两个方面细化一下
    1. MySQL 可重读隔离级别下,A 事务在执行过程中,要多次使用某条记录,中途 B 事务已经对这条记录修改了,且提都提上去了。那么此时我这个 A 事务还有必要用旧数据往下走吗,走出来的结果能符合预期吗,或者我在哪些业务场景下比较适合要求这个记录每次读出来是一样的。
    2.第二个方面,MySQL 中,A 事务在执行过程中,要多次使用某条记录,我怕它两次访问查出来结果不一样,反正都是同一条记录,我咋不只查一次,一直就用第一次查出来的。
    31 条回复    2024-03-27 10:10:49 +08:00
    buaasoftdavid
        1
    buaasoftdavid  
       61 天前   ❤️ 2
    可重读级别好处是可以大大提高数据库的并发。读的数据是假(旧)的概率不高,而且即使是假(旧)的,在一些应用下也不会对应用照成影响,在这样的应用下可重读的作用是巨大的。比如你 12306 定火车票的时候经常查询的时候余票有 1 张,但是付款的时候显示没有余票了。这个查询余票的功能就是可重复读,读到了假(旧)的数据也没事。最后下单的时候的隔离级别高就行。查询余票的隔离级别低可以让更多人同时去查询。
    xlzyxxn
        2
    xlzyxxn  
       60 天前
    这就叫事务
    haython
        3
    haython  
       60 天前
    @buaasoftdavid 在 MySQL 中,4 个隔离级别中,可重复读级别性能排名第 3 ,除去”读未提交“和”串行化“这 2 个基本不会使用的级别,可重复读性能最差
    leonshaw
        4
    leonshaw  
       60 天前   ❤️ 1
    可重读并不是读的数据是假的或者老版本的,而是在事务中对同一个数据多次读取结果是一致的。
    waytodelay
        5
    waytodelay  
       60 天前
    可重读提高并发吗? RR 加的锁比 RC 多啊
    @buaasoftdavid
    lichao
        6
    lichao  
       60 天前
    这个要先申明是哪个数据库,例如 MySQL 和 PG 对可重复读的行为就不太一样
    k7262140
        7
    k7262140  
       60 天前
    sigma65535
        8
    sigma65535  
       60 天前
    可重复读有两种读模式,一种快照读,一种当前读。不想读旧数据,用当前读
    abccccabc
        9
    abccccabc  
       60 天前
    面试官最喜欢问这个问题了,那么我有一个问题:这个事务隔离机制,有人改动过 mysql 默认配置的值吗?
    demoshengxw
        10
    demoshengxw  
       60 天前   ❤️ 2
    我以前也不理解可重复读的现实意义是什么,直到我看到一个例子后面就忘不了了 例子如下:假设你在管理一个个人银行账户表。一个表存了账户余额,一个表存了账单明细。到了月底你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。
    buaasoftdavid
        11
    buaasoftdavid  
       60 天前
    2.第二个方面,MySQL 中,A 事务在执行过程中,要多次使用某条记录,我怕它两次访问查出来结果不一样,反正都是同一条记录,我咋不只查一次,一直就用第一次查出来的。
    --------
    比如飞机选座,一个 A 用户先查询座位图,花 10 分钟做决定,选了一个位置 X ,提交。在这 10 分钟内,B 用户查询座位图,选 X ,提交。这个时候如果 A 只查一次然后提交(也就是不可重复读),那 A 和 B 会选同一个座位(冲突)。但实际上在可重复读级别下会再读一次,然后 A 事务提交失败的。
    cnsdytedison
        12
    cnsdytedison  
       60 天前 via Android
    @abccccabc 有,不过原因是从 oracle 迁移到 mysql 有一些本来可以的写法会导致死锁。为了减少改动所以主要是通过改这个来解决也最。
    waytodelay
        13
    waytodelay  
       60 天前
    @demoshengxw 我的理解也是对账之类的才走这个 RR 。其他的业务为了并发还是用 RC
    buaasoftdavid
        14
    buaasoftdavid  
       60 天前
    @haython 大大提高并发当然是相对于可窜行化级别来说的。
    leichnX
        15
    leichnX  
    OP
       60 天前
    @demoshengxw 认真读了你的例子,我感觉在这个例子中也没有什么数据需要获取两次呀,中途插入了啥也不会有影响吧。把获取对账需要的数据当作事务 A, 它执行两个操作,1 获取用户两个月的余额快照记录,2 获取用户本月账单明细

    select *
    from account where userId = 10 and time > 上个月;

    select *
    from order where userId = 0 and time >= 这个月 1 号;
    watzds
        16
    watzds  
       60 天前
    隔离就是看不到其他事务的操作,如果两次读取结果不一样,肯定要么其他事务修改了数据,要么是自己改的(这种情况是可见的),这个实际影响是各种各样的,比如



    A() {
    是否发通知 = B(); //检查余额,判断是否欠费

    C(是否发通知); //发送欠费通知邮件,邮件内容中包含余额
    }

    A 方法整个是一个事务,B C 方法内部都有读数据库操作,如果重复读结果不一样,发送的通知内容中余额可能是 100 非欠费
    leonshaw
        17
    leonshaw  
       60 天前
    > 那么此时我这个 A 事务还有必要用旧数据往下走吗,走出来的结果能符合预期吗

    这种问题要靠锁(乐观锁)解决,更低的隔离级别并不能保证最后一次读之后到提交前不会有其它改动。
    watzds
        18
    watzds  
       60 天前   ❤️ 1
    @watzds #16 当然也可以只读一次,后续复用,但是这样对代码有要求,可能根据不同情况,复用的内容也不一样,那多麻烦,一不小心就出错,那还叫事务吗

    另外

    A() {
    是否发通知 = B(); //检查余额,判断是否欠费

    //这时刚好其他事务充值成功

    C(是否发通知,余额); //发送欠费通知邮件,邮件内容中包含余额和最近 N 次充值记录
    }

    就算余额被你复用了,但是充值记录读取到最新的,导致看起来明明刚充值,还是欠费
    me1onsoda
        19
    me1onsoda  
       60 天前
    我一直不理解可重读这个场景有什么存在的必要。
    为什么多次查询同一条记录,想多次使用就存起来呗
    git00ll
        20
    git00ll  
       60 天前
    @leichnX 你需要查询 account 和 order 两张表,查询 account 后你会获取到一个余额,然后再查询 order 时你肯定希望此时查询的明细就是查询 account 那一瞬间对应的 order ,不然如果 order 被修改了,你的 order 和 account 会对不齐。


    这里的可重复读不止针对同一条数据的可重复读,如你所说业务上也许不会在事物内针对同一条数据查两遍, 但是跨表的可重复读是很有用的。
    leichnX
        21
    leichnX  
    OP
       60 天前
    @watzds 这个例子很赞,确实有这么一些场景,套个可重读的隔离级别上去就可以少考虑不少事儿了,减小写代码的复杂度。
    leichnX
        22
    leichnX  
    OP
       60 天前
    @git00ll 这两次查询查两张表,可重读会帮忙把它们时间间隙卡的很短吗,以前还真没了解过这个。它会和 Java 多线程一样使得两个查询之间线程不会中断吗?
    git00ll
        23
    git00ll  
       60 天前
    @leichnX 不是时间间隙卡的很短。 当事物内进行第一条 sql 查询的时候就会生成一个快照版本号,并且对所有的表生效。相当于给所有的表生成了快照。 所以能够保证后续的查询(无论哪张表)都是那一瞬间的快照值,因此能够保证读一致性。
    leichnX
        24
    leichnX  
    OP
       60 天前
    @git00ll 仔细想想感觉不对啊,比如手动执行 sql 的时候,先启动事务,在执行第一条查询查表 1 ,等五秒再执行第二条查询查表 2 ,这中间别人要是插了数据进表 2 去,肯定是会查出来的,什么隔离级别也会查到插进去的新数据。
    git00ll
        25
    git00ll  
       60 天前   ❤️ 1
    @leichnX 针对数据插入就是 “幻读” 问题了,“可重复读” 指的是数据更新。

    并且 Mysql RR 级别是能解决快照读的幻读问题的, 如你描述两次查询中间被插入符合条件的数据并不会被查出来。
    leichnX
        26
    leichnX  
    OP
       60 天前
    @git00ll 又测了一下,你说的是对的,我开始拿 SERIALIZABLE 测了一遍,结果和我说的一样,我想当然的以为可重读也是会读到新插数据或改的数据。然而可重读读到的还真是旧数据,这很牛逼,很重要。

    事务 A
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    BEGIN

    SELECT *
    FROM 表 1 ;

    转过去执行事务 B

    SELECT *
    FROM 表 2 ; //发现读到的是旧数据
    commit;

    事务 B
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    BEGIN
    UPDATE 表 2
    SET Name='22'
    leichnX
        27
    leichnX  
    OP
       60 天前
    @watzds 明白了,和 20 说的是一回事,在需要获取跨表一致性数据的时候,这个可重读级别很重要。
    luoqeng
        28
    luoqeng  
       60 天前
    vczyh
        29
    vczyh  
       60 天前   ❤️ 1
    可复重读隔离级别内在含义是可以保证一致性,当你第一次执行 select 语句的时候就决定了接下来你能读到什么数据,这些数据跟其他事务是否修改没有关系。
    orzwalker111
        30
    orzwalker111  
       60 天前   ❤️ 1
    mvcc 保证了 rr 下可重复读,mvcc 是 undo log 链和 readview 视图实现的,这个视图是在事务开启后执行第一个快照读时,基于 [整个库] 生成的快照。也就是说这个快照中的“账单、明细这两个表数据已经不会变了“
    10 楼所举的例子很合适,不要局限于”单张表在一个事务中的多次查询“这种场景
    leichnX
        31
    leichnX  
    OP
       59 天前
    @orzwalker111 不要局限于”单张表在一个事务中的多次查询“这种场景 这句话说的太对了,然而从学校到社会,可重读这个问题大多数人都是拿单张表说事儿,太容易让人困惑了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2543 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 05:53 · PVG 13:53 · LAX 22:53 · JFK 01:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.