0
点赞
收藏
分享

微信扫一扫

有趣的生产案例两则

前言

今天短暂的一天就出现了两起有趣的生产案例,十分值得分享 ~

删除列自动删除掉所有依赖的索引

第一个例子是同事反馈的:开发删除了某一列之后,将复合索引也顺带删除了,于是导致查询雪崩。

看个例子,开发正常使用这个复合索引进行查询

postgres=# create table t1(id int,info text);
CREATE TABLE
postgres=# insert into t1 select n,md5(random()::text) from generate_series(1,100000) as n;
INSERT 0 100000
postgres=# create index t1_idx on t1(id,info);
CREATE INDEX
postgres=# analyze t1;
ANALYZE
postgres=# explain select id,info from t1 where id = '99';
QUERY PLAN
-----------------------------------------------------------------------
Index Only Scan using t1_idx on t1 (cost=0.42..8.44 rows=1 width=37)
Index Cond: (id = 99)
(2 rows)

然后业务发版,进行表结构变更,删除了其中的info列

postgres=# alter table t1 drop column info;
ALTER TABLE
postgres=# \d t1
Table "public.t1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |

DBA惊呆了😮,复合索引也被删除了,并且没有任何的提示,于是导致今天本该使用这个复合索引的SQL全部走了顺序扫描,雪崩...(这个在Oracle中也是相同行为)

虽然这看起来是正常的,列都不在了索引当然也不在。

postgres=# create table t2(id int,info text);
CREATE TABLE
postgres=# create index mydx on t2(info);
CREATE INDEX
postgres=# \d t2
Table "public.t2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |
Indexes:
"mydx" btree (info)

postgres=# alter table t2 drop column info;
ALTER TABLE
postgres=# \d t2
Table "public.t2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |

但是对于复合索引就有点微妙了,我觉得更优雅的方式应该是从复合索引中自动去掉这一列,然后重构索引,而不是直接一把梭删掉了这个复合索引(Oracle也是一样的行为),并且没有任何提示,不然稍不注意就可能掉进了坑里。这个锅太重了。

因此,强烈建议在删除列之前,查找一下依赖关系,哪些表包含这个列,是否有复合索引

postgres=# create table test(id int,info text);
CREATE TABLE
postgres=# create table test3(info int);
CREATE TABLE
postgres=# create index myidx on test(id);
CREATE INDEX
postgres=# create index mydix2 on test(id,info);
CREATE INDEX
postgres=# select
t.relname as table_name,
i.relname as index_name,
a.attname as column_name
from
pg_class t,
pg_class i,
pg_index ix,
pg_attribute a
where
t.oid = ix.indrelid
and i.oid = ix.indexrelid
and a.attrelid = t.oid
and a.attnum = ANY(ix.indkey)
and t.relkind = 'r'
and t.relname like 'test%'
order by
t.relname,
i.relname;
table_name | index_name | column_name
------------+------------+-------------
test | mydix2 | id
test | mydix2 | info
test | myidx | id
(3 rows)

postgres=# SELECT
t.table_schema,
t.table_name
FROM
information_schema.tables t
INNER JOIN information_schema.columns c ON c.table_name = t.table_name
AND c.table_schema = t.table_schema
WHERE
c.column_name = 'info'
AND t.table_schema NOT IN ('information_schema', 'pg_catalog')
AND t.table_type = 'BASE TABLE'
ORDER BY
t.table_schema;
table_schema | table_name
--------------+------------
public | test3
public | test
(2 rows)

这样的话,比如我要删除info列,通过这个SQL我就可以知晓myidx2这个复合索引也会级联删除!

或者更硬气一点,通过event trigger禁止删列、删索引

create function fetg() returns event_trigger language plpgsql as $$
begin
if exists (
select 1
from pg_event_trigger_dropped_objects() as t
where
t.object_type = 'table column' and
t.object_identity like any(array['%.t.y', 'public.tt.zz']))
then
raise exception 'Columns t.y and public.tt.zz are important!';
end if;
end $$;

create event trigger etg on sql_drop execute procedure fetg();

postgres=# create table t(x int, y int);
CREATE TABLE
postgres=# alter table t drop column y;
ERROR: Columns t.y and public.tt.zz are important!
CONTEXT: PL/pgSQL function fetg() line 10 at RAISE

pg_restore不恢复索引

这个例子也是今天发现的,同事找到我说pg_restore不会恢复索引?这又是什么鬼?让我们复现一下

postgres=# create table test_dump(id int,info text);
CREATE TABLE
postgres=# create index on test_dump(id);
CREATE INDEX
postgres=# \d test_dump
Table "public.test_dump"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |
Indexes:
"test_dump_id_idx" btree (id)

postgres=# insert into test_dump values(1,'test');
INSERT 0 1
postgres=# insert into test_dump values(2,'handsome');
INSERT 0 1

同事使用的Fd格式,因为要导出的这个表比较大,而只有Fd支持并行

