PostgreSQL處理數(shù)據(jù)并發(fā)更新沖突的解決方法
一、并發(fā)更新沖突的場(chǎng)景
當(dāng)兩個(gè)或多個(gè)事務(wù)同時(shí)嘗試對(duì)同一行數(shù)據(jù)進(jìn)行修改時(shí),就可能發(fā)生并發(fā)更新沖突。常見(jiàn)的場(chǎng)景包括:
- 同時(shí)修改同一行的不同列
- 同時(shí)對(duì)同一列進(jìn)行不同的值更新
二、PostgreSQL 中的并發(fā)控制機(jī)制
PostgreSQL 主要使用 MVCC(多版本并發(fā)控制,Multiversion Concurrency Control ) 來(lái)處理并發(fā)事務(wù)。MVCC 允許事務(wù)讀取到符合其隔離級(jí)別需求的數(shù)據(jù)版本,而不需要加鎖阻塞其他事務(wù)的讀操作。然而,在寫(xiě)操作時(shí),仍可能出現(xiàn)沖突。
(一) 封鎖機(jī)制
PostgreSQL 使用多種類(lèi)型的鎖來(lái)控制對(duì)數(shù)據(jù)的并發(fā)訪問(wèn)。常見(jiàn)的鎖類(lèi)型包括:
- 共享鎖(Shared Lock):允許其他事務(wù)也獲取共享鎖,但阻止獲取排他鎖。常用于讀取操作。
- 排他鎖(Exclusive Lock):阻止其他事務(wù)獲取任何類(lèi)型的鎖,常用于寫(xiě)入操作。
鎖的粒度可以是行級(jí)(Row-Level)、頁(yè)級(jí)(Page-Level)和表級(jí)(Table-Level)。
(二) 事務(wù)隔離級(jí)別
PostgreSQL 支持四種事務(wù)隔離級(jí)別:
- 讀未提交(Read Uncommitted):這是最低的隔離級(jí)別,一個(gè)事務(wù)可以讀取到其他事務(wù)未提交的數(shù)據(jù)修改,可能導(dǎo)致臟讀、不可重復(fù)讀和幻讀等問(wèn)題。
- 讀已提交(Read Committed):事務(wù)只能讀取已經(jīng)提交的數(shù)據(jù),避免了臟讀,但仍可能出現(xiàn)不可重復(fù)讀和幻讀。
- 可重復(fù)讀(Repeatable Read):在一個(gè)事務(wù)內(nèi)多次讀取相同的數(shù)據(jù)會(huì)得到相同的結(jié)果,避免了不可重復(fù)讀,但可能出現(xiàn)幻讀。
- 串行化(Serializable):最高的隔離級(jí)別,通過(guò)嚴(yán)格的并發(fā)控制確保事務(wù)的串行執(zhí)行,避免了臟讀、不可重復(fù)讀和幻讀。
三、并發(fā)更新沖突的解決方法
(一) 重試機(jī)制
一種簡(jiǎn)單的方法是當(dāng)沖突發(fā)生時(shí),讓事務(wù)進(jìn)行重試。示例如下
DO $$ DECLARE conflict_detected BOOLEAN := FALSE; BEGIN LOOP -- 嘗試執(zhí)行更新操作 UPDATE products SET price = 100 WHERE id = 1; -- 檢查是否有沖突(例如,通過(guò)檢查受影響的行數(shù)) IF NOT FOUND THEN conflict_detected := TRUE; ELSE EXIT; END IF; -- 若有沖突,等待一段時(shí)間并重試 IF conflict_detected THEN PERFORM pg_sleep(1); END IF; END LOOP; END; $$;
在上述示例中,如果更新操作沒(méi)有影響到任何行(表示可能存在沖突),則設(shè)置一個(gè)標(biāo)志,等待一段時(shí)間后重試。
(二) 使用樂(lè)觀并發(fā)控制
樂(lè)觀并發(fā)控制假設(shè)并發(fā)沖突很少發(fā)生。在這種方式中,事務(wù)在更新數(shù)據(jù)時(shí)不進(jìn)行加鎖,而是在提交時(shí)檢查數(shù)據(jù)是否被其他事務(wù)修改。如果沒(méi)有沖突,事務(wù)成功提交;如果有沖突,事務(wù)回滾并根據(jù)需要重試。
-- 獲取數(shù)據(jù)的初始版本 SELECT price AS original_price FROM products WHERE id = 1; -- 進(jìn)行業(yè)務(wù)處理和修改 UPDATE products SET price = 100 WHERE id = 1 AND price = original_price;
在上述示例中,更新操作僅在數(shù)據(jù)未被其他事務(wù)修改的情況下成功。
(三) 使用悲觀并發(fā)控制
悲觀并發(fā)控制則假設(shè)并發(fā)沖突很可能發(fā)生,在事務(wù)執(zhí)行期間獲取所需的鎖來(lái)阻塞其他可能沖突的事務(wù)。
BEGIN; -- 獲取排他鎖 LOCK TABLE products IN SHARE ROW EXCLUSIVE MODE; -- 進(jìn)行數(shù)據(jù)更新 UPDATE products SET price = 100 WHERE id = 1; COMMIT;
在更新數(shù)據(jù)時(shí),同時(shí)遞增版本字段:
UPDATE products SET price = 100, version = version + 1 WHERE id = 1 AND version = <expected_version>;
(四) 應(yīng)用版本字段
給表添加一個(gè)版本字段來(lái)跟蹤數(shù)據(jù)的更改。
CREATE TABLE products ( id SERIAL PRIMARY KEY, price DECIMAL(10, 2), version INT DEFAULT 0 );
在更新數(shù)據(jù)時(shí),同時(shí)遞增版本字段:
UPDATE products SET price = 100, version = version + 1 WHERE id = 1 AND version = <expected_version>;
如果更新影響的行數(shù)為 0,表示存在沖突,因?yàn)轭A(yù)期的版本與實(shí)際的版本不一致。
(五) 基于時(shí)間戳的沖突解決
為每行數(shù)據(jù)添加一個(gè)時(shí)間戳字段,記錄數(shù)據(jù)的最后修改時(shí)間。
CREATE TABLE products ( id SERIAL PRIMARY KEY, price DECIMAL(10, 2), last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
在更新時(shí),僅更新時(shí)間戳比當(dāng)前事務(wù)讀取的時(shí)間戳更早的數(shù)據(jù):
UPDATE products SET price = 100 WHERE id = 1 AND last_modified <= <read_timestamp>;
四、實(shí)際應(yīng)用中的考慮因素
(一) 性能影響
- 不同的沖突解決方法對(duì)數(shù)據(jù)庫(kù)性能有不同的影響。例如,使用封鎖可能導(dǎo)致其他事務(wù)的等待,增加系統(tǒng)的阻塞時(shí)間,從而影響并發(fā)性。而樂(lè)觀并發(fā)控制在沖突很少發(fā)生時(shí)性能較好,但在沖突頻繁時(shí)可能導(dǎo)致大量的事務(wù)重試,增加了總體的執(zhí)行時(shí)間。
- 應(yīng)用版本字段或基于時(shí)間戳的方法可能需要額外的存儲(chǔ)空間來(lái)維護(hù)版本或時(shí)間戳信息,并在更新時(shí)進(jìn)行額外的判斷和處理。
(二) 業(yè)務(wù)邏輯適應(yīng)性
- 某些業(yè)務(wù)場(chǎng)景可能更適合某種特定的沖突解決方法。例如,如果業(yè)務(wù)對(duì)數(shù)據(jù)的一致性要求非常高,不能容忍任何不一致的情況,那么悲觀并發(fā)控制或串行化隔離級(jí)別可能是更好的選擇。
- 對(duì)于沖突不太頻繁且對(duì)響應(yīng)時(shí)間要求較高的場(chǎng)景,樂(lè)觀并發(fā)控制可能更合適。
(三) 數(shù)據(jù)分布和訪問(wèn)模式
- 如果數(shù)據(jù)的訪問(wèn)是高度并發(fā)的,并且多個(gè)事務(wù)經(jīng)常同時(shí)訪問(wèn)相同的數(shù)據(jù)行,那么需要更加謹(jǐn)慎地選擇沖突解決方法,以避免過(guò)度的阻塞和沖突。
- 對(duì)于數(shù)據(jù)分布較為均勻,沖突概率較低的情況,可以采用相對(duì)簡(jiǎn)單和高效的方法,如樂(lè)觀并發(fā)控制。
五、示例分析
假設(shè)我們有一個(gè)在線商店的庫(kù)存管理系統(tǒng),其中有一個(gè) inventory
表來(lái)存儲(chǔ)商品的庫(kù)存數(shù)量。
CREATE TABLE inventory ( product_id INT PRIMARY KEY, quantity INT, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
現(xiàn)在有兩個(gè)并發(fā)的事務(wù):
事務(wù) 1:
BEGIN; SELECT * FROM inventory WHERE product_id = 1; -- 假設(shè)讀取到的數(shù)量為 10 UPDATE inventory SET quantity = 5 WHERE product_id = 1 AND last_updated <= <read_timestamp>; COMMIT;
事務(wù) 2:
BEGIN; SELECT * FROM inventory WHERE product_id = 1; -- 假設(shè)也讀取到的數(shù)量為 10 UPDATE inventory SET quantity = 8 WHERE product_id = 1 AND last_updated <= <read_timestamp>; COMMIT;
如果這兩個(gè)事務(wù)幾乎同時(shí)執(zhí)行,可能會(huì)發(fā)生沖突。
如果我們采用基于時(shí)間戳的沖突解決方法:
- 事務(wù) 1 讀取數(shù)據(jù)時(shí)獲取了當(dāng)前的時(shí)間戳(
T1
)。 - 事務(wù) 2 讀取數(shù)據(jù)時(shí)獲取了稍晚的時(shí)間戳(
T2
)。
當(dāng)事務(wù) 1 嘗試更新時(shí),如果自它讀取以來(lái)沒(méi)有其他事務(wù)修改數(shù)據(jù)(即 last_updated <= T1
),則更新成功。
當(dāng)事務(wù) 2 嘗試更新時(shí),如果發(fā)現(xiàn)數(shù)據(jù)的 last_updated
大于 T2
(說(shuō)明在事務(wù) 2 讀取之后被修改過(guò)),則更新失敗,事務(wù) 2 可以選擇回滾并重試,或者根據(jù)業(yè)務(wù)邏輯進(jìn)行其他處理。
以上就是PostgreSQL處理數(shù)據(jù)并發(fā)更新沖突的解決方法的詳細(xì)內(nèi)容,更多關(guān)于PostgreSQL數(shù)據(jù)并發(fā)更新沖突的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PostgreSQL中的日期/時(shí)間函數(shù)詳解
這篇文章主要給大家介紹了關(guān)于PostgreSQL中日期/時(shí)間函數(shù)的相關(guān)資料,文章通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01Postgresql通過(guò)查詢(xún)進(jìn)行更新的操作
這篇文章主要介紹了Postgresql通過(guò)查詢(xún)進(jìn)行更新的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01PostgreSQL 自定義自動(dòng)類(lèi)型轉(zhuǎn)換操作(CAST)
這篇文章主要介紹了PostgreSQL 自定義自動(dòng)類(lèi)型轉(zhuǎn)換操作(CAST),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01PostgreSQL 對(duì)IN,EXISTS,ANY/ALL,JOIN的sql優(yōu)化方案
這篇文章主要介紹了PostgreSQL 對(duì)IN,EXISTS,ANY/ALL,JOIN的sql優(yōu)化方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01PostgreSQL中數(shù)據(jù)批量導(dǎo)入導(dǎo)出的錯(cuò)誤處理
在 PostgreSQL 中進(jìn)行數(shù)據(jù)的批量導(dǎo)入導(dǎo)出是常見(jiàn)的操作,但有時(shí)可能會(huì)遇到各種錯(cuò)誤,下面將詳細(xì)探討可能出現(xiàn)的錯(cuò)誤類(lèi)型、原因及相應(yīng)的解決方案,并提供具體的示例來(lái)幫助您更好地理解和處理這些問(wèn)題,需要的朋友可以參考下2024-07-07postgresql數(shù)據(jù)庫(kù)安裝部署搭建主從節(jié)點(diǎn)的詳細(xì)過(guò)程(業(yè)務(wù)庫(kù))
這篇文章主要介紹了postgresql數(shù)據(jù)庫(kù)安裝部署搭建主從節(jié)點(diǎn)的詳細(xì)過(guò)程(業(yè)務(wù)庫(kù)),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01PostgreSQL表膨脹監(jiān)控案例(精確計(jì)算)
這篇文章主要介紹了PostgreSQL表膨脹監(jiān)控案例(精確計(jì)算),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01玩轉(zhuǎn)PostgreSQL之30個(gè)實(shí)用SQL語(yǔ)句
本文主要整理總結(jié)了30個(gè)實(shí)用SQL,方便大家可以高效利用PostgreSQL,需要的朋友可以參考下2022-11-11