---
# System prepended metadata

title: PostgreSQL update 的探討
tags: [一級屠豬士, Postgresql]

---

# PostgreSQL update 的探討
## 前言
大家都熟知幾個資料庫操作的語法,select,insert,delete與update.
我們來看這幾個語法,先由 update 來入手.
## 觀察一下 update 之後 table的使用空間
先建立一個 table, 指定不做 autovacuum. 
```sql=
create table t231124a (
  id int primary key
, val int
) with (autovacuum_enabled=false
, toast.autovacuum_enabled=false);

insert into t231124a values
(1, 1);

-- 一筆資料, 起跳 8K 
select pg_size_pretty(pg_table_size('t231124a'));
 pg_size_pretty 
----------------
 8192 bytes 

-- 建立 update x次 的 sp
create or replace procedure sp231124a(in x int)
language plpgsql
as $$
declare i int;
begin
  for i in 1..x loop
    update t231124a
       set val = i
     where id = 1;
  end loop;
  commit;
end $$;

--- update 1000 次
call sp231124a(1000);

commit;

select pg_size_pretty(pg_table_size('t231124a'));

 pg_size_pretty 
----------------
 64 kB
 
commit;

-- 變 64K了

-- 使用 vacuum 清理
vacuum full t231124a;

select pg_size_pretty(pg_table_size('t231124a'));

 pg_size_pretty 
----------------
 8192 bytes

-- 回到 8k
```
## 使用 pg_dirtyread 來觀察
我們可以使用 pg_dirtyread 來觀察,還沒做 vacuum 時的情況.
```bash=
https://github.com/df7cb/pg_dirtyread

git clone https://github.com/df7cb/pg_dirtyread.git
 
make

sudo make install
```
### 安裝之後的操作
```sql=
create extension pg_dirtyread;

create table t231222a (
  id int
, bar text
, ts timestamptz default clock_timestamp()
) with (autovacuum_enabled = false
, toast.autovacuum_enabled = false);

insert into t231222a(id, bar) values
(1, 'SONE-009');

update t231222a
   set bar = 'SSIS-911'
     , ts = clock_timestamp()
 where id = 1;

update t231222a
   set bar = 'SSIS-931'
     , ts = clock_timestamp()
 where id = 1;
 
delete
  from t231222a
 where id = 1;

insert into t231222a(id, bar) values
(1, 'SSIS-894');


-- 一定要 select 一次, 讓 pg_dirtyread 可以更新資訓
select *
  from t231222a;

 id |   bar    |              ts               
----+----------+-------------------------------
  1 | SSIS-894 | 2023-12-22 15:29:16.473379+08
(1 row)

--
select * 
  from pg_dirtyread('t231222a') 
       as t(ctid tid, xmin xid, xmax xid, dead boolean,
           id int, bar text, ts timestamptz);

 ctid  | xmin | xmax | dead | id |   bar    |              ts               
-------+------+------+------+----+----------+-------------------------------
 (0,1) | 2068 | 2070 | t    |  1 | SONE-009 | 2023-12-22 15:28:36.281726+08
 (0,2) | 2070 | 2072 | t    |  1 | SSIS-911 | 2023-12-22 15:28:47.288191+08
 (0,3) | 2072 | 2074 | t    |  1 | SSIS-931 | 2023-12-22 15:28:57.183544+08
 (0,4) | 2076 |    0 | f    |  1 | SSIS-894 | 2023-12-22 15:29:16.473379+08
(4 rows)

vacuum full t231222a;

select * 
  from pg_dirtyread('t231222a') 
       as t(ctid tid, xmin xid, xmax xid, dead boolean,
           id int, bar text, ts timestamptz);

 ctid  | xmin | xmax | dead | id |   bar    |              ts               
-------+------+------+------+----+----------+-------------------------------
 (0,1) | 2076 |    0 | f    |  1 | SSIS-894 | 2023-12-22 15:29:16.473379+08
(1 row)
```
### 說明
可以觀察到在做 vacumm 之前, 做了兩次 update, 做了一次 delete,最後做 delete.
第一次 update, 將 SONE-009, 變更為 SSIS-911, 並變更 timestamp.
第二次 update, 將 SSIS-911, 變更為 SSIS-931, 並變更timestamp.
可以看到,ctid xmin, max 的變化, dead 標誌為 true.
接著兩個動作, delete 將 SSIS-931 那筆標誌為 dead.
insert 輸入最新的 SSIS-894, 以及 timestamp.

我們可以把 delete 的動作,理解成做查詢(select), 其實語法是幾乎一樣,除了沒有 select 欄位,找到將後dead 標誌為 true.
insert 就是 insert 了.
update 就可以理解為 delete 與 insert 的組合.
這樣就能夠理解那段做1000次 update , table的使用空間是 64K.
因為其實就是做了1000次 delete, insert.

## 結語
透過理解指令的內部動作,對後續我們開發系統時,能夠更深入的思考規劃.