PostgreSQL教程FG130-ECPG避坑:嵌入式SQL常见错误与解决
本文档风哥主要介绍PostgreSQL的ECPG(Embedded SQL in C)常见错误与解决方案,包括编译错误、运行时错误、逻辑错误等内容,风哥教程参考PostgreSQL官方文档ECPG内容,适合开发人员在学习和测试中使用。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 ECPG常见错误类型
在使用ECPG进行开发时,可能会遇到以下几类错误:
- 编译错误:预处理错误、语法错误、类型不匹配等
- 运行时错误:连接失败、SQL执行错误、资源不足等
- 逻辑错误:程序逻辑错误、数据处理错误、事务处理错误等
- 性能问题:查询速度慢、内存泄漏、连接池耗尽等
1.2 错误原因分析
ECPG错误的常见原因:
- 语法错误:SQL语句语法不正确,缺少分号或关键字
- 类型不匹配:C类型与SQL类型不匹配
- 连接问题:数据库连接参数错误或服务器未运行
- 权限问题:用户权限不足,无法执行某些操作
- 资源问题:内存不足、连接数达到上限等
- 逻辑问题:程序逻辑错误,导致数据处理不正确
1.3 错误处理机制
ECPG提供了多种错误处理机制:
// 1. 使用sqlca结构
// sqlca是ECPG提供的全局结构,包含SQL执行的状态信息
// 检查SQL执行结果
if (sqlca.sqlcode != 0) {
fprintf(stderr, “SQL错误: %s\n”, sqlca.sqlerrm.sqlerrmc);
fprintf(stderr, “SQL代码: %d\n”, sqlca.sqlcode);
}
// 2. 使用WHENEVER语句
// WHENEVER语句用于指定错误处理行为
// 遇到错误时继续执行
EXEC SQL WHENEVER SQLERROR CONTINUE;
// 遇到错误时跳转到指定标签
EXEC SQL WHENEVER SQLERROR GOTO error_handler;
// 遇到错误时停止程序
EXEC SQL WHENEVER SQLERROR STOP;
// 3. 使用指示器变量
// 指示器变量用于检测NULL值和截断
EXEC SQL BEGIN DECLARE SECTION;
char name[100];
short name_ind; // 指示器变量
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT name INTO :name :name_ind FROM fgedu_employees WHERE id = 1;
if (name_ind == -1) {
printf(“name为NULL\n”);
} else if (name_ind > 0) {
printf(“name被截断,原始长度: %d\n”, name_ind);
}
Part02-生产环境规划与建议
2.1 编译错误
2.1.1 预处理错误
// 错误现象
$ ecpg example.pgc
example.pgc:5: ERROR: syntax error at or near “EXEC”
// 原因:缺少分号或语法错误
// 错误代码
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password // 缺少分号
// 正确代码
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
// 错误2:类型不匹配
// 错误现象
$ ecpg example.pgc
example.pgc:10: ERROR: variable “age” has type “char *” but expression has type “int”
// 原因:C类型与SQL类型不匹配
// 错误代码
EXEC SQL BEGIN DECLARE SECTION;
char *age; // 错误:应该使用int
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT age INTO :age FROM fgedu_employees WHERE id = 1;
// 正确代码
EXEC SQL BEGIN DECLARE SECTION;
int age;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT age INTO :age FROM fgedu_employees WHERE id = 1;
2.1.2 链接错误
// 错误现象
$ gcc -o example example.c -lecpg
/tmp/cc12345.o: In function `main’:
example.c:(.text+0x10): undefined reference to `ECPGconnect’
// 原因:缺少libecpg库或库路径不正确
// 解决方案
$ gcc -o example example.c -lecpg -lpq -I/usr/include/postgresql -L/usr/lib64
// 错误4:头文件找不到
// 错误现象
$ ecpg example.pgc
example.pgc:2: error: ecpglib.h: No such file or directory
// 原因:头文件路径不正确
// 解决方案
$ ecpg -I /usr/include/postgresql example.pgc
$ gcc -o example example.c -lecpg -lpq -I/usr/include/postgresql
2.2 运行时错误
2.2.1 连接错误
// 错误现象
连接失败: FATAL: password authentication failed for fgedu “postgres”
// 原因:密码错误或连接参数不正确
// 解决方案
// 检查连接参数
EXEC SQL BEGIN DECLARE SECTION;
char fgedudb[50] = “fgedudb”;
char fgedu[50] = “postgres”;
char password[50] = “correct_password”; // 确保密码正确
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 1;
}
// 错误6:数据库不存在
// 错误现象
连接失败: FATAL: fgedudb “fgedudb” does not exist
// 原因:数据库不存在
// 解决方案
// 先创建数据库
$ createdb fgedudb
// 或在代码中处理
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
// 尝试创建数据库
EXEC SQL CREATE DATABASE :fgedudb;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “创建数据库失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 1;
}
// 重新连接
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
}
2.2.2 SQL执行错误
// 错误现象
SQL错误: ERROR: relation “fgedu_employees” does not exist
// 原因:表不存在
// 解决方案
// 在代码中检查表是否存在,不存在则创建
EXEC SQL CREATE TABLE fgedu_IF NOT EXISTS fgedu_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL
);
// 错误8:权限不足
// 错误现象
SQL错误: ERROR: permission denied for relation fgedu_employees
// 原因:用户权限不足
// 解决方案
// 授予权限
$ psql -c “GRANT ALL PRIVILEGES ON TABLE fgedu_employees TO postgres;”
// 或在代码中处理错误
EXEC SQL SELECT * FROM fgedu_employees;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “查询失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
// 记录日志或通知管理员
return 1;
}
// 错误9:数据类型不匹配
// 错误现象
SQL错误: ERROR: column “age” is of type integer but expression is of type character varying
// 原因:插入的数据类型与列类型不匹配
// 解决方案
// 确保数据类型匹配
EXEC SQL BEGIN DECLARE SECTION;
int age = 25; // 确保是int类型
EXEC SQL END DECLARE SECTION;
EXEC SQL INSERT INTO fgedu_employees (age) VALUES (:age);
2.3 逻辑错误
2.3.1 事务处理错误
// 错误现象
数据插入后查询不到
// 原因:事务未提交
// 错误代码
EXEC SQL BEGIN TRANSACTION;
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
// 忘记提交事务
// 正确代码
EXEC SQL BEGIN TRANSACTION;
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
EXEC SQL COMMIT;
// 错误11:事务嵌套
// 错误现象
SQL错误: ERROR: transaction block is already active
// 原因:嵌套事务
// 错误代码
EXEC SQL BEGIN TRANSACTION;
EXEC SQL BEGIN TRANSACTION; // 错误:不能嵌套
// 解决方案
// 使用保存点
EXEC SQL BEGIN TRANSACTION;
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
EXEC SQL SAVEPOINT sp1;
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥2号’, 30);
EXEC SQL COMMIT;
2.3.2 游标处理错误
// 错误现象
SQL错误: ERROR: cursor “cursor_name” already exists
// 原因:游标未关闭就重新声明
// 错误代码
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM fgedu_employees;
// 使用游标…
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM fgedu_employees; // 错误:游标已存在
// 正确代码
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM fgedu_employees;
// 使用游标…
EXEC SQL CLOSE cur; // 关闭游标
EXEC SQL DECLARE cur CURSOR FOR SELECT * FROM fgedu_employees; // 重新声明
// 错误13:游标越界
// 错误现象
SQL错误: ERROR: cursor is not positioned on a row
// 原因:游标已经到达结果集末尾
// 解决方案
EXEC SQL WHENEVER NOT FOUND DO BREAK;
while (1) {
EXEC SQL FETCH cur INTO :id, :name, :age;
// 处理数据…
}
Part03-生产环境项目实施方案
3.1 错误预防
3.1.1 编码规范
// 1. 始终检查SQL执行结果
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 1;
}
// 2. 使用指示器变量处理NULL值
EXEC SQL BEGIN DECLARE SECTION;
char name[100];
short name_ind;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT name INTO :name :name_ind FROM fgedu_employees WHERE id = 1;
if (name_ind == -1) {
strcpy(name, “未知”);
}
// 3. 确保字符串以null结尾
EXEC SQL BEGIN DECLARE SECTION;
char name[100] = {0}; // 初始化为0
EXEC SQL END DECLARE SECTION;
// 4. 使用合适的数据类型
// C类型与SQL类型对应关系:
// int -> INTEGER
// long -> BIGINT
// short -> SMALLINT
// float -> REAL
// double -> DOUBLE PRECISION
// char[] -> VARCHAR/CHAR/TEXT
// 5. 及时释放资源
EXEC SQL CLOSE cur;
EXEC SQL DISCONNECT;
3.1.2 输入验证
// 验证字符串长度
void safe_strcpy(char *dest, const char *src, size_t size) {
if (strlen(src) >= size) {
fprintf(stderr, “警告:字符串被截断\n”);
}
strncpy(dest, src, size – 1);
dest[size – 1] = ‘\0’;
}
// 验证数值范围
int validate_age(int age) {
if (age < 0 || age > 150) {
fprintf(stderr, “错误:年龄必须在0-150之间\n”);
return 0;
}
return 1;
}
// 使用示例
EXEC SQL BEGIN DECLARE SECTION;
char name[100];
int age;
EXEC SQL END DECLARE SECTION;
// 从用户输入获取数据
printf(“请输入姓名:”);
scanf(“%99s”, name);
printf(“请输入年龄:”);
scanf(“%d”, &age);
// 验证输入
if (!validate_age(age)) {
return 1;
}
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (:name, :age);
3.2 错误检测
3.2.1 日志记录
#include
#include
FILE *log_file = NULL;
void init_log(const char *filename) {
log_file = fopen(filename, “a”);
if (log_file == NULL) {
fprintf(stderr, “无法打开日志文件\n”);
return;
}
}
void log_error(const char *operation, const char *error_msg) {
if (log_file == NULL) return;
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[26];
strftime(time_str, 26, “%Y-%m-%d %H:%M:%S”, tm_info);
fprintf(log_file, “[%s] ERROR: %s – %s\n”, time_str, operation, error_msg);
fflush(log_file);
}
void close_log() {
if (log_file != NULL) {
fclose(log_file);
log_file = NULL;
}
}
// 使用示例
int main() {
init_log(“/var/log/fgedu_fgapp.log”);
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
log_error(“CONNECT”, sqlca.sqlerrm.sqlerrmc);
close_log();
return 1;
}
// … 其他操作 …
close_log();
return 0;
}
3.3 错误恢复
3.3.1 事务回滚
int perform_transaction() {
EXEC SQL BEGIN TRANSACTION;
// 操作1
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
if (sqlca.sqlcode != 0) {
fprintf(stderr, “插入失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK;
return 0;
}
// 操作2
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥2号’, 30);
if (sqlca.sqlcode != 0) {
fprintf(stderr, “插入失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK;
return 0;
}
// 提交事务
EXEC SQL COMMIT;
return 1;
}
// 使用保存点进行部分回滚
int perform_complex_transaction() {
EXEC SQL BEGIN TRANSACTION;
// 操作1
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
if (sqlca.sqlcode != 0) {
EXEC SQL ROLLBACK;
return 0;
}
// 创建保存点
EXEC SQL SAVEPOINT sp1;
// 操作2(可能失败)
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥2号’, 30);
if (sqlca.sqlcode != 0) {
// 回滚到保存点
EXEC SQL ROLLBACK TO SAVEPOINT sp1;
fprintf(stderr, “操作2失败,但操作1已保留\n”);
}
// 提交事务
EXEC SQL COMMIT;
return 1;
}
Part04-生产案例与实战讲解
4.1 常见陷阱
4.1.1 内存管理陷阱
// 错误代码
EXEC SQL BEGIN DECLARE SECTION;
char name[10]; // 缓冲区太小
EXEC SQL END DECLARE SECTION;
strcpy(name, “这是一个很长的姓名”); // 溢出!
EXEC SQL INSERT INTO fgedu_employees (name) VALUES (:name);
// 正确代码
EXEC SQL BEGIN DECLARE SECTION;
char name[100]; // 足够大的缓冲区
EXEC SQL END DECLARE SECTION;
safe_strcpy(name, “这是一个很长的姓名”, sizeof(name));
EXEC SQL INSERT INTO fgedu_employees (name) VALUES (:name);
// 陷阱2:未初始化的变量
// 错误代码
EXEC SQL BEGIN DECLARE SECTION;
int age; // 未初始化
EXEC SQL END DECLARE SECTION;
EXEC SQL INSERT INTO fgedu_employees (age) VALUES (:age); // 可能插入垃圾值
// 正确代码
EXEC SQL BEGIN DECLARE SECTION;
int age = 0; // 初始化为0
EXEC SQL END DECLARE SECTION;
age = 25; // 赋值
EXEC SQL INSERT INTO fgedu_employees (age) VALUES (:age);
4.1.2 并发陷阱
// 错误代码(多线程环境下)
void *worker(void *arg) {
// 多个线程共享同一个连接
EXEC SQL INSERT INTO fgedu_employees (name) VALUES (‘风哥1号’);
return NULL;
}
// 正确代码
// 每个线程使用独立的连接或使用连接池
void *worker(void *arg) {
ConnectionPool *pool = (ConnectionPool *)arg;
PGconn *conn = get_connection(pool);
// 使用连接执行操作
// …
release_connection(pool, conn);
return NULL;
}
// 陷阱4:死锁
// 错误代码
// 事务A:更新表1,然后更新表2
// 事务B:更新表2,然后更新表1
// 可能导致死锁
// 解决方案:统一访问顺序
// 所有事务都按照相同的顺序访问表
EXEC SQL BEGIN TRANSACTION;
EXEC SQL UPDATE fgedu_employees SET …; // 先更新employees
EXEC SQL UPDATE fgedu_departments SET …; // 再更新departments
EXEC SQL COMMIT;
4.2 解决方案
# 1. 编译问题
## 问题:ECPG预处理失败
## 解决方案:
– 检查SQL语句语法
– 确保所有SQL语句以分号结尾
– 检查变量声明是否正确
– 使用ecpg的-v选项查看详细错误信息
## 问题:链接失败
## 解决方案:
– 确保安装了libecpg库
– 检查库路径是否正确
– 使用-L选项指定库路径
– 确保链接了所有必要的库(-lecpg -lpq)
# 2. 运行时问题
## 问题:连接失败
## 解决方案:
– 检查数据库服务器是否运行
– 检查连接参数是否正确
– 检查网络连接
– 检查防火墙设置
## 问题:SQL执行失败
## 解决方案:
– 检查SQL语句语法
– 检查表和列是否存在
– 检查用户权限
– 检查数据类型是否匹配
## 问题:性能问题
## 解决方案:
– 使用连接池
– 使用批量操作
– 优化SQL语句
– 创建索引
# 3. 逻辑问题
## 问题:数据不一致
## 解决方案:
– 使用事务
– 正确使用保存点
– 确保事务提交或回滚
– 检查并发控制
## 问题:内存泄漏
## 解决方案:
– 确保关闭游标
– 确保断开数据库连接
– 使用valgrind等工具检测内存泄漏
4.3 最佳实践
// 1. 错误处理
#define CHECK_SQL_ERROR(operation) \
if (sqlca.sqlcode != 0) { \
fprintf(stderr, “错误在 %s: %s\n”, operation, sqlca.sqlerrm.sqlerrmc); \
return 1; \
}
// 使用示例
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
CHECK_SQL_ERROR(“CONNECT”);
// 2. 资源管理
void cleanup() {
EXEC SQL CLOSE ALL CURSORS;
EXEC SQL DISCONNECT ALL;
}
// 使用atexit注册清理函数
atexit(cleanup);
// 3. 事务管理
int safe_transaction() {
EXEC SQL BEGIN TRANSACTION;
// 执行操作
EXEC SQL INSERT INTO fgedu_employees (name, age) VALUES (‘风哥1号’, 25);
if (sqlca.sqlcode != 0) {
EXEC SQL ROLLBACK;
return 0;
}
EXEC SQL COMMIT;
return 1;
}
// 4. 输入验证
int validate_input(const char *name, int age) {
if (name == NULL || strlen(name) == 0) {
fprintf(stderr, “错误:姓名不能为空\n”);
return 0;
}
if (age < 0 || age > 150) {
fprintf(stderr, “错误:年龄必须在0-150之间\n”);
return 0;
}
return 1;
}
// 5. 日志记录
void log_operation(const char *operation, int success) {
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[26];
strftime(time_str, 26, “%Y-%m-%d %H:%M:%S”, tm_info);
printf(“[%s] %s: %s\n”, time_str, operation, success ? “成功” : “失败”);
}
Part05-风哥经验总结与分享
5.1 调试技巧
ECPG调试技巧:
- 使用ecpg的-v选项:查看详细的预处理信息
- 检查生成的C代码:查看ecpg生成的代码,了解预处理结果
- 使用gdb调试:使用gdb调试ECPG程序
- 添加日志:在关键位置添加日志,跟踪程序执行流程
- 使用sqlca:检查sqlca结构,了解SQL执行状态
- 使用PQerrorMessage:获取详细的错误信息
- 检查返回值:始终检查函数返回值
- 使用valgrind:检测内存泄漏和内存错误
5.2 编码规范
// 1. 命名规范
// – 变量名使用小写字母和下划线
// – 常量名使用大写字母和下划线
// – 函数名使用小写字母和下划线
// 2. 代码格式
// – 使用4个空格缩进
// – 每行不超过80个字符
// – 在操作符前后添加空格
// 3. 注释规范
// – 使用//或/* */添加注释
// – 在函数前添加函数说明注释
// – 在复杂逻辑处添加注释
// 4. 错误处理
// – 始终检查SQL执行结果
// – 使用宏简化错误处理
// – 记录详细的错误日志
// 5. 资源管理
// – 及时释放资源
// – 使用atexit注册清理函数
// – 避免内存泄漏
// 6. 安全性
// – 验证所有输入
// – 使用参数化查询,避免SQL注入
// – 保护敏感信息(密码等)
5.3 故障排除
ECPG故障排除流程:
from oracle:www.itpux.com
- 收集信息:收集错误信息、日志、配置文件等
- 分析错误:分析错误类型和原因
- 定位问题:定位问题发生的代码位置
- 制定方案:制定解决方案
- 实施修复:实施修复并验证
- 总结记录:记录问题和解决方案
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
