大家好,我是mep。今天一起來探討一下Redis緩存的問題,SpringBoot如何集成Redis網上文章很多,基本都是介紹如何配置redisTemplate,如何調用,本文就不過多介紹了。這次我們研究的是:Redis的事務。 首先拋出一個問題,Redis支持事務嗎? 答案肯定是支持,不然也不需要我 ...
大家好,我是mep。今天一起來探討一下Redis緩存的問題,SpringBoot如何集成Redis網上文章很多,基本都是介紹如何配置redisTemplate,如何調用,本文就不過多介紹了。這次我們研究的是:Redis的事務。
首先拋出一個問題,Redis支持事務嗎?
答案肯定是支持,不然也不需要我們在這裡探討了。
然後你拿到關鍵詞"Redis 事務"去搜索引擎搜索一下,得到了這樣的答案:
Redis支持事務,But!Redis的事務不保證原子性,事務不會回滾。例如:我在Redis中提交了一個事務,包含3條命令,其中第2條命令報錯了,並不會導致第一條命令的回滾,也不會阻止第三條命令的執行。
可是,真的是這樣嗎?你試過嗎?哈哈,知道你懶得試,我來幫你們試試看嘍!
先看一個我自己測試的例子,以下例子中RedisTemplate都開啟了事務支持,否則測試沒有意義,我的RedisConfiguration代碼如下:
@Configuration public class RedisConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setEnableTransactionSupport(true); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } }
例1,使用@Transactional註解,方法執行過程中報錯,代碼如下:
@Transactional public void testRedisTransaction() { employeeMapper.updateByPrimaryKey(Employee.builder() .id(4L) .name("uuuuu") .gender(Gender.MALE) .build()); redisTemplate.opsForValue().set("111", "111"); int i = 1 / 0; redisTemplate.opsForValue().set("222", "111"); }
執行前Redis緩存情況:
執行以上方法後,肯定會報錯:
java.lang.ArithmeticException: / by zero
猜猜執行完後資料庫和Redis中數據操作是什麼樣的?
1.資料庫會回滾,即update無效,這個並不意外,沒啥可說的
2.執行後Redis緩存情況:
空的?不是說Redis的事務不支持回滾嗎?為什麼key修改卻無效了呢?
確實,因為Redis根本沒有回滾,它的事務壓根就沒有提交!!!
這就是Redis的事務和關係型資料庫不一樣的地方,資料庫一個事務中如果某一條SQL報錯或方法中有RuntimeException(@Transactional預設)拋出的話,事務會回滾。對於Redis的事務來說,如果方法中拋RuntimeException的話,事務壓根不提交,被DISCARD之後,自然不會執行。
如果你看到這裡了,說明你一開始就質疑最上面搜索到的結果,連查到的知識都會質疑和驗證,為什麼要相信我上面說的事務壓根就沒有提交的結論呢?
帶著疑問,我們繼續驗證,先上代碼:
@Transactional public void testRedisTransaction() { employeeMapper.updateByPrimaryKey(Employee.builder() .id(4L) .name("uuuuu") .gender(Gender.MALE) .build()); System.out.println(1234); redisTemplate.opsForValue().set("111", "a"); redisTemplate.opsForValue().set("222", "a"); redisTemplate.exec(); int i = 1 / 0; }
這次主動在報異常前提交了Redis事務,結果如下:
到這裡,我們得到結論是這樣的:
Redis事務不能回滾,方法報異常時事務並沒有回滾,之所以數據沒有被寫入到Redis,是因為事務被DISCARD了
根據我們查到的內容,還需要驗證Redis的事務不能保證原子性,繼續上示例:
例2,使用@Transactional註解,在Redis事務中報錯,代碼如下:
@Transactional public void testRedisTransactionOnly() { redisTemplate.opsForValue().set("333", "a"); redisTemplate.opsForHash().put("333", "a", 111); }
正常來說,應該會報WRONGTYPE Operation的錯誤,不過,執行結果是這樣的:
甚至,連個錯誤都沒有報!
是代碼的問題嗎?還是因為Redis的事務忽略了異常的命令,只執行了正常的命令?
繼續測試,清空Redis,去掉@Transactional註解:
// @Transactional public void testRedisTransactionOnly() { redisTemplate.opsForValue().set("333", "a"); redisTemplate.opsForHash().put("333", "a", 111); }
執行結果:
可見代碼沒有問題,確實會報錯,只是提交到一個事務中,它不保證原子性,只執行了可執行的命令,即使後續的命令報錯,也不會回滾,而且不會報錯。
至此,Redis事務相關的驗證已結束。
結論就是我們開始搜索到的結果:
Redis支持事務,But!Redis的事務不保證原子性,事務不會回滾,提交後會執行可正常執行的命令,忽略報錯的命令。
最後,來自Redis官網的一句話佐證我們的結論, 附出處:Transactions | Redis