Skip to content

PostgreSQL SQL编写规范

命名规范

数据库对象命名

  • 使用小写字母:所有数据库对象名称(表、列、索引、函数等)使用小写字母
  • 使用下划线分隔:使用下划线作为单词分隔符,避免使用驼峰命名法
  • 避免保留字:避免使用 PostgreSQL 保留字作为对象名称
  • 限制长度:名称长度不宜过长,建议不超过30个字符
  • 有意义的名称:使用描述性强、易于理解的名称

具体对象命名规则

对象类型命名规则示例
数据库项目名称或业务名称ecommerce, blog_db
模式功能模块名称public, sales, warehouse
名词复数形式users, orders, products
名词或形容词+名词user_id, order_date, product_name
主键表名_ididuser_id, id
外键关联表名_iduser_id, product_id
索引idx_表名_列名idx_users_email, idx_orders_status_created_at
唯一索引uk_表名_列名uk_users_email, uk_products_sku
函数动词+名词get_user_by_id, calculate_total_amount
存储过程动词+名词process_order, send_notification
视图v_表名v_业务名称v_active_users, v_sales_report
物化视图mv_表名mv_业务名称mv_daily_sales, mv_product_inventory
序列表名_列名_sequsers_id_seq, orders_order_no_seq

示例

sql
-- 推荐
CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX uk_users_email ON users(email);

