1. 首页 > PostgreSQL教程 > 正文

PostgreSQL教程FG130-ECPG避坑:嵌入式SQL常见错误与解决

本文档风哥主要介绍PostgreSQL的ECPG(Embedded SQL in C)常见错误与解决方案,包括编译错误、运行时错误、逻辑错误等内容,风哥教程参考PostgreSQL官方文档ECPG内容,适合开发人员在学习和测试中使用。更多视频教程www.fgedu.net.cn

Part01-基础概念与理论知识

1.1 ECPG常见错误类型

在使用ECPG进行开发时,可能会遇到以下几类错误:

ECPG错误类型:

  • 编译错误:预处理错误、语法错误、类型不匹配等
  • 运行时错误:连接失败、SQL执行错误、资源不足等
  • 逻辑错误:程序逻辑错误、数据处理错误、事务处理错误等
  • 性能问题:查询速度慢、内存泄漏、连接池耗尽等

1.2 错误原因分析

ECPG错误的常见原因:

  • 语法错误:SQL语句语法不正确,缺少分号或关键字
  • 类型不匹配:C类型与SQL类型不匹配
  • 连接问题:数据库连接参数错误或服务器未运行
  • 权限问题:用户权限不足,无法执行某些操作
  • 资源问题:内存不足、连接数达到上限等
  • 逻辑问题:程序逻辑错误,导致数据处理不正确

1.3 错误处理机制

ECPG提供了多种错误处理机制:

// 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);
}

风哥提示:良好的错误处理机制是ECPG程序稳定运行的关键,建议在开发时就考虑各种错误情况,并编写相应的处理代码。学习交流加群风哥微信: itpux-com

Part02-生产环境规划与建议

2.1 编译错误

2.1.1 预处理错误

// 错误1:ECPG预处理错误

// 错误现象
$ 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 链接错误

// 错误3:链接错误

// 错误现象
$ 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 连接错误

// 错误5:连接失败

// 错误现象
连接失败: 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执行错误

// 错误7:表不存在

// 错误现象
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 事务处理错误

// 错误10:事务未提交

// 错误现象
数据插入后查询不到

// 原因:事务未提交
// 错误代码
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 游标处理错误

// 错误12:游标未关闭

// 错误现象
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;
// 处理数据…
}

风哥教程针对风哥教程针对风哥教程针对生产环境建议:在生产环境中,建议对所有可能的错误情况进行处理,并记录详细的错误日志,便于问题排查和系统维护。学习交流加群风哥QQ113257174

Part03-生产环境项目实施方案

3.1 错误预防

3.1.1 编码规范

// ECPG编码规范

// 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;
}

风哥提示:良好的错误恢复机制可以确保数据的一致性和完整性,在发生错误时能够及时回滚或恢复。更多学习教程公众号风哥教程itpux_com

Part04-生产案例与实战讲解

4.1 常见陷阱

4.1.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 并发陷阱

// 陷阱3:并发访问冲突

// 错误代码(多线程环境下)
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 解决方案

# ECPG常见问题解决方案

# 1. 编译问题

## 问题:ECPG预处理失败
## 解决方案:
– 检查SQL语句语法
– 确保所有SQL语句以分号结尾
– 检查变量声明是否正确
– 使用ecpg的-v选项查看详细错误信息

## 问题:链接失败
## 解决方案:
– 确保安装了libecpg库
– 检查库路径是否正确
– 使用-L选项指定库路径
– 确保链接了所有必要的库(-lecpg -lpq)

# 2. 运行时问题

## 问题:连接失败
## 解决方案:
– 检查数据库服务器是否运行
– 检查连接参数是否正确
– 检查网络连接
– 检查防火墙设置

## 问题:SQL执行失败
## 解决方案:
– 检查SQL语句语法
– 检查表和列是否存在
– 检查用户权限
– 检查数据类型是否匹配

## 问题:性能问题
## 解决方案:
– 使用连接池
– 使用批量操作
– 优化SQL语句
– 创建索引

# 3. 逻辑问题

## 问题:数据不一致
## 解决方案:
– 使用事务
– 正确使用保存点
– 确保事务提交或回滚
– 检查并发控制

## 问题:内存泄漏
## 解决方案:
– 确保关闭游标
– 确保断开数据库连接
– 使用valgrind等工具检测内存泄漏

4.3 最佳实践

// ECPG最佳实践

// 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 ? “成功” : “失败”);
}

风哥教程针对风哥教程针对风哥教程针对生产环境建议:在生产环境中,建议遵循最佳实践,编写健壮的代码,处理所有可能的错误情况,确保系统的稳定性和可靠性。from PostgreSQL:www.itpux.com

Part05-风哥经验总结与分享

5.1 调试技巧

ECPG调试技巧:

  • 使用ecpg的-v选项:查看详细的预处理信息
  • 检查生成的C代码:查看ecpg生成的代码,了解预处理结果
  • 使用gdb调试:使用gdb调试ECPG程序
  • 添加日志:在关键位置添加日志,跟踪程序执行流程
  • 使用sqlca:检查sqlca结构,了解SQL执行状态
  • 使用PQerrorMessage:获取详细的错误信息
  • 检查返回值:始终检查函数返回值
  • 使用valgrind:检测内存泄漏和内存错误
风哥提示:调试是解决问题的重要手段,掌握调试技巧可以快速定位和解决问题。

5.2 编码规范

// ECPG编码规范

// 1. 命名规范
// – 变量名使用小写字母和下划线
// – 常量名使用大写字母和下划线
// – 函数名使用小写字母和下划线

// 2. 代码格式
// – 使用4个空格缩进
// – 每行不超过80个字符
// – 在操作符前后添加空格

// 3. 注释规范
// – 使用//或/* */添加注释
// – 在函数前添加函数说明注释
// – 在复杂逻辑处添加注释

// 4. 错误处理
// – 始终检查SQL执行结果
// – 使用宏简化错误处理
// – 记录详细的错误日志

// 5. 资源管理
// – 及时释放资源
// – 使用atexit注册清理函数
// – 避免内存泄漏

// 6. 安全性
// – 验证所有输入
// – 使用参数化查询,避免SQL注入
// – 保护敏感信息(密码等)

5.3 故障排除

ECPG故障排除流程:

from oracle:www.itpux.com

  1. 收集信息:收集错误信息、日志、配置文件等
  2. 分析错误:分析错误类型和原因
  3. 定位问题:定位问题发生的代码位置
  4. 制定方案:制定解决方案
  5. 实施修复:实施修复并验证
  6. 总结记录:记录问题和解决方案
持续改进:ECPG开发是一个不断学习和改进的过程,建议总结经验教训,持续改进代码质量和开发流程。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息