PG高效去重

方案 1:使用 DISTINCT ON(PostgreSQL 特有,推荐)

-- 只保留每个 id 的第一条
SELECT DISTINCT ON (id) * FROM t 
ORDER BY id, ctid;

方案 2:使用 ROW_NUMBER(推荐)

-- 查看重复行
WITH ranked AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn
    FROM t
)
SELECT * FROM ranked WHERE rn > 1;

-- 删除重复行
WITH ranked AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn
    FROM t
)
DELETE FROM t 
WHERE ctid IN (SELECT ctid FROM ranked WHERE rn > 1);

优点: SQL 标准,可读性强,更灵活
缺点: 需要配合 ctid 来实现删除

方案 3:使用 CTID

-- 查看重复行
SELECT * FROM t a 
WHERE a.ctid <> (
    SELECT MIN(b.ctid) 
    FROM t b 
    WHERE a.id = b.id
);

-- 删除重复行
DELETE FROM t 
WHERE ctid <> (
    SELECT MIN(b.ctid) 
    FROM t b 
    WHERE t.id = b.id
);

优点: 快速,直接操作物理位置
缺点: PostgreSQL 特有语法,不够优雅

方案 4:使用 NOT IN(简单但低效)

-- 保留 id 最小的一条
DELETE FROM t 
WHERE id NOT IN (
    SELECT MIN(id) 
    FROM t 
    GROUP BY id
);

缺点: 如果 id 相同但其他字段不同,逻辑可能有问题


postgres启动服务状态卡前台窗口

问题描述

执行systemct start postgresql-16.service,前台窗口一直不退出。检查postgres服务状态,运行正常。

问题分析

症状就是 systemctl start 一直卡在前台窗口。
因为 systemd 没收到 PostgreSQL 服务启动完成的状态通知,才会一直卡在前台窗口。这种问题主要出现在自编译安装或第三方安装的postgres数据库中,如编译过程中没有添加参数 --with-systemd

cat /usr/lib/systemd/system/postgresql-16.service
[Unit]
Description=PostgreSQL 16 database server
Documentation=https://www.postgresql.org/docs/16/static/
After=network.target
[Service]
Type=notify #问题点,编译未添加 --with-systemd
User=postgres
Group=postgres
Environment=PGDATA=/xxx/pgdata/
ExecStart=/xxx/bin/postgres -D ${PGDATA}
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=0
OOMScoreAdjust=-1000
Environment=PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj
Environment=PG_OOM_ADJUST_VALUE=0
[Install]
WantedBy=multi-user.target

问题解决

将notify更改为simple, systemd 只要看到主进程postgres启动就当服务已启动,不等待额外通知。
[Service]
Type=simple

企业CxO数据库选型应该回答清楚的 15 个问题

1、这款数据库的发展历史
2、是不是适合我们当前的场景
3、是不是符合长期发展需求(数据量维度, 数据模型维度, 数据类型, 计算, 检索维度)
4、公司内部有没有会用这款产品的, 有多深
5、公司内部有没有有没有熟悉这个产品的数据库架构师
6、有没有会管理、优化的
7、有没有开发依赖的生态产品, 是否符合公司技术栈, 使用这个产品的时间成本。
8、有没有外部商业化售后服务
9、是否符合行业合规要求
10、有哪些用户在用这个产品
11、数据库源代码生命力 (开源社区的运作逻辑, 是否可以长久运作, 核心组组成,为什么贡献,提交代码的流程,代码质量,全球有多少内核开发者, 有多少国家在贡献, 代码掌握在谁手里, 开源许可, 有没有那个国家控制, 有没有那个公司控制)
12、这款数据库处于什么发展周期(上升, 下降?)
13、这款数据库未来的发展方向
14、以上数据如何量化,对比其他数据库, 有没有更合适的其他数据库产品
15、这款数据库的人才库分布如何?
1)应用开发(SQL)人才
2)应用框架开发人才
3)管理人才
4)数据库架构师人才
5)数据库底座内核开发
6)数据库应用内核开发
7)数据库服务提供商

索引使用的前提

首先,是前面提到的Access Method, 然后是使用的operator class, 以及opc中定义的operator或function;
其次,遵循CBO的选择
#seq_page_cost = 1.0
#random_page_cost = 4.0
#cpu_tuple_cost = 0.01
#cpu_index_tuple_cost = 0.005
#cpu_operator_cost = 0.0025
#effective_cache_size = 128MB
最后,遵循完CBO的选择, 还需要符合当前配置的Planner 配置
#enable_bitmapscan = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
#enable_material = on
#enable_mergejoin = on
#enable_nestloop = on
#enable_seqscan = on
#enable_sort = on
#enable_tidscan = on

