2010-06-09 37 views
1

我想在PostgreSQL中定義一個觸發器來檢查通用表上的插入行是否具有以下屬性:「沒有其他行存在相同的有效時間「(鍵是排序鍵)。實際上,我已經實現了它。但由於觸發器必須掃描整個表,現在我想知道:是否需要表級鎖?或者這是由PostgreSQL自己管理的?PostgreSQL,觸發器和併發執行臨時密鑰

這裏是一個例子。 在即將到來的Pos​​tgreSQL 9.0我會以這種方式定義表:

 
CREATE TABLE medicinal_products 
(
aic_code CHAR(9), -- sequenced key 
full_name VARCHAR(255), 
market_time PERIOD, 
    EXCLUDE USING gist 
    (aic_code CHECK WITH =, 
    market_time CHECK WITH &&) 
); 

但其實我一直是這樣定義的:

 
CREATE TABLE medicinal_products 
(
PRIMARY KEY (aic_code, vs), 
aic_code CHAR(9), -- sequenced key 
full_name VARCHAR(255), 
vs DATE NOT NULL, 
ve DATE, 
CONSTRAINT valid_time_range 
     CHECK (ve > vs OR ve IS NULL) 
); 

於是,我寫了檢查觸發費用:「兩種不同的藥品在兩個不同的時期可以有相同的代碼,但不是在同一時間」。

因此,代碼:

 
INSERT INTO medicinal_products VALUES ('1','A','2010-01-01','2010-04-01'); 
INSERT INTO medicinal_products VALUES ('1','A','2010-03-01','2010-06-01'); 

返回一個錯誤。

+0

您在評論中提到一個答案,即使用DATE列存儲有效時間。那麼你能否澄清一下,實際上你只是在檢查日期衝突,而不是完整的時間戳? – araqnid 2010-06-09 15:14:48

回答

1

一個解決方案是使用第二個表來檢測衝突,並用觸發器填充該表。使用你加入到這個問題的模式:

CREATE TABLE medicinal_product_date_map(
    aic_code char(9) NOT NULL, 
    applicable_date date NOT NULL, 
    UNIQUE(aic_code, applicable_date)); 

(注:這是第二次嘗試,由於誤讀您的要求在第一時間輪希望這是正確的這個時候。)。

一些功能,以保持該表:

CREATE FUNCTION add_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date) 
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$ 
    INSERT INTO medicinal_product_date_map 
    SELECT $1, $2 + offset 
    FROM generate_series(0, $3 - $2) 
$$; 
CREATE FUNCTION clr_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date) 
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$ 
    DELETE FROM medicinal_product_date_map 
    WHERE aic_code = $1 AND applicable_date BETWEEN $2 AND $3 
$$; 

填充該表第一時間:

SELECT count(add_medicinal_product_date_range(aic_code, vs, ve)) 
FROM medicinal_products; 

現在創建觸發器更改medicinal_products後填充最新的地圖:後插入調用add_ ,更新後調用clr_(舊值)和add_(新值),刪除後調用clr_。

CREATE FUNCTION sync_medicinal_product_date_map() 
RETURNS trigger LANGUAGE plpgsql AS $$ 
BEGIN 
    IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN 
    PERFORM clr_medicinal_product_date_range(OLD.aic_code, OLD.vs, OLD.ve); 
    END IF; 
    IF TG_OP = 'UPDATE' OR TG_OP = 'INSERT' THEN 
    PERFORM add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve); 
    END IF; 
    RETURN NULL; 
END; 
$$; 
CREATE TRIGGER sync_date_map 
    AFTER INSERT OR UPDATE OR DELETE ON medicinal_products 
    FOR EACH ROW EXECUTE PROCEDURE sync_medicinal_product_date_map(); 

被添加的任何產品與在同一天相同的代碼上medicinal_product_date_map意願陷阱的唯一性約束:

