Mybatis一級緩存的坑

Mybatis一級緩存的坑

前序

接着上篇文章插入唯一數據的各種問題。中間過程說Spring的事務,是不是在生命週期的時候程序提交了事務,但是數據庫沒有提交事務。其實不會的,當時寫文章的時候只是根據當時的現象去推測,後來我用代碼測試實際是會提交事務的。Spring是沒有錯的。代碼如下:

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public void update(User user) {
        // 根據username查詢用戶,username普通索引
        // SQL: select * from user where username = #{username} limit 1
        User top = userMapper.findTopByUsername(user);
        if (null == top) {
            System.out.println("lock 之前 = " + Thread.currentThread().getName());
            lock.lock();
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @SneakyThrows
                @Override
                public void afterCompletion(int status) {
                    System.out.println("解鎖 = " + Thread.currentThread().getName());
                    Thread.sleep(30000);
                    lock.unlock();
                    super.afterCompletion(status);
                }
            });
            // SQL: select * from user where username = #{username} limit 1
            top = userMapper.findTopByUsername(user);
            System.out.println("second top = " + top);
            if (null == top) {
                top = new User();
                top.setUsername(user.getUsername())
                        .setNum(user.getNum())
                ;
                userMapper.save(top);
                return;
            }
        }
        userMapper.update(user);
    }

之前說過,併發進入第一個null判斷的線程會串行化執行lock到unlock的代碼,但是表現的是,併發的線程在第二次查詢裏就會讀不到值,整段表現的是不可重複讀的現象。但實際隔離級別是讀提交,不符合原理。於是我就猜測Spring事務提交和真實事務提交可能有差別,還在Spring源碼中找到了一段註釋。後來想想還是親手驗證一下,以防誤人子弟,那就不好了,所以就改成上面代碼。如果我的猜測正確,那在unlock執行之前,數據庫就查不到新插入的這條記錄。實際上在該線程sleep期間,我去數據庫是可以查詢到最新插入的記錄,這說明我的猜測是錯誤,對那段源碼註釋也理解錯了。

到此,問題又沒有思緒了,突然有一天,朋友在討論Mybatis緩存。此時豁然開朗了,在Spring事務中,是會使用Mybatis的一級緩存的。瞬間可以解釋所有的現象了。爲什麼呢?因爲併發進入第一個空判斷的線程,之前執行過一次查詢,所以第二次查詢不會去查詢數據庫,而是會直接緩存讀。因此就像可重複讀一樣,在此出現了。那爲什麼for update查詢又是可以查到的呢?因爲for update查詢Mybatis不會走一級緩存,所以for update查詢是可以讀到上一個線程插入的值。後來看Mybatis的查詢日誌,發現和這個設想一模一樣。

緩存坑總結

後來百度了一下,發現在多線程環境下mybatis的緩存會很多坑,各種莫名其妙的問題出現,因爲它的session線程不安全,看源碼就知道,各種字段變量。

  1. 破壞事務的語義,例子在上面;
  2. 髒數據;

【參考】

mybatis一級緩存讓我憔悴 https://www.cnblogs.com/Yatces/p/12342481.html

MyBatis 一級緩存在分佈式下的坑 https://blog.csdn.net/valada/article/details/104012588

MyBatis一級緩存引起的無窮遞歸 https://www.cnblogs.com/Leo_wl/p/5377121.html

mybatis的一級緩存會不會產生髒數據問題? https://www.zhihu.com/question/53321129

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章