函数建分区表

按周生成分区表

do                                                                                                               
$$
DECLARE base text; --生命sql类型

pgsqltest text; --字符串为文本类型,执行函数


i int; --i为整数
BEGIN
base = 'create table main_history_p_%s partition of main_history_p for values FROM (''%s'') to (''%s'')';


FOR i IN 0..11 loop --不是左闭右开
pgsqltest = format(base,
to_char('2024-01-01'::timestamp + (i || 'week')::INTERVAL, --第一个%s占位符
'YYYYMMDD'),
'2024-01-01'::timestamp + (i || 'week')::INTERVAL, --第二个%s占位符
'2024-01-01'::timestamp + (i + 1 || 'week')::INTERVAL); --第三个%s占位符
--raise notice '%', sqlstring;
EXECUTE pgsqltest; --执行sqlstring
END loop; --结束loop
END --结束begin
$$language plpgsql; --结束函数

按月生成分区表

do                                                                                                               
$$
DECLARE base text; --生命sql类型

pgsqltest text; --字符串为文本类型,执行函数

i int; --i为整数
BEGIN
base = 'create table main_history_p_%s partition of main_history_p for values FROM (''%s'') to (''%s'')';

FOR i IN 0..11 loop --不是左闭右开
pgsqltest = format(base,
to_char('2024-01-01'::timestamp + (i || 'month')::INTERVAL, --第一个%s占位符
'YYYYMMDD'),
'2024-01-01'::timestamp + (i || 'month')::INTERVAL, --第二个%s占位符
'2024-01-01'::timestamp + (i + 1 || 'month')::INTERVAL); --第三个%s占位符
--raise notice '%', sqlstring;
EXECUTE pgsqltest; --执行sqlstring
END loop; --结束loop
END --结束begin
$$language plpgsql; --结束函数

PL/pgSQL编写造数据脚本

1.编写SQL脚本,插入到main_history表

1.1 创建表

CREATE TABLE main_history (
amount int4 NULL,
"content" varchar NULL,
main_id int4 NULL
);

1.2 批量插入

--_configList 使用 “_” 前缀来标识变量,用于区分sql中的字段 
CREATE OR REPLACE FUNCTION batchInsert(_configList varchar[][], _main_id int) RETURNS void AS $$
DECLARE
_config varchar[];
_content varchar;
_amount int;
BEGIN
--获取二维数组的每个一维数组
FOREACH _config SLICE 1 IN ARRAY (_configList) LOOP
_content := _config[1];
_amount := _config[2];
--把变量输出到控制台
RAISE NOTICE 'config = %, content = %, amount = % main_id = %', _config, _content, _amount, _main_id;
--用变量拼接sql语句并且实际运行在server上
INSERT INTO main_history (amount, content, main_id) VALUES (_amount, _content, _main_id);
END LOOP;
END;
$$ LANGUAGE plpgsql;
--使用函数
select batchInsert(ARRAY[['ccontent1', '1'], ['content2', '2']], 1);
--删除函数
drop function batchInsert;

2. 函数说明

2.1 声明数组

声明的时候可以不额外区分一维、二维数组
_configList varchar[][] 和 _configList varchar[] 是一样的

2.2 数组赋值

声明为varchar后,赋值时也要是varchar类型。
_configList varchar[][] := (ARRAY[[‘ccontent1’, ‘1’], [‘content2’, ‘2’]]);

2.3 数组遍历

二维数组的遍历
–_configList 由外部传入, 而FOREACH SLICE IN ARRAY都是关键字
FOREACH _config SLICE 1 IN ARRAY (_configList) LOOP
_content := _config[1];
_amount := _config[2];
END LOOP;

PG无法释放空间问题分析

近期对PG数据库的两张分区表进行数据删除操作,近40G数据。当PG的两张分区表完成操作后,执行vaccum full table发现磁盘空间无任何的变动。

操作步骤:

1、建表导入无数据。例如:
CREATE TABLE t1 AS SELECT * FROM t2 WITH NO DATA;

2、导入数据。例如:
insert into t1 select * from t2 where create_time > ‘2021-01-01’ and create_time < ‘2021-07-01’;

3、校验导入的数据量。例如:
select count(id) from t1;

4、删除数据并校验。例如:
delete from t1 where create_time > ‘2020-07-01’ and create_time < ‘2021-01-01’;

