Skip to content

OceanBase SQL开发规范

命名规范

1. 数据库对象命名

表命名

  • 使用小写字母下划线组合
  • 命名应具有明确的业务含义
  • 避免使用保留字关键字
  • 建议使用单数形式(如:user 而非 users
  • 表名长度不超过30个字符

示例

sql
-- 推荐
CREATE TABLE `user_info` (...);
CREATE TABLE `order_detail` (...);

-- 不推荐
CREATE TABLE `UserInfo` (...); -- 大小写混合
CREATE TABLE `users` (...); -- 复数形式
CREATE TABLE `select` (...); -- 关键字

列命名

  • 使用小写字母下划线组合
  • 命名应具有明确的业务含义
  • 避免使用保留字关键字
  • 主键建议使用 id表名_id(如:user_id
  • 外键建议使用 关联表名_id(如:user_id
  • 时间字段使用明确的后缀(如:create_time, update_time

示例

sql
-- 推荐
id INT PRIMARY KEY,
user_name VARCHAR(50),
create_time DATETIME,
order_id INT,

-- 不推荐
userName VARCHAR(50), -- 驼峰命名
select VARCHAR(50), -- 关键字
ctime DATETIME, -- 缩写不明确

索引命名

  • 主键索引:PRIMARY KEY
  • 唯一索引:uk_表名_列名1_列名2
  • 普通索引:idx_表名_列名1_列名2
  • 联合索引:包含所有涉及的列名,按重要性排序
  • 避免使用过长的索引名

示例

sql
-- 推荐
CREATE UNIQUE INDEX `uk_user_phone` ON `user_info` (`phone`);
CREATE INDEX `idx_order_user_time` ON `order_detail` (`user_id`, `create_time`);

-- 不推荐
CREATE UNIQUE INDEX `unique_phone` ON `user_info` (`phone`); -- 缺少表名
CREATE INDEX `index1` ON `order_detail` (`user_id`, `create_time`); -- 无意义命名

2. 变量和参数命名

存储过程/函数参数

  • 使用小写字母下划线组合
  • 建议添加类型前缀(如:p_ 表示参数)
  • 命名应具有明确的业务含义

示例

sql
-- 推荐
CREATE PROCEDURE `proc_update_user`(
  p_user_id INT,
  p_user_name VARCHAR(50)
)
BEGIN
  -- 存储过程逻辑
END;

-- 不推荐
CREATE PROCEDURE `proc_update_user`(
  userid INT,
  username VARCHAR(50)
)
BEGIN
  -- 存储过程逻辑
END;

局部变量

  • 使用小写字母下划线组合
  • 建议添加类型前缀(如:v_ 表示变量)
  • 命名应具有明确的业务含义

示例

sql
-- 推荐
DECLARE v_user_count INT;
DECLARE v_current_time DATETIME;

-- 不推荐
DECLARE count INT;
DECLARE time DATETIME;

语法规范

1. SQL语句格式

缩进和换行

  • 使用4个空格进行缩进
  • 关键字大写,如:SELECT, FROM, WHERE, JOIN
  • 每个子句单独一行
  • 长SQL语句适当换行
  • 逗号放在行尾

示例

sql
-- 推荐
SELECT
    u.id,
    u.user_name,
    o.order_no,
    o.amount
FROM
    user_info u
JOIN
    order_detail o ON u.id = o.user_id
WHERE
    u.status = 1
    AND o.create_time >= '2024-01-01'
ORDER BY
    o.create_time DESC;

-- 不推荐
select u.id,u.user_name,o.order_no,o.amount from user_info u join order_detail o on u.id=o.user_id where u.status=1 and o.create_time>='2024-01-01' order by o.create_time desc;

注释规范

  • 使用--进行单行注释
  • 使用/* */进行多行注释
  • 复杂SQL语句必须添加注释
  • 注释应简洁明了,说明业务逻辑

示例

sql
-- 查询活跃用户的订单信息
SELECT
    u.id,
    u.user_name,
    o.order_no,
    o.amount
FROM
    user_info u
JOIN
    order_detail o ON u.id = o.user_id
WHERE
    u.status = 1 -- 1表示活跃状态
    AND o.create_time >= '2024-01-01';

2. 数据类型规范

选择合适的数据类型

  • 优先使用精确数据类型(如:INT 而非 VARCHAR 存储数字)
  • 避免使用过大的数据类型(如:BIGINT 存储年龄)
  • 时间类型使用 DATETIMETIMESTAMP
  • 字符串类型根据实际长度选择 VARCHARCHAR

示例

sql
-- 推荐
age INT, -- 年龄使用INT
phone VARCHAR(15), -- 手机号使用VARCHAR
create_time DATETIME, -- 创建时间使用DATETIME
status TINYINT, -- 状态使用TINYINT

-- 不推荐
age VARCHAR(3), -- 年龄使用VARCHAR
phone BIGINT, -- 手机号使用BIGINT
create_time VARCHAR(20), -- 创建时间使用VARCHAR
status INT, -- 状态使用INT(值仅为0或1)

避免隐式类型转换

  • 确保查询条件中的数据类型与表结构一致
  • 避免在索引列上进行类型转换

示例

sql
-- 推荐
SELECT * FROM user_info WHERE phone = '13800138000'; -- 与VARCHAR类型一致

-- 不推荐
SELECT * FROM user_info WHERE phone = 13800138000; -- 隐式类型转换,无法使用索引

性能优化规范

1. 查询优化

避免全表扫描

  • 为查询条件中的列创建合适的索引
  • 避免使用 SELECT *,只查询必要的列
  • 合理使用 LIMIT 限制返回行数

示例

sql
-- 推荐
SELECT id, user_name FROM user_info WHERE status = 1 LIMIT 100;

-- 不推荐
SELECT * FROM user_info WHERE status = 1; -- 全表扫描,返回不必要的列

优化JOIN查询

  • 优先使用INNER JOIN,避免OUTER JOIN
  • 小表驱动大表
  • 为JOIN条件中的列创建索引
  • 避免超过3个表的JOIN

示例

sql
-- 推荐
SELECT
    u.id,
    u.user_name,
    o.order_no
FROM
    user_info u -- 小表
JOIN
    order_detail o ON u.id = o.user_id -- 大表
WHERE
    u.status = 1;

-- 不推荐
SELECT
    u.id,
    u.user_name,
    o.order_no,
    p.product_name,
    a.address
FROM
    user_info u
LEFT JOIN
    order_detail o ON u.id = o.user_id
LEFT JOIN
    product p ON o.product_id = p.id
LEFT JOIN
    address a ON u.id = a.user_id
WHERE
    u.status = 1;

优化子查询

  • 避免使用相关子查询
  • 考虑使用JOIN替代子查询
  • 合理使用EXISTS替代IN

示例

sql
-- 推荐
SELECT u.id, u.user_name
FROM user_info u
WHERE EXISTS (
    SELECT 1 FROM order_detail o WHERE o.user_id = u.id
);

-- 不推荐
SELECT u.id, u.user_name
FROM user_info u
WHERE u.id IN (
    SELECT o.user_id FROM order_detail o WHERE o.amount > 100
);

2. 索引优化

索引设计原则

  • WHEREJOINORDER BYGROUP BY中的列创建索引
  • 联合索引最左前缀原则
  • 避免过多的索引(每个表不超过5个)
  • 避免在频繁更新的列上创建索引

示例

sql
-- 推荐:联合索引,遵循最左前缀原则
CREATE INDEX `idx_order_user_time` ON `order_detail` (`user_id`, `create_time`);

-- 查询时使用最左前缀
SELECT * FROM order_detail WHERE user_id = 1 AND create_time >= '2024-01-01';

-- 不推荐:创建过多索引
CREATE INDEX `idx_order_user` ON `order_detail` (`user_id`);
CREATE INDEX `idx_order_time` ON `order_detail` (`create_time`);
CREATE INDEX `idx_order_amount` ON `order_detail` (`amount`);
CREATE INDEX `idx_order_status` ON `order_detail` (`status`);
CREATE INDEX `idx_order_product` ON `order_detail` (`product_id`);

避免索引失效

  • 避免在索引列上使用函数
  • 避免使用**!=<>**、IS NULLIS NOT NULL
  • 避免使用LIKE '%xxx'(前缀模糊查询)
  • 避免使用OR连接条件(除非所有条件列都有索引)

示例

sql
-- 推荐
SELECT * FROM user_info WHERE user_name LIKE '张%'; -- 后缀模糊查询,可使用索引
SELECT * FROM user_info WHERE user_id = 1 OR user_id = 2; -- IN替代OR

-- 不推荐
SELECT * FROM user_info WHERE SUBSTRING(user_name, 1, 1) = '张'; -- 函数导致索引失效
SELECT * FROM user_info WHERE user_name LIKE '%张%'; -- 前缀模糊查询,索引失效
SELECT * FROM user_info WHERE status != 1; -- !=导致索引失效
SELECT * FROM user_info WHERE user_id = 1 OR phone = '13800138000'; -- OR导致索引失效

3. 写入优化

批量操作

  • 使用批量INSERT替代单条INSERT
  • 批量操作的数量控制在1000条以内
  • 避免频繁的提交事务

示例

sql
-- 推荐
INSERT INTO user_info (user_name, phone, status) VALUES
('用户1', '13800138001', 1),
('用户2', '13800138002', 1),
('用户3', '13800138003', 1);

-- 不推荐
INSERT INTO user_info (user_name, phone, status) VALUES ('用户1', '13800138001', 1);
INSERT INTO user_info (user_name, phone, status) VALUES ('用户2', '13800138002', 1);
INSERT INTO user_info (user_name, phone, status) VALUES ('用户3', '13800138003', 1);

避免大事务

  • 拆分大事务为多个小事务
  • 减少事务中的操作数量
  • 缩短事务的执行时间

示例

sql
-- 推荐:拆分大事务
START TRANSACTION;
-- 操作1
COMMIT;

START TRANSACTION;
-- 操作2
COMMIT;

-- 不推荐:大事务
START TRANSACTION;
-- 操作1
-- 操作2
-- ...
-- 操作1000
COMMIT;

事务处理规范

1. 事务使用原则

明确事务边界

  • 为所有写操作添加事务
  • 明确事务的开始结束
  • 避免长事务

示例

sql
-- 推荐
START TRANSACTION;
UPDATE user_info SET balance = balance - 100 WHERE id = 1;
UPDATE order_detail SET status = 2 WHERE id = 1;
COMMIT;

-- 不推荐
-- 缺少事务,可能导致数据不一致
UPDATE user_info SET balance = balance - 100 WHERE id = 1;
UPDATE order_detail SET status = 2 WHERE id = 1;

合理设置事务隔离级别

  • 根据业务需求选择合适的隔离级别
  • 大多数业务使用 READ COMMITTED
  • 避免使用 SERIALIZABLE 隔离级别

示例

sql
-- 推荐
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- 事务操作
COMMIT;

-- 不推荐
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- 事务操作
COMMIT;

2. 错误处理

捕获和处理异常

  • 使用TRY...CATCH捕获异常(如果支持)
  • 明确回滚事务的条件
  • 记录详细的错误日志

示例

sql
-- 推荐
BEGIN
    START TRANSACTION;
    UPDATE user_info SET balance = balance - 100 WHERE id = 1;
    UPDATE order_detail SET status = 2 WHERE id = 1;
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        -- 记录错误日志
        INSERT INTO error_log (error_msg, create_time) VALUES (SQLERRM, NOW());
END;

-- 不推荐
START TRANSACTION;
UPDATE user_info SET balance = balance - 100 WHERE id = 1;
UPDATE order_detail SET status = 2 WHERE id = 1;
COMMIT;

安全规范

1. 防止SQL注入

使用参数化查询

  • 避免拼接SQL字符串
  • 使用预编译语句
  • 使用ORM框架的参数绑定功能

示例

sql
-- 推荐:参数化查询
PREPARE stmt FROM 'SELECT * FROM user_info WHERE user_name = ?';
SET @name = 'test';
EXECUTE stmt USING @name;
DEALLOCATE PREPARE stmt;

-- 不推荐:拼接SQL字符串
SET @name = 'test';
SET @sql = CONCAT('SELECT * FROM user_info WHERE user_name = ''', @name, '''');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

最小权限原则

  • 为应用程序创建专用的数据库用户
  • 仅授予必要的权限
  • 避免使用ROOT用户连接数据库

示例

sql
-- 推荐
CREATE USER 'app_user'@'%' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON db_name.* TO 'app_user'@'%';

-- 不推荐
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%';

2. 数据保护

敏感数据加密

  • 敏感数据进行加密存储(如:密码、身份证号)
  • 使用安全的加密算法(如:SHA-256、AES-256)
  • 避免明文存储敏感信息

示例

sql
-- 推荐
INSERT INTO user_info (user_name, password, id_card) VALUES (
    'test',
    SHA2('password123', 256), -- 密码加密
    AES_ENCRYPT('110101199001011234', 'encryption_key') -- 身份证号加密
);

-- 不推荐
INSERT INTO user_info (user_name, password, id_card) VALUES (
    'test',
    'password123', -- 明文密码
    '110101199001011234' -- 明文身份证号
);

防止敏感信息泄露

  • 避免在日志中记录敏感信息
  • 不在错误信息中暴露敏感数据
  • 限制敏感数据的查询权限

存储过程和函数规范

1. 存储过程设计

命名规范

  • 存储过程:proc_业务名称_操作
  • 函数:func_业务名称_功能
  • 使用小写字母下划线组合

示例

sql
-- 推荐
CREATE PROCEDURE `proc_user_update`(...);
CREATE FUNCTION `func_order_calculate`(...) RETURNS INT;

-- 不推荐
CREATE PROCEDURE `updateUser`(...); -- 驼峰命名
CREATE FUNCTION `calculate`(...) RETURNS INT; -- 命名不明确

设计原则

  • 单个存储过程不超过200行
  • 避免复杂的逻辑
  • 明确参数的输入输出类型
  • 包含错误处理机制

2. 函数设计

限制使用

  • 避免在查询条件中使用自定义函数
  • 函数应简洁高效
  • 明确返回值类型

示例

sql
-- 推荐
CREATE FUNCTION `func_get_user_name`(p_user_id INT) RETURNS VARCHAR(50)
BEGIN
    DECLARE v_user_name VARCHAR(50);
    SELECT user_name INTO v_user_name FROM user_info WHERE id = p_user_id;
    RETURN v_user_name;
END;

-- 不推荐:在查询条件中使用函数
SELECT * FROM order_detail WHERE func_get_user_name(user_id) = 'test';

代码审查规范

1. 审查内容

语法审查

  • 检查语法错误
  • 检查命名规范
  • 检查注释规范

性能审查

  • 检查全表扫描
  • 检查索引使用
  • 检查JOIN查询
  • 检查事务设计

安全审查

  • 检查SQL注入风险
  • 检查敏感数据处理
  • 检查权限设置

2. 审查工具

  • 使用OceanBase自带的SQL审核工具
  • 使用第三方SQL审核工具(如:SQLAdvisor、SOAR)
  • 结合监控系统进行性能审查

常见问题(FAQ)

Q1: 如何优化慢SQL?

A1: 优化慢SQL的步骤:

  1. 使用EXPLAIN分析执行计划
  2. 检查是否缺少索引
  3. 优化SQL语句,避免全表扫描
  4. 优化JOIN查询
  5. 考虑拆分大查询

Q2: 如何选择合适的索引?

A2: 选择合适索引的原则:

  1. 为WHERE、JOIN、ORDER BY、GROUP BY中的列创建索引
  2. 联合索引遵循最左前缀原则
  3. 避免过多的索引
  4. 考虑索引的选择性
  5. 定期维护和优化索引

Q3: 如何防止SQL注入?

A3: 防止SQL注入的方法:

  1. 使用参数化查询或预编译语句
  2. 避免拼接SQL字符串
  3. 使用ORM框架的参数绑定功能
  4. 对输入进行严格验证和过滤
  5. 最小权限原则

Q4: 事务隔离级别如何选择?

A4: 事务隔离级别的选择:

  1. READ UNCOMMITTED:很少使用,可能导致脏读
  2. READ COMMITTED:大多数业务场景使用,避免脏读
  3. REPEATABLE READ:避免不可重复读,适合数据一致性要求高的场景
  4. SERIALIZABLE:最高隔离级别,避免幻读,但性能较差

Q5: 如何优化批量插入?

A5: 优化批量插入的方法:

  1. 使用批量INSERT语句
  2. 控制批量大小(建议1000条以内)
  3. 调整bulk_insert_buffer_size参数
  4. 关闭自动提交事务
  5. 考虑使用LOAD DATA INFILE(如果支持)

Q6: 如何处理长事务?

A6: 处理长事务的方法:

  1. 拆分大事务为多个小事务
  2. 缩短事务执行时间
  3. 避免在事务中进行耗时操作(如:网络请求、文件IO)
  4. 定期监控长事务
  5. 设置合理的事务超时时间

Q7: 如何命名数据库对象?

A7: 数据库对象命名原则:

  1. 使用小写字母和下划线组合
  2. 具有明确的业务含义
  3. 避免使用保留字和关键字
  4. 统一命名规范
  5. 避免过长的命名

Q8: 如何优化JOIN查询?

A8: 优化JOIN查询的方法:

  1. 优先使用INNER JOIN,避免OUTER JOIN
  2. 小表驱动大表
  3. 为JOIN条件中的列创建索引
  4. 避免超过3个表的JOIN
  5. 考虑使用子查询替代复杂JOIN