[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-01-01','2010-04-01'); 
INSERT 0 1 
[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-03-01','2010-06-01'); 
ERROR: duplicate key value violates unique constraint "medicinal_product_date_map_aic_code_applicable_date_key" 
DETAIL: Key (aic_code, applicable_date)=(1  , 2010-03-01) already exists. 
CONTEXT: SQL function "add_medicinal_product_date_range" statement 1 
SQL statement "SELECT add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve)" 
PL/pgSQL function "sync_medicinal_product_date_map" line 6 at PERFORM 

這取決於的值被檢查爲具有離散空間 - 這就是爲什麼我詢問了日期與時間戳。雖然時間戳在技術上是離散的,因爲Postgresql只存儲微秒分辨率,但在每一微秒增加一個條目到映射表中,該產品適用於此是不實際的。儘管如此,你也許還可以得到比全表掃描更好的東西來檢查重疊的時間戳記間隔,並且在僅僅尋找第一個間隔之前或之前不尋找......但是,對於易於離散的空間,我更喜歡這種方法,即IME也可以用於其他方面(例如需要快速查找哪些產品適用於某一天的報告)。

我也喜歡這種方法,因爲這種方式充分利用了數據庫的唯一性約束機制。另外,我覺得在主表的併發更新的情況下它會更可靠:在不鎖定併發更新的情況下,驗證觸發器可能看不到衝突,並允許插入兩個併發會話,即然後在兩個交易的影響都可見時發生衝突。

+0

好吧,我明白了。這是一個有趣的解決方案。但是,我必須存儲大約30,000個產品。假設他們平均在市場上待了15年。在這種情況下,地圖表將被填充164百萬行,不是嗎?這並不影響表演? – Hobbes 2010-06-10 07:58:47

+0

那麼,具有該行數的表格將對您的數據庫產生* some *影響。儘管如此,除非產品表正在更新,否則這個額外的表格甚至不會被使用。 (我沒有將代碼放入觸發器來檢查相關列的更新,但也可以這樣做)。 – araqnid 2010-06-10 10:55:23

+0

即使安裝了一個小的安裝(例如默認32mb buffercache),我的開發機器上的一個非常快速的實驗表明,這種大小沒有問題 - 最初需要一段時間才能創建表,但似乎沒有任何問題個別產品更新明顯受損。 – araqnid 2010-06-10 11:21:12

0

只是一個想法,如果有效時間塊可以用一個數字或東西被編碼,創建的ID + TimeBlock UNIQUE索引是極快的,並解決所有表鎖的問題。

它由PostgreSQL自己管理。在select中,它獲得一個ACCESS_SHARE鎖,這意味着您可以查詢該表,但不執行更新。

徹底解決這可能會幫助你是使用高速緩存一樣的Ehcache或memcached的存儲ID/timeblock信息,而不是在所有使用PostgreSQL的。許多人可以堅持下來,以便他們能夠在服務器重啓時倖存下來,並且不會出現這種鎖定行爲。

+0

我認爲這實際上是我最終在我的答案中寫的 - 在(id,applicable_date)上創建唯一索引,其中applicable_date是您的術語中的TimeBlock;即自1970-01-01以來的天數。儘管僅創建一個索引是不夠的,索引需要存儲必須以某種方式生成的(id,timeblock)的每個有效組合。 – araqnid 2010-06-09 19:38:46

+0

如果可以計算時間段,則不需要生成時間段。例如:對於一小時的時間段計算自2000/01/01午夜以來的小時數。在桌子被填滿時,索引會照顧ret。 Postgres有非常好的功能來切片和骰子日期/時間無論如何你想要的。 – 2010-06-09 20:40:48

+0

這很好,如果數據表中的單個行對應於單個時間段,但在OP的情況下,他們不會 - 他將插入涵蓋多天的範圍,並且僅添加所涵蓋的天/時間段之一是不夠的。 – araqnid 2010-06-09 21:06:47

-1

爲什麼你不能使用UNIQUE約束?會更快(這是一個索引),更容易。

+0

使用兩個DATE列存儲有效時間。而且我不能使用UNIQUE約束,因爲我必須確保句點不重疊。 – Hobbes 2010-06-09 08:20:23

+1

即將發佈9.0有一個解決方案,排除約束: http://www.postgresql.org/docs/9.0/static/ddl-constraints.html#AEN2530 – 2010-06-09 08:23:31

+0

我知道,我也讀過該功能在這裏:http://www.pgcon.org/2010/schedule/events/201.en.html。 但在此期間... – Hobbes 2010-06-09 08:57:41