5、释放磁盘空间。例如:
VACUUM FULL t1

执行了vaccum full t1无任何的变化。经过分析,大表进行分区操作后,需要对每张分区表执行vaccum full t1_01,
vaccum full t1_02 至到
vaccum full t1_0x最后一张表。

数据库设计

一、数据库设计数据库的流程

一.数据设计流程模型

1.现实世界的实体模型通过建模转换为信息世界的概念模型(即E-R模型)

2.概念模型经过模型转化,得到数据库世界使用的数据模型(在关系数据库设计中为关系模型)

3.数据库模型进一步规范化,得到数据库结构模型

1. E-R(entity-)模型图

1.1 E-R图的组成要素:

• 实体(entity)显示世界客观存在,并区别于其他对象的“事件”或“物体”

• 属性(attribute)每个实体的特征

• 主键(key) 能唯一区分实体的属性

• 联系(relationship)实体与实体之间的联系,有1:1,1:n,m:n

2 E-R图转换到关系模式:

对于E-R图中每个实体,都应转换为一个关系

该关系应包括

◇ 对应实体的全部属性,

◇ 并应根据关系所表达的语义确定哪个属性或哪几个属性组作为“关键字”,关键字用下划线标识。

2.1 关系模型中常用的概念:

关系:可以理解为一张二维表,每个关系都具有一个关系名,就是通常说的表名

元组:可以理解为二维表中的一行,在数据库中经常被称为记录

属性:可以理解为二维表中的一列,在数据库中经常被称为字段

:属性的取值范围,也就是数据库中某一列的取值限制

关键字:一组可以唯一标识元祖的属性,数据库中常称为主键,由一个或多个列组成

关键模式:指对关系的描述。其格式为:关系名(属性1,属性2,… …,属性N),在数据库中成为表结构主键用下划线标出

2.2 E-R图转换到关系模式

1) 1:1联系
2) (1:n联系)
3) E-R图转换到关系模式(m:n联系)

3. 数据库设计规范化,得到数据结构模型

1)第一范式(1nd NF)

1. 第一范式的目标是确保每列的原子性

2. 如果每列都是不可再分的最小数据单元(最小的原子单元),则满足第一范式。

2)第二范式

1. 如果一个关系满足1NF,并且出任何一个非主键字段的数据都要依赖于主键字段,则满足第二范式(2NF)

2. 第二范式要求每个表只描述一件事情

3)第三范式(3rd NF)

1. 若一个关系满足第一范式(1NF)和第二范式(2NF),任何两个非主键字段的数值之间不存在函数依赖关系,则满足第三范式(3NF)。

pg优化方法

优化具体方法

  1、SQL后面添加limit

  2、禁用select *

  3、优化like语句

  4、避免在索引列上使用内置函数和表达式操作

  5、对查询进行优化,应考虑在 where 及 order by 涉及的列上建立索引,尽量避免全表扫描

  6、在适当的时候,使用only indexscan

  7、避免排序

    7.1、灵活使用集合运算符的 ALL 可选项
    7.2、使用 EXISTS 代替 DISTINCT
    7.3、在极值函数中使用索引(MAX/MIN)
    7.4、在 GROUP BY 子句和 ORDER BY 子句中使用索引

  8、删除冗余和重复索引

  9、关于大量DELETE/UPDATE操作

  10、where 子句中考虑使用默认值代替 null

  11、合理使用exists&in

  12、能写在 WHERE 子句里的条件不要写在 HAVING 子句里

  13、用varchar代替char,合理设置varchar可变字段长度

  14、where后字段值注意引号使用,易导致索引失效

  15、当在 SQL 语句中连接多个表时,请使用表的别名,并把别名前缀于每一列上,这样语义更加清晰

  16、索引不适合建在有大量重复数据的字段上,如性别这类型数据库字段

  17、表关联不要太多

  18、Inner join 、left join、right join,优先使用 Inner join,如果是 left join,左边表结果尽量小

    18.1、分解关联查询
    18.2、改写关联查询

  19、减少中间表

    19.1、灵活使用 HAVING 子句
    19.2、需要对多个字段使用 IN 谓词时,将它们汇总到一处

  20、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描

  21、使用多列索引时,注意索引列的顺序,一般遵循最左匹配原则

  22、字段类型能用数值尽量用数值类型

  23、长度很长的多字段联合主键用hash

  24、禁用UUID作为主键

  25、善用set、explain查看&调试执行计划