The “directory” format is the only format that supports parallel dumps.

那让我们也来同样操作一下,这里加上-v输出详细的备份信息

[postgres@xiongcc ~]$ pg_dump -Fd -t test_dump -f dump_dir -v 
pg_dump: last built-in OID is 16383
pg_dump: reading extensions
...
...
pg_dump: reading indexes
pg_dump: flagging indexes in partitioned tables
...
pg_dump: dumping contents of table "public.test_dump"

可以看到其实pg_dump是备份了索引的,另外也可以看一下备份出来doc文件。

有些老铁可能不知道pg_restore --list这个操作,这个其实可以查看Fd备份出来的文件信息的,另外还可以做很多灵活的操作,典型场景就是导出函数,比如:

pg_dump -U username --format=c --schema-only -f dump_test your_database
pg_restore --list dump_test | grep FUNCTION > function_list ---从list里面摘出来所有的函数
pg_restore -U username -d your_other_database -L function_list dump_test

当然也可以使用如下这个原始方式导出函数,通过SQL先查出所有的函数

[postgres@xiongcc ~]$ psql -At postgres > func_file.sql <<"__END__"
SELECT pg_get_functiondef(f.oid)
FROM pg_catalog.pg_proc f
INNER JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid)
WHERE n.nspname = 'public';
__END__

[postgres@xiongcc ~]$ cat func_file.sql
create or replace function loop_test_01() returns void
as $$
declare
n numeric := 0;
begin
loop
n := n + 1;
raise notice 'n 的当前值为: %',n;
exit when n >= 10;
-- return;
end loop;
end;
$$ language plpgsql;

扯远了,看下备份内容,里面确实有索引

[postgres@xiongcc ~]$ pg_restore --list dump_dir/
;
; Archive created at 2022-06-23 16:27:19 CST
; dbname: postgres
; TOC Entries: 8
; Compression: -1
; Dump Version: 1.14-0
; Format: DIRECTORY
; Integer: 4 bytes
; Offset: 8 bytes
; Dumped from database version: 15beta1
; Dumped by pg_dump version: 15beta1
;
;
; Selected TOC Entries:
;
218; 1259 16455 TABLE public test_dump postgres
3465; 0 16455 TABLE DATA public test_dump postgres
3321; 1259 16460 INDEX public test_dump_id_idx postgres

现在恢复一下试试

[postgres@xiongcc ~]$ pg_restore -t test_dump -d postgres dump_dir/
[postgres@xiongcc ~]$ psql
psql (15beta1)
Type "help" for help.

postgres=# \d test_dump
Table "public.test_dump"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |

可以看到索引确实没有!那么我的索引去了哪里?

后来经过一阵摸索,原来使用Fd的方式,使用-t只是指定了表,你还得指定-I,指定恢复索引,因为这二者属于不同的对象

If you use ​​-t​​​, you limit the operation to the table you named. Indices are different objects and need to be selected with the ​​-I​​​ (or ​​--index=​​) selector.

[postgres@xiongcc ~]$ psql -c "drop table test_dump"
DROP TABLE
[postgres@xiongcc ~]$ pg_restore -t test_dump -d postgres -I test_dump_id_idx dump_dir/
[postgres@xiongcc ~]$ psql
psql (15beta1)
Type "help" for help.

postgres=# \d test_dump
Table "public.test_dump"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |
Indexes:
"test_dump_id_idx" btree (id)

赶脚怪怪的。还是Fp好使,使用管道都不需要落地,pg_dump -Fp | psql,还能自己编辑查看备份SQL,不过文本格式不支持并行,这或许是plain格式的缺点了吧,而custom和directory支持调整顺序(如上)和默认压缩,所以这三种模式的区别,一定得知晓其中的坑。

小结

论影响的话,其实第一个情况比较坑人,稍不注意就可能造成生产事故,所以我决定在生产规范中添加一条

若开发发版需要删除表中的某些列,需要先执行SQL,查看是否存在包含该列的复合索引,因为删除列会级联删除所有包括该列的索引,并且没有任何的提示!可能会导致其他依赖该复合索引的查询使用顺序扫描,导致雪崩

另外,今天学徒群里我看到有人迫切想要PostgreSQL高可用相关的文章,比如HA对比、HA选型、各个HA的利弊等,后续我就把之前早就写好的发出来吧,师母已呆!

参考

​​https://dba.stackexchange.com/questions/251838/prevent-drop-column-with-index-on-it-in-postgresql​​

​​https://stackoverflow.com/questions/5347050/postgresql-sql-script-to-get-a-list-of-all-tables-that-have-a-particular-column​​

​​https://www.codegrepper.com/code-examples/sql/postgresql+search+all+tables+for+column+name​​

​​https://serverfault.com/questions/806271/pg-restore-on-a-single-table-not-restoring-indexes​​

​​https://stackoverflow.com/questions/13758003/how-to-take-backup-of-functions-only-in-postgres​​


举报

相关推荐

0 条评论