外观
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存储年龄) - 时间类型使用
DATETIME或TIMESTAMP - 字符串类型根据实际长度选择
VARCHAR或CHAR
示例:
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. 索引优化
索引设计原则
- 为WHERE、JOIN、ORDER BY、GROUP 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 NULL、IS 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的步骤:
- 使用
EXPLAIN分析执行计划 - 检查是否缺少索引
- 优化SQL语句,避免全表扫描
- 优化JOIN查询
- 考虑拆分大查询
Q2: 如何选择合适的索引?
A2: 选择合适索引的原则:
- 为WHERE、JOIN、ORDER BY、GROUP BY中的列创建索引
- 联合索引遵循最左前缀原则
- 避免过多的索引
- 考虑索引的选择性
- 定期维护和优化索引
Q3: 如何防止SQL注入?
A3: 防止SQL注入的方法:
- 使用参数化查询或预编译语句
- 避免拼接SQL字符串
- 使用ORM框架的参数绑定功能
- 对输入进行严格验证和过滤
- 最小权限原则
Q4: 事务隔离级别如何选择?
A4: 事务隔离级别的选择:
- READ UNCOMMITTED:很少使用,可能导致脏读
- READ COMMITTED:大多数业务场景使用,避免脏读
- REPEATABLE READ:避免不可重复读,适合数据一致性要求高的场景
- SERIALIZABLE:最高隔离级别,避免幻读,但性能较差
Q5: 如何优化批量插入?
A5: 优化批量插入的方法:
- 使用批量INSERT语句
- 控制批量大小(建议1000条以内)
- 调整
bulk_insert_buffer_size参数 - 关闭自动提交事务
- 考虑使用LOAD DATA INFILE(如果支持)
Q6: 如何处理长事务?
A6: 处理长事务的方法:
- 拆分大事务为多个小事务
- 缩短事务执行时间
- 避免在事务中进行耗时操作(如:网络请求、文件IO)
- 定期监控长事务
- 设置合理的事务超时时间
Q7: 如何命名数据库对象?
A7: 数据库对象命名原则:
- 使用小写字母和下划线组合
- 具有明确的业务含义
- 避免使用保留字和关键字
- 统一命名规范
- 避免过长的命名
Q8: 如何优化JOIN查询?
A8: 优化JOIN查询的方法:
- 优先使用INNER JOIN,避免OUTER JOIN
- 小表驱动大表
- 为JOIN条件中的列创建索引
- 避免超过3个表的JOIN
- 考虑使用子查询替代复杂JOIN
