Rumah  >  Artikel  >  Java  >  Bagaimana untuk menangani keadaan perlumbaan menggunakan Java dan PostgreSQL

Bagaimana untuk menangani keadaan perlumbaan menggunakan Java dan PostgreSQL

WBOY
WBOYasal
2024-07-18 10:15:301044semak imbas

How to deal with race conditions using Java and PostgreSQL

Menggunakan penguncian untuk mengawal konkurensi pangkalan data

Bayangkan anda sedang mengusahakan sistem e-dagang dan beribu-ribu orang cuba membeli produk terakhir yang tinggal pada masa yang sama. Walau bagaimanapun, ramai daripada mereka boleh meneruskan ke pembayaran dan menyelesaikan pesanan. Apabila anda menyemak stok anda, anda mempunyai produk dengan kuantiti negatif. Bagaimanakah ini boleh berlaku dan bagaimana anda boleh menyelesaikannya?

Jom kod! Perkara pertama yang mungkin anda fikirkan ialah menyemak stok sebelum pembayaran. Mungkin seperti ini:

public void validateAndDecreaseSolution(long productId, int quantity {
    Optional<StockEntity> stockByProductId = 
 stockRepository.findStockByProductId(productId);

    int stock = stockByProductId.orElseThrow().getStock();
    int possibleStock = stock - quantity;

    if (stock <= 0 || possibleStock < 0) {
        throw new OutOfStockException("Out of stock");
    }

    stockRepository.decreaseStock(productId, quantity);
}

Anda boleh menggunakan pengesahan ini, tetapi apabila kita bercakap tentang ratusan, ribuan, berjuta-juta, malah berpuluh-puluh permintaan sesaat, pengesahan ini tidak akan mencukupi. Apabila 10 permintaan mencapai sekeping kod ini pada masa yang sama dan pangkalan data mengembalikan nilai yang sama untuk stockByProductId, kod anda akan rosak. Anda memerlukan cara untuk menyekat permintaan lain semasa kami melakukan pengesahan ini.

Penyelesaian pertama - UNTUK KEMASKINI

Tambah pernyataan kunci pada SELECT anda. Dalam contoh ini saya melakukan ini menggunakan FOR UPDATE dengan Spring Data. Seperti yang dikatakan oleh dokumentasi PostgreSQL

UNTUK KEMASKINI menyebabkan baris yang diambil oleh pernyataan SELECT akan dikunci seolah-olah untuk kemas kini. Ini menghalang mereka daripada diubah suai atau dipadamkan oleh transaksi lain sehingga transaksi semasa tamat.

@Query(value = "SELECT * FROM stocks s WHERE s.product_id = ?1 FOR UPDATE", nativeQuery = true)
Optional<StockEntity> findStockByProductIdWithLock(Long productId);
public void validateAndDecreaseSolution1(long productId, int quantity) {
    Optional<StockEntity> stockByProductId = stockRepository.findStockByProductIdWithLock(productId);

    // ... validate

    stockRepository.decreaseStock(productId, quantity);
}

Semua permintaan ke jadual stok menggunakan ID produk akan menunggu sehingga transaksi sebenar selesai. Objektif di sini adalah untuk memastikan anda mendapat nilai terkini stok yang dikemas kini.

Penyelesaian kedua - pg_advisory_xact_lock

Penyelesaian ini serupa dengan yang sebelumnya, tetapi anda boleh memilih apa itu kunci kunci. Kami akan mengunci keseluruhan transaksi sehingga kami menyelesaikan semua pemprosesan pengesahan dan pengurangan stok.

public void acquireLockAndDecreaseSolution2(long productId, int quantity) {
    Query nativeQuery = entityManager.createNativeQuery("select pg_advisory_xact_lock(:lockId)");
    nativeQuery.setParameter("lockId", productId);
    nativeQuery.getSingleResult();

    Optional<StockEntity> stockByProductId = stockRepository.findStockByProductId(productId);

    // check stock and throws exception if it is necessary

    stockRepository.decreaseStock(productId, quantity);
}

Permintaan seterusnya hanya akan berinteraksi dengan produk dengan ID yang sama selepas transaksi ini tamat.

Penyelesaian ketiga - klausa WHERE

Dalam kes ini, kami tidak akan mengunci baris atau transaksi kami. Mari kita benarkan transaksi ini diteruskan sehingga penyata kemas kini. Perhatikan syarat terakhir: stok > 0. Ini tidak akan membenarkan stok kami kurang daripada sifar. Jadi jika dua orang cuba membeli pada masa yang sama, salah seorang daripada mereka akan menerima ralat kerana pangkalan data kami tidak akan membenarkan stok <= -1.

@Transactional
@Modifying
@Query(nativeQuery = true, value = "UPDATE stocks SET stock = stock - :quantity WHERE product_id = :productId AND stock > 0")
int decreaseStockWhereQuantityGreaterThanZero(@Param("productId") Long productId, @Param("quantity") int quantity);




Kesimpulan

Penyelesaian pertama dan kedua menggunakan penguncian pesimis sebagai strategi. Yang ketiga ialah penguncian optimistik. Strategi penguncian pesimis digunakan apabila anda mahukan akses terhad kepada sumber semasa anda melakukan sebarang tugas yang melibatkan sumber ini. Sumber sasaran akan dikunci untuk sebarang akses lain sehingga anda menyelesaikan proses anda. Berhati-hati dengan kebuntuan!

Dengan penguncian optimistik, anda boleh melakukan pelbagai pertanyaan pada sumber yang sama tanpa sebarang sekatan. Ia digunakan apabila konflik tidak mungkin berlaku. Biasanya, anda akan mempunyai versi yang berkaitan dengan baris anda dan apabila anda mengemas kini baris ini, pangkalan data akan membandingkan versi baris anda dengan versi baris dalam pangkalan data. Jika kedua-duanya sama, perubahan akan berjaya. Jika tidak, anda perlu mencuba semula. Seperti yang anda lihat, saya tidak menggunakan mana-mana baris versi dalam artikel ini, tetapi penyelesaian ketiga saya tidak menyekat sebarang permintaan dan mengawal konkurensi menggunakan stok > 0 syarat.

Jika anda ingin melihat kod penuh, anda boleh menyemak di GitHub saya.

Terdapat banyak strategi lain untuk melaksanakan penguncian pesimis dan optimistik, anda boleh mencari lebih lanjut tentang UNTUK KEMASKINI DENGAN SKIP LOCKed contohnya.

Atas ialah kandungan terperinci Bagaimana untuk menangani keadaan perlumbaan menggunakan Java dan PostgreSQL. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn