閱讀562 返回首頁    go 阿裏雲 go 技術社區[雲棲]


一張表有且隻有一條記錄(續) - 支持插入,並且查詢、更新、刪除隻作用在最後一條記錄上

標簽

PostgreSQL , 有且隻有一條記錄


背景

之前寫過一篇文檔,介紹如何控製某張表有且隻有一條記錄。

《如何實現一張表有且隻有一條記錄 implement PostgreSQL table have one and only one row》

接下來這個需求與之類似,一張表好像有且隻有一條記錄,要求這樣:

1、支持插入、更新、刪除、查詢操作,

2、有一個時間字段用來區分這條記錄是什麼時候插入、更新的。

3、更新隻作用在最後一條記錄(時間最大的那條)上,

4、查詢隻返回時間最大的一條記錄。所以看起來就隻有一條記錄一樣。(實際上如果插入了很多,那麼就是很多條)

5、刪除時,刪除所有記錄。

實現方法

建立2張表,一張視圖,麵向用戶的是視圖(所有的增刪改查都基於視圖,所以用戶可以忘記基表和影子表)。

1、基表

create table base_tbl (  
  id serial8 primary key,  -- 必須有一個PK  
  info text,    --  用戶自身需求的內容  
  c1 int,       --  用戶自身需求的內容  
  c2 int,       --  用戶自身需求的內容  
  ts timestamp  --  時間(更新、插入時務必更新這個時間)  
);  
  
create index idx_base_tbl on base_tbl (ts);  

2、影子表(用於觸發器),如果沒有影子表,直接對基表建立觸發器,會有鎖錯誤。

create table shadow_base_tbl ();  

3、基表的limit 1視圖

create view tbl as select * from base_tbl order by ts desc limit 1;  

創建規則和觸發器,實現前麵提到的需求。

1、視圖insert, update, delete規則

create rule r1 AS ON INSERT TO tbl DO INSTEAD INSERT INTO base_tbl (info, c1, c2, ts) VALUES (new.info, new.c1, new.c2, clock_timestamp());  
create rule r2 AS ON UPDATE TO tbl DO INSTEAD UPDATE base_tbl SET info = new.info, c1=new.c1, c2=new.c2, ts=clock_timestamp() WHERE base_tbl.id=old.id;  
create rule r3 AS ON DELETE TO tbl DO INSTEAD DELETE FROM shadow_base_tbl;  

2、影子表delete觸發器

create or replace function tg_truncate_v() returns trigger as $$  
declare  
begin  
  truncate base_tbl;  
  return null;  
end;  
$$ language plpgsql strict;  
  
create trigger tg before delete on shadow_base_tbl for each statement execute procedure tg_truncate_v();  

結構定義如下

postgres=# \d+ tbl  
                                       View "public.tbl"  
 Column |            Type             | Collation | Nullable | Default | Storage  | Description   
--------+-----------------------------+-----------+----------+---------+----------+-------------  
 id     | bigint                      |           |          |         | plain    |   
 info   | text                        |           |          |         | extended |   
 c1     | integer                     |           |          |         | plain    |   
 c2     | integer                     |           |          |         | plain    |   
 ts     | timestamp without time zone |           |          |         | plain    |   
View definition:  
 SELECT base_tbl.id,  
    base_tbl.info,  
    base_tbl.c1,  
    base_tbl.c2,  
    base_tbl.ts  
   FROM base_tbl  
  ORDER BY base_tbl.ts DESC  
 LIMIT 1;  
Rules:  
 r1 AS  
    ON INSERT TO tbl DO INSTEAD  INSERT INTO base_tbl (id, info, c1, c2, ts)  
  VALUES (new.id, new.info, new.c1, new.c2, clock_timestamp())  
 r2 AS  
    ON UPDATE TO tbl DO INSTEAD  UPDATE base_tbl SET info = new.info, c1 = new.c1, c2 = new.c2, ts = clock_timestamp()  
  WHERE base_tbl.id = old.id  
 r3 AS  
    ON DELETE TO tbl DO INSTEAD  DELETE FROM shadow_base_tbl  
  
postgres=# \d+ base_tbl  
                                                          Table "public.base_tbl"  
 Column |            Type             | Collation | Nullable |               Default                | Storage  | Stats target | Description   
--------+-----------------------------+-----------+----------+--------------------------------------+----------+--------------+-------------  
 id     | bigint                      |           | not null | nextval('base_tbl_id_seq'::regclass) | plain    |              |   
 info   | text                        |           |          |                                      | extended |              |   
 c1     | integer                     |           |          |                                      | plain    |              |   
 c2     | integer                     |           |          |                                      | plain    |              |   
 ts     | timestamp without time zone |           |          |                                      | plain    |              |   
