在冲突时更新具有相同值的目标列对性能的影响

Gee*_*ock 5 postgresql performance postgresql-performance

这是一个关于 Postgres (v10) 的内部工作原理和性能的问题。

给定一个表 ,在和列github_repos上具有多列唯一索引,下面的两个批量更新插入操作之间是否存在任何性能差异(或其他需要注意的问题)?不同之处在于,在第一个查询中,和列包含在 UPDATE 中,而在第二个查询中则不包含。由于 UPDATE 在冲突时运行,因此和的更新值将与旧值相同,但这些列包含在 UPDATE 中,因为我正在使用的底层库就是这样设计的。我想知道按原样使用是否安全,或者我是否应该在更新中明确排除这些列。org_idgithub_idorg_idgithub_idorg_idgithub_id

查询#1:

INSERT INTO "github_repos" ("org_id","github_id","name")
VALUES (1,1,'foo')
ON CONFLICT (org_id, github_id)
DO UPDATE SET "org_id"=EXCLUDED."org_id","github_id"=EXCLUDED."github_id","name"=EXCLUDED."name"
RETURNING "id"
Run Code Online (Sandbox Code Playgroud)

查询#2:

INSERT INTO "github_repos" ("org_id","github_id","name")
VALUES (1,1,'foo')
ON CONFLICT (org_id, github_id)
DO UPDATE SET "name"=EXCLUDED."name"
RETURNING "id"
Run Code Online (Sandbox Code Playgroud)

github_repos桌子:

      Column       |       Type        | Collation | Nullable 
-------------------+-------------------+-----------+----------+
 id                | bigint            |           | not null |
 org_id            | bigint            |           | not null |
 github_id         | bigint            |           | not null |
 name              | character varying |           | not null |

Indexes:
    "github_repos_pkey" PRIMARY KEY, btree (id)
    "unique_repos" UNIQUE, btree (org_id, github_id)
Run Code Online (Sandbox Code Playgroud)

a1e*_*x07 3

从性能角度来看,这似乎没有什么区别,假设您没有仅在更新某些列时运行的触发器。

整行都被更新,或者更具体地说是 Postgres 特有的(Postgres 没有就地更新),新的元组将被插入,旧的元组将被标记为 dead 。从这一点来看,实际更改是否仅发生在一列、所有列或两者都没有发生并不重要。您可以检查 n_tup_upd 和 n_dead_tup 列的值pg_stat_all_tables- 它们在每次更新后都会增加。n_dead_tup执行后最终会重置vacuum

唯一可能受到负面影响的是热更新(如果索引列不更改值,则不会有新的索引元组)。HOT 更新在很多地方都有解释,我认为最好的一个是http://www.interdb.jp/pg/pgsql07.html

测试 HOT 更新(使用 Postgres 9.6、10.4 和 10.5 完成)

--- setup
-- useful extension for checking data and index pages
create extension pageinspect; 
-- dummy table  (varchar is used instead of text just to avoid any complication related to TOAST )
create table test_update(test_update_id int not null , some_data varchar(50),constraint test_update_pkey primary key (test_update_id) );

-- insert  2 rows
insert into test_update (test_update_id, some_data) values (1, 'test1');
insert into test_update (test_update_id, some_data) values (2, 'test2');

--check table stats : 
select relname ,n_tup_upd, n_dead_tup , n_tup_hot_upd  from pg_stat_all_tables where relname ='test_update';
 --(all zeroes) 
-- check #rows in index :
select * from bt_page_items('test_update_pkey',1) ;
-- 2 rows

----------------------------------------------
-- Test 1 . perform dummy update : 
update  test_update set test_update_id = test_update_id , some_data = some_data;

-- check tables stats again, 
--n_tup_upd = n_dead_tup = n_tup_hot_upd  = 2 
-- check index page again, results are the same - 2 rows

-- Test 2 . real update non-key column , and dummy update of key column  :
update  test_update set test_update_id = test_update_id , some_data = some_data||'_new';

-- check tables stats again, 
--n_tup_upd = n_dead_tup = n_tup_hot_upd  = 4 
-- check index page again, results are the same - 2 rows

-- Test 3 . real update non-key column , key column is not in the statement:
update  test_update set some_data = some_data||'_new_2';

-- check tables stats again, 
--n_tup_upd = n_dead_tup = n_tup_hot_upd  = 6 
-- check index page again, results are the same - 2 rows

-- Test 4. Now do real update of key column :

update  test_update set test_update_id = test_update_id+2;
-- check tables stats again, 
--n_tup_upd = n_dead_tup = 8, n_tup_hot_upd  = 6  (no HOT update this time)
-- check index page again, now we see 4 rows
Run Code Online (Sandbox Code Playgroud)

看来 Postgres 足够聪明,可以识别索引列未更改的情况,并执行热更新;因此,从性能角度来看,更新语句中包含或不包含键列没有区别。唯一重要的是实际价值是否改变。当然,这种行为仅限于 B-Tree 索引。