-- 不推荐
CREATE TABLE Users (
    UserID SERIAL PRIMARY KEY,
    Email VARCHAR(255) NOT NULL,
    PasswordHash VARCHAR(255) NOT NULL,
    CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

语法规范

大小写规则

  • 关键字大写:SQL 关键字(SELECT、FROM、WHERE、JOIN等)使用大写
  • 函数名大写:内置函数名(CURRENT_TIMESTAMP、COUNT、SUM等)使用大写
  • 对象名小写:数据库对象名称(表、列、索引等)使用小写
  • 值和变量小写:字符串值、数值、变量等使用小写(除非有特殊要求)

缩进和换行

  • 使用4个空格缩进:避免使用制表符
  • 关键字后换行:SELECT、FROM、WHERE、JOIN等关键字后换行
  • 逗号前置:逗号放在列名或表达式的前面,便于添加、删除或注释列
  • 合理使用空格:在操作符两侧、逗号后添加空格
  • 长查询分行:长查询语句应适当分行,提高可读性

示例

sql
-- 推荐
SELECT
    u.user_id
    ,u.email
    ,u.created_at
    ,COUNT(o.order_id) AS order_count
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE u.created_at >= '2023-01-01'
GROUP BY
    u.user_id
    ,u.email
    ,u.created_at
HAVING COUNT(o.order_id) > 5
ORDER BY
    u.created_at DESC
    ,order_count DESC
LIMIT 10;

-- 不推荐
select u.user_id, u.email, u.created_at, count(o.order_id) as order_count from users u join orders o on u.user_id = o.user_id where u.created_at >= '2023-01-01' group by u.user_id, u.email, u.created_at having count(o.order_id) > 5 order by u.created_at desc, order_count desc limit 10;

注释规范

  • 单行注释:使用 -- 进行单行注释
  • 多行注释:使用 /* */ 进行多行注释
  • 注释位置:在复杂查询、函数、存储过程前添加注释
  • 清晰的注释内容:注释应简洁明了,说明代码的功能和用途

示例

sql
-- 单行注释:查询活跃用户数量
SELECT COUNT(*) FROM users WHERE is_active = true;

/*
 * 多行注释:
 * 计算每月销售总额
 * 按月份分组并排序
 */
SELECT
    DATE_TRUNC('month', order_date) AS month
    ,SUM(total_amount) AS total_sales
FROM orders
GROUP BY month
ORDER BY month;

查询编写最佳实践

SELECT 语句

  • 只选择需要的列:避免使用 SELECT *,只选择实际需要的列
  • 使用表别名:在多表查询中使用简洁的表别名
  • 避免重复计算:使用 CTE 或子查询避免重复计算
  • 使用 LIMIT 限制结果集:在测试或分页查询中使用 LIMIT 限制返回行数

WHERE 子句

  • 优先使用索引列:将索引列放在 WHERE 子句的前面
  • 避免在索引列上使用函数:如 DATE(created_at) = '2023-01-01' 应改为 created_at BETWEEN '2023-01-01' AND '2023-01-01 23:59:59'
  • 使用 IN 替代 OR:当条件较多时,使用 IN 替代多个 OR 条件
  • 避免使用 NOT IN:NOT IN 可能导致全表扫描,考虑使用 LEFT JOIN 或 NOT EXISTS 替代

JOIN 语句

  • 使用明确的 JOIN 类型:优先使用 INNER JOIN、LEFT JOIN 等明确的 JOIN 类型,避免使用隐式 JOIN
  • 限制 JOIN 表的数量:JOIN 表的数量不宜过多,建议不超过5个
  • 使用 ON 子句指定连接条件:在 JOIN 子句中明确指定连接条件
  • 避免笛卡尔积:确保每个 JOIN 都有适当的连接条件

GROUP BY 和 HAVING

  • GROUP BY 列完整:GROUP BY 子句应包含 SELECT 列表中所有非聚合列
  • 使用 HAVING 过滤聚合结果:使用 HAVING 过滤聚合结果,而不是 WHERE
  • 避免在 HAVING 中使用非聚合列:HAVING 子句应只包含聚合函数或 GROUP BY 列

ORDER BY

  • 使用索引列排序:ORDER BY 子句应优先使用索引列
  • 避免使用 SELECT * 并 ORDER BY:当使用 ORDER BY 时,避免使用 SELECT *
  • 考虑 NULL 值排序:明确指定 NULL 值的排序方式(NULLS FIRST 或 NULLS LAST)

示例

sql
-- 推荐:只选择需要的列,使用表别名,使用明确的 JOIN 类型
SELECT
    u.user_id
    ,u.email
    ,o.order_id
    ,o.order_date
    ,o.total_amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
WHERE u.created_at >= '2023-01-01'
ORDER BY o.order_date DESC
LIMIT 10;

-- 不推荐:使用 SELECT *,隐式 JOIN,在索引列上使用函数
SELECT *
FROM users, orders
WHERE users.user_id = orders.user_id
AND DATE(users.created_at) = '2023-01-01'
ORDER BY orders.order_date;

数据修改语句

INSERT 语句

  • 指定列名:明确指定插入的列名,避免依赖列顺序
  • 使用批量插入:对于大量数据插入,使用批量插入提高性能
  • 使用 RETURNING 子句:需要返回插入结果时,使用 RETURNING 子句
  • 考虑默认值:合理使用默认值,减少插入语句的长度

UPDATE 语句

  • 使用 WHERE 子句:UPDATE 语句必须包含 WHERE 子句,避免误更新全表
  • 使用 LIMIT 限制更新行数:在测试或特定场景下,使用 LIMIT 限制更新行数
  • 使用 RETURNING 子句:需要返回更新结果时,使用 RETURNING 子句
  • 避免更新主键:尽量避免更新主键值

DELETE 语句

  • 使用 WHERE 子句:DELETE 语句必须包含 WHERE 子句,避免误删除全表
  • 使用 LIMIT 限制删除行数:在测试或特定场景下,使用 LIMIT 限制删除行数
  • 考虑使用 TRUNCATE:对于清空表数据,考虑使用 TRUNCATE 替代 DELETE
  • 注意外键约束:删除数据时,注意外键约束的影响

示例

sql
-- 推荐:指定列名,使用 RETURNING 子句
INSERT INTO users (email, password_hash) 
VALUES ('test@example.com', 'hashed_password')
RETURNING user_id;

-- 推荐:使用 WHERE 子句,明确指定条件
UPDATE users 
SET email = 'new_email@example.com', updated_at = CURRENT_TIMESTAMP
WHERE user_id = 1
RETURNING user_id, email;

-- 推荐:使用 WHERE 子句,限制删除行数
DELETE FROM users 
WHERE created_at < '2022-01-01' AND is_active = false
LIMIT 1000;

-- 不推荐:不指定列名,依赖列顺序
INSERT INTO users VALUES (DEFAULT, 'test@example.com', 'hashed_password', DEFAULT, DEFAULT);

-- 不推荐:没有 WHERE 子句,可能误更新全表
UPDATE users SET is_active = false;

性能考虑

索引使用

  • 创建合适的索引:根据查询模式创建合适的索引
  • 避免过多索引:每个表的索引数量不宜过多,建议不超过5个
  • 考虑复合索引:对于经常一起查询的列,考虑创建复合索引
  • 定期重建索引:对于频繁更新的表,定期重建索引以提高性能

查询优化

  • 使用 EXPLAIN ANALYZE:分析查询执行计划,优化慢查询
  • 避免全表扫描:通过创建索引或优化查询条件,避免全表扫描
  • 使用 CTE 或子查询:将复杂查询拆分为简单的 CTE 或子查询
  • 避免递归查询:除非必要,避免使用递归查询

事务管理

  • 保持事务简短:事务应尽可能简短,减少锁持有时间
  • 避免长事务:长事务会导致锁争用和 MVCC 膨胀
  • 使用合适的隔离级别:根据业务需求选择合适的隔离级别
  • 使用 SAVEPOINT:在复杂事务中使用 SAVEPOINT,便于回滚部分操作

示例

sql
-- 推荐:使用 EXPLAIN ANALYZE 分析查询
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

-- 推荐:使用 CTE 拆分复杂查询
WITH recent_orders AS (
    SELECT order_id, user_id, total_amount
    FROM orders
    WHERE order_date >= '2023-01-01'
)
SELECT
    u.user_id
    ,u.email
    ,ro.total_amount
FROM users u
JOIN recent_orders ro ON u.user_id = ro.user_id;

安全考虑

防止 SQL 注入

  • 使用参数化查询:使用参数化查询或预处理语句,避免直接拼接 SQL
  • 验证输入:对用户输入进行严格验证和过滤
  • 使用最小权限原则:数据库用户应只拥有必要的权限
  • 避免使用动态 SQL:除非必要,避免使用动态 SQL

数据保护

  • 加密敏感数据:对敏感数据(如密码、信用卡号)进行加密存储
  • 使用视图限制数据访问:使用视图限制用户对敏感数据的访问
  • 审计重要操作:对重要操作(如用户登录、数据修改)进行审计
  • 定期备份数据:定期备份数据库,确保数据安全

示例

sql
-- 推荐:使用参数化查询
PREPARE stmt1 (VARCHAR) AS
SELECT * FROM users WHERE email = $1;
EXECUTE stmt1('test@example.com');
DEALLOCATE stmt1;

-- 推荐:使用视图限制数据访问
CREATE VIEW user_public_info AS
SELECT user_id, email, created_at
FROM users
WHERE is_active = true;

-- 不推荐:直接拼接 SQL,容易导致 SQL 注入
SELECT * FROM users WHERE email = '' || $email || '';

版本特定语法

PostgreSQL 12+ 特性

  • 表分区增强:支持声明式分区,简化分区表管理
  • JSONB 增强:支持 JSONB 路径查询
  • 索引增强:支持覆盖索引

PostgreSQL 13+ 特性

  • 并行查询增强:支持更多并行查询场景
  • 逻辑复制增强:支持发布-订阅模式
  • 真空增强:支持并行真空

PostgreSQL 14+ 特性

  • JSONB 增强:支持 JSONB 子脚本语法
  • 索引增强:支持多值索引
  • 事务增强:支持事务级别的临时表

PostgreSQL 15+ 特性

  • 权限增强:支持行级安全策略
  • 查询增强:支持 MERGE 语句
  • 性能增强:支持增量排序

PostgreSQL 16+ 特性

  • 并行查询增强:支持更多并行执行场景
  • 索引增强:支持表达式索引的统计信息
  • 安全性增强:支持更细粒度的权限控制

代码审查要点

语法检查

  • 检查 SQL 语法是否正确
  • 检查命名规范是否符合要求
  • 检查缩进和换行是否规范
  • 检查注释是否清晰完整

性能检查

  • 检查是否存在慢查询
  • 检查索引使用是否合理
  • 检查 JOIN 表的数量是否过多
  • 检查是否存在全表扫描

安全性检查

  • 检查是否存在 SQL 注入风险
  • 检查是否使用了最小权限原则
  • 检查敏感数据是否加密
  • 检查是否存在审计机制

可读性检查

  • 检查代码是否易于理解
  • 检查命名是否清晰易懂
  • 检查注释是否充分
  • 检查代码结构是否合理

常见问题 (FAQ)

为什么不推荐使用 SELECT *?

  • 性能问题:SELECT * 会返回所有列,包括不需要的列,增加网络传输和内存消耗
  • 维护问题:当表结构发生变化时,SELECT * 可能导致应用程序出现问题
  • 可读性问题:SELECT * 使查询结果的列不明确,降低代码可读性

如何处理 NULL 值?

  • 使用 COALESCE 函数:将 NULL 值转换为默认值
  • 使用 IS NULL 或 IS NOT NULL:明确检查 NULL 值
  • 在 ORDER BY 中指定 NULLS FIRST 或 NULLS LAST:控制 NULL 值的排序方式
  • 考虑使用 NOT NULL 约束:对于不应该为 NULL 的列,添加 NOT NULL 约束

什么时候使用索引?

  • 经常用于 WHERE 条件的列
  • 经常用于 JOIN 条件的列
  • 经常用于 ORDER BY 或 GROUP BY 的列
  • 区分度高的列(如 email、ID 等)

如何优化慢查询?

  • 使用 EXPLAIN ANALYZE 分析执行计划
  • 创建合适的索引
  • 优化查询条件,避免全表扫描
  • 减少 JOIN 表的数量
  • 使用 CTE 或子查询拆分复杂查询

如何防止 SQL 注入?

  • 使用参数化查询或预处理语句
  • 对用户输入进行严格验证和过滤
  • 避免直接拼接 SQL
  • 使用最小权限原则

什么时候使用事务?

  • 需要保证多个操作的原子性时
  • 需要保证数据一致性时
  • 需要回滚操作时
  • 涉及多个表的修改时

如何选择合适的隔离级别?

  • READ COMMITTED:默认隔离级别,适用于大多数场景
  • REPEATABLE READ:适用于需要一致读取的场景
  • SERIALIZABLE:最高隔离级别,适用于需要严格一致性的场景
  • READ UNCOMMITTED:最低隔离级别,可能读取到未提交的数据,不推荐使用

如何管理数据库对象权限?

  • 使用角色管理权限
  • 遵循最小权限原则
  • 定期审查权限设置
  • 为不同用户设置不同的权限

总结

SQL 编写规范是保证数据库代码质量和可维护性的重要手段。通过遵循统一的命名规范、语法规范和最佳实践,可以提高 SQL 代码的可读性、性能和安全性。

在编写 SQL 代码时,需要考虑性能、安全性和可维护性等方面,同时关注 PostgreSQL 不同版本的特性和语法差异。定期进行代码审查,及时发现和修复问题,也是保证 SQL 代码质量的重要环节。

通过不断学习和实践 SQL 编写规范,可以提高数据库开发的效率和质量,为业务发展提供可靠的数据支持。