Indexes:  
    "base_tbl_pkey" PRIMARY KEY, btree (id)  
    "idx_base_tbl" btree (ts)  
  
postgres=# \d+ shadow_base_tbl   
                            Table "public.shadow_base_tbl"  
 Column | Type | Collation | Nullable | Default | Storage | Stats target | Description   
--------+------+-----------+----------+---------+---------+--------------+-------------  
Triggers:  
    tg BEFORE DELETE ON shadow_base_tbl FOR EACH STATEMENT EXECUTE PROCEDURE tg_truncate_v()  

測試tbl視圖的dml如下

1、插入多次

postgres=# insert into tbl(info,c1,c2,ts) values ('test',1,2,now());  
INSERT 0 1  
postgres=# insert into tbl(info,c1,c2,ts) values ('test12',2,222,now());  
INSERT 0 1  
postgres=# insert into tbl(info,c1,c2,ts) values ('test12',2,222,now());  
INSERT 0 1  
postgres=# insert into tbl(info,c1,c2,ts) values ('test12',2,222,now());  
INSERT 0 1  
  
隻顯示最後一條記錄的目的達到  
postgres=# select * from tbl;  
 id |  info  | c1 | c2  |            ts               
----+--------+----+-----+---------------------------  
  4 | test12 |  2 | 222 | 2017-07-11 20:39:56.75285  
(1 row)  
  
查看基表,所有記錄都在  
postgres=# select * from base_tbl;  
 id |  info  | c1 | c2  |             ts               
----+--------+----+-----+----------------------------  
  1 | test   |  1 |   2 | 2017-07-11 20:39:49.933267  
  2 | test12 |  2 | 222 | 2017-07-11 20:39:54.939552  
  3 | test12 |  2 | 222 | 2017-07-11 20:39:56.406619  
  4 | test12 |  2 | 222 | 2017-07-11 20:39:56.75285  
(4 rows)  

2、查詢

postgres=# select * from tbl;  
 id |  info  | c1 | c2  |            ts               
----+--------+----+-----+---------------------------  
  4 | test12 |  2 | 222 | 2017-07-11 20:39:56.75285  
(1 row)  

3、更新

隻會更新最後一條   
  
postgres=# update tbl set info='abcde';  
UPDATE 1  
postgres=# select * from base_tbl;  
 id |  info  | c1 | c2  |             ts               
----+--------+----+-----+----------------------------  
  1 | test   |  1 |   2 | 2017-07-11 20:39:49.933267  
  2 | test12 |  2 | 222 | 2017-07-11 20:39:54.939552  
  3 | test12 |  2 | 222 | 2017-07-11 20:39:56.406619  
  4 | abcde  |  2 | 222 | 2017-07-11 20:42:08.230306  
(4 rows)  
  
postgres=# select * from tbl;  
 id | info  | c1 | c2  |             ts               
----+-------+----+-----+----------------------------  
  4 | abcde |  2 | 222 | 2017-07-11 20:42:08.230306  
(1 row)  

4、刪除

刪除,觸發truncate基表的動作  
  
postgres=# delete from tbl;  
DELETE 0  
postgres=# select * from tbl;  
 id | info | c1 | c2 | ts   
----+------+----+----+----  
(0 rows)  
  
postgres=# select * from base_tbl;  
 id | info | c1 | c2 | ts   
----+------+----+----+----  
(0 rows)  

5、維護

實際上如果用戶一直不調用delete,那麼隨著插入,基表會越來越大。

建議要經常維護基表(例如 鎖住基表,把最後一條查出來,TRUNCATE 基表,然後把最後一條插進去)。

例子

begin;
set lock_timeout ='1s';
create LOCAL temp table tmp1 (like tbl) ;
lock table base_tbl in ACCESS EXCLUSIVE mode;
insert into tmp1 select * from tbl;
truncate base_tbl;
insert into tbl select * from tmp1;
drop table tmp1;
end;

注意

注意到,插入是不管你原來有沒有記錄的,並不是insert on conflict do update的做法。

所以插入的並發可以做到很高。

而更新,可能並發還是會較低,因為鎖的是最後一條記錄。記錄成為鎖熱點並發就上不來。

最後更新:2017-07-14 09:04:07

  上一篇:go  為什麼數據庫事務會出現未知狀態,如何處理
  下一篇:go  PostgreSQL 10 流式物理、邏輯主從 最佳實踐