高并發(fā)環(huán)境下安全修改同一行數(shù)據(jù)庫數(shù)據(jù)的策略分享
隨著互聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,越來越多的應(yīng)用需要在高并發(fā)環(huán)境中運行,尤其是在處理大量用戶請求時,數(shù)據(jù)庫的并發(fā)控制成為了業(yè)務(wù)的關(guān)鍵。本文將介紹如何在高并發(fā)情況下,安全地修改數(shù)據(jù)庫中的同一行數(shù)據(jù)。
1. 理解并發(fā)問題
并發(fā)操作的問題主要源于多個事務(wù)在同一時間嘗試訪問或修改同一行數(shù)據(jù)。這可能導(dǎo)致數(shù)據(jù)不一致,或者在極端情況下,導(dǎo)致數(shù)據(jù)丟失。為了防止這種情況,我們需要一種機制來確保在一個時間點,只有一個事務(wù)能修改特定的數(shù)據(jù)行。
2. 悲觀鎖
悲觀鎖是一種常見的并發(fā)控制技術(shù),適用于數(shù)據(jù)競爭激烈的場景。當(dāng)一個事務(wù)嘗試讀取或修改數(shù)據(jù)時,悲觀鎖會假設(shè)其他事務(wù)可能也會嘗試修改該數(shù)據(jù),因此會在數(shù)據(jù)上加鎖。其它事務(wù)在此期間將不能對該數(shù)據(jù)進(jìn)行修改,直到鎖被釋放。這種鎖機制主要通過數(shù)據(jù)庫提供的鎖機制實現(xiàn),如行鎖、表鎖等。
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = dataSource.getConnection(); //關(guān)閉自動提交 conn.setAutoCommit(false); //SELECT ... FOR UPDATE是悲觀鎖的典型應(yīng)用,此時會鎖住被選中的行,其他事務(wù)無法更新這些行 String sql = "SELECT * FROM product WHERE id = ? FOR UPDATE"; ps = conn.prepareStatement(sql); ps.setInt(1, 1); rs = ps.executeQuery(); if (rs.next()) { //獲取當(dāng)前庫存數(shù)量 int count = rs.getInt("count"); if (count > 0) { String updateSql = "UPDATE product SET count = count - 1 WHERE id = ?"; ps = conn.prepareStatement(updateSql); ps.setInt(1, 1); ps.executeUpdate(); conn.commit(); System.out.println("扣減成功,剩余庫存:" + (count - 1)); } else { System.out.println("庫存不足,扣減失敗"); } } } catch (Exception e) { if (conn != null) { try { //如果出現(xiàn)異常,進(jìn)行回滾 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } finally { //在最后,關(guān)閉資源 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
3. 樂觀鎖
與悲觀鎖不同,樂觀鎖在數(shù)據(jù)被修改時并不加鎖,而是在數(shù)據(jù)提交時檢查數(shù)據(jù)是否被其他事務(wù)修改過。如果在一個事務(wù)處理過程中數(shù)據(jù)被另一個事務(wù)修改,那么這個事務(wù)就會回滾并重新嘗試。樂觀鎖適用于數(shù)據(jù)競爭不激烈,讀操作遠(yuǎn)多于寫操作的場景。
在Java中,樂觀鎖常常通過"版本號"或者"時間戳"等方式實現(xiàn)。當(dāng)讀取數(shù)據(jù)時,同時也會讀取數(shù)據(jù)的版本號,當(dāng)更新數(shù)據(jù)時,會帶上這個版本號,只有當(dāng)數(shù)據(jù)庫中的當(dāng)前版本和帶上的版本號相同,才會更新數(shù)據(jù),并把版本號加1。
示例如下:首先,我們需要在實體類中添加一個版本號字段,并且用@Version
注解標(biāo)注
import javax.persistence.*; @Entity @Data public class Product { @Id @GeneratedValue private Long id; private Integer count; //版本號,用于實現(xiàn)樂觀鎖 @Version private Integer version; }
然后,在進(jìn)行更新操作時,JPA會自動帶上版本號進(jìn)行檢查:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ProductService { @Autowired private ProductRepository productRepository; @Transactional public void reduceCount(Long productId) { //找到產(chǎn)品 Product product = productRepository.findById(productId).get(); //檢查庫存 if (product.getCount() > 0) { //如果庫存足夠,減少庫存 product.setCount(product.getCount() - 1); productRepository.save(product); } else { //庫存不足,拋出異常 throw new RuntimeException("庫存不足"); } } }
4. MVCC(多版本并發(fā)控制)
多版本并發(fā)控制(MVCC)是一種用于增加數(shù)據(jù)庫并發(fā)性能的技術(shù),它會為每一行數(shù)據(jù)創(chuàng)建版本,以支持高并發(fā)讀寫。每當(dāng)有新的事務(wù)嘗試修改數(shù)據(jù),它都會創(chuàng)建一個新的數(shù)據(jù)版本,而不是直接修改原始數(shù)據(jù)。這樣可以讓讀事務(wù)繼續(xù)訪問舊版本數(shù)據(jù),而寫事務(wù)則修改新版本數(shù)據(jù),從而提高并發(fā)性能。
5. 分布式鎖
在分布式系統(tǒng)中,由于數(shù)據(jù)可能分散在多個節(jié)點上,我們需要一種全局的鎖機制來保證數(shù)據(jù)一致性。分布式鎖可以提供這樣的機制,常見的實現(xiàn)方式有基于數(shù)據(jù)庫的分布式鎖,基于緩存(Redis等)的分布式鎖,以及基于Zookeeper的分布式鎖等。
// 使用 Redisson 庫實現(xiàn) Redis 分布式鎖 RedissonClient redisson = Redisson.create(); RLock lock = redisson.getLock("myLock"); try{ lock.lock(); // 執(zhí)行業(yè)務(wù)操作 }finally{ lock.unlock(); }
總的來說,在高并發(fā)環(huán)境下安全地修改同一行數(shù)據(jù),我們需要選擇合適的鎖機制,以保證數(shù)據(jù)的一致性和系統(tǒng)的性能。同時,我們也需要關(guān)注系統(tǒng)的實際需求和性能瓶頸,以實現(xiàn)最優(yōu)的并發(fā)控制策略。
到此這篇關(guān)于高并發(fā)環(huán)境下安全修改同一行數(shù)據(jù)庫數(shù)據(jù)的策略分享的文章就介紹到這了,更多相關(guān)修改數(shù)據(jù)庫數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot?@Validated的概念及示例實戰(zhàn)
這篇文章主要介紹了springboot?@Validated的概念以及實戰(zhàn),使用?@Validated?注解,Spring?Boot?應(yīng)用可以有效地實現(xiàn)輸入驗證,提高數(shù)據(jù)的準(zhǔn)確性和應(yīng)用的安全性,本文結(jié)合實例給大家講解的非常詳細(xì),需要的朋友可以參考下2024-04-04springboot + vue 實現(xiàn)遞歸生成多級菜單(實例代碼)
這篇文章主要介紹了springboot + vue 實現(xiàn)遞歸生成多級菜單,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12Java8中LocalDateTime與時間戳timestamp的互相轉(zhuǎn)換
這篇文章主要給大家介紹了關(guān)于Java8中LocalDateTime與時間戳timestamp的互相轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之背包問題
背包問題是一個非常典型的考察動態(tài)規(guī)劃應(yīng)用的題目,對其加上不同的限制和條件,可以衍生出諸多變種,若要全面理解動態(tài)規(guī)劃,就必須對背包問題了如指掌2022-02-02一文詳解如何在Java?Maven項目中使用JUnit?5進(jìn)行測試
這篇文章主要介紹了如何在Java?Maven項目中使用JUnit?5進(jìn)行測試的相關(guān)資料,JUnit5是一個流行的Java測試框架,它涵蓋了JUnit5的概述、環(huán)境配置、編寫測試用例、運行測試、高級特性和最佳實踐,需要的朋友可以參考下2025-04-04