引言:DB2调用方法的重要性
DB2作为IBM公司开发的企业级关系型数据库管理系统,广泛应用于金融、制造、零售等关键行业。掌握DB2的调用方法对于开发人员、DBA和数据分析师来说至关重要。本文将从基础语法开始,逐步深入到高级应用实战,并提供常见问题的解决方案,帮助读者全面掌握DB2的调用技术。
在现代企业应用中,DB2调用通常涉及多种场景:从简单的CRUD操作到复杂的存储过程调用,从单机应用到分布式系统集成。理解不同调用方式的优缺点和适用场景,能够帮助我们设计出更高效、更可靠的数据库交互方案。本文将涵盖SQL调用、CLI调用、嵌入式SQL、存储过程调用等多种方式,并提供详细的代码示例和最佳实践。
城市基础语法:SQL调用入门
1.1 基本SQL语句结构
DB2的SQL调用是最基础也是最常用的方式。首先,我们需要了解基本的SQL语句结构:
-- 连接到数据库
CONNECT TO 数据库名 USER 用户名 USING 密码;
-- 基本查询语句
SELECT 列名1, 列名2 FROM 表名 WHERE 条件;
-- 插入语句
INSERT INTO 表名 (列名1, 列名2) VALUES (值1, 值2);
-- 更新语句
UPDATE 表名 SET 列名1 = 值1 WHERE 条件;
-- 删除语句
DELETE FROM 表名 WHERE 条件;
1.2 参数标记的使用
在实际应用中,为了避免SQL注入和提高性能,我们通常使用参数标记(?)来代替直接的值:
-- 使用参数标记的查询
SELECT * FROM EMPLOYEE WHERE DEPT = ? AND SALARY > ?;
-- 使用参数标记的插入
INSERT INTO EMPLOYEE (EMPNO, ENAME, DEPT, SALARY) VALUES (?, ?, ?, ?);
1.3 游标操作
对于需要逐行处理结果集的情况,我们需要使用游标:
-- 声明游标
DECLARE emp_cursor CURSOR FOR
SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?;
-- 打开游标
OPEN emp_cursor;
-- 获取数据
FETCH emp_cursor INTO :empno, :ename, :salary;
-- 关闭游标
CLOSE emp_cursor;
2. 嵌入式SQL编程
2.1 嵌入式SQL基础
嵌入式SQL是将SQL语句直接嵌入到宿主语言(如C/C++、COBOL等)代码中的一种方式。DB2通过预编译器处理这些SQL语句,将其转换为宿主语言的函数调用。
// C语言中的嵌入式SQL示例
#include <sqlca.h>
EXEC SQL BEGIN DECLARE SECTION;
char empno[7];
char ename[51];
double salary;
char dept[4];
EXEC SQL END DECLARE SECTION;
// 连接数据库
EXEC SQL CONNECT TO MYDB USER myuser USING mypass;
// 查询员工信息
EXEC SQL SELECT EMPNO, ENAME, SALARY
INTO :empno, :ename, :salary
FROM EMPLOYEE
WHERE DEPT = 'D01';
// 处理结果
printf("Employee: %s, Salary: %.2f\n", ename, salary);
// 断开连接
EXEC SQL DISCONNECT CURRENT;
2.2 游标在嵌入式SQL中的应用
// 使用游标处理多行结果
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?;
EXEC SQL OPEN emp_cursor USING :input_dept;
while (1) {
EXEC SQL FETCH emp_cursor INTO :empno, :ename, :salary;
if (SQLCODE != 0) break; // SQLCODE=0表示成功,100表示无数据
// 处理每一行数据
printf("Employee: %s, Salary: %.2f\n", ename, salary);
}
EXEC SQL CLOSE emp_cursor;
2.3 错误处理
在嵌入式SQL中,我们需要检查SQLCA(SQL通信区)来处理错误:
// 错误处理示例
if (SQLCODE < 0) {
printf("SQL Error: %ld\n", SQLCODE);
printf("Error Message: %s\n", sqlca.sqlerrm.sqlerrmc);
// 执行回滚操作
EXEC SQL ROLLBACK;
} else if (SQLCODE == 0) {
// 成功执行
EXEC SQL COMMIT;
}
3. CLI/ODBC调用方法
3.1 CLI基础概念
CLI(Call Level Interface)是DB2提供的标准编程接口,基于X/Open和SQL/CLI标准。它比嵌入式SQL更灵活,不需要预编译,是现代应用开发的推荐方式。
3.2 CLI编程步骤
以下是使用DB2 CLI连接和查询数据库的完整示例:
#include <sqlcli1.h>
#include <stdio.h>
#include <string.h>
void check_error(SQLRETURN rc, SQLSMALLINT handle_type, SQLHANDLE handle) {
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLSMALLINT length;
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), &length);
printf("Error: %s - %s\n", sqlstate, message);
}
}
int main() {
SQLRETURN rc;
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLCHAR connstr[] = "DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;PROTOCOL=TCPIP;UID=myuser;PWD=mypass;";
SQLCHAR sql[] = "SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?";
SQLCHAR dept[4] = "D01";
SQLCHAR empno[7];
SQLCHAR ename[51];
SQLDOUBLE salary;
SQLLEN empno_ind, ename_ind, salary_ind;
// 1. 分配环境句柄
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
check_error(rc, SQL_HANDLE_ENV, env);
// 2. 设置属性
rc = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
check_error(rc, SQL_HANDLE_ENV, env);
// 3. 分配连接句柄
rc = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
check_error(rc, SQL_HANDLE_DBC, dbc);
// 4. 连接数据库
rc = SQLDriverConnect(dbc, NULL, connstr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
check_error(rc, SQL_HANDLE_DBC, dbc);
// 5. 分配语句句柄
rc = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 6. 准备SQL语句
rc = SQLPrepare(stmt, sql, SQL_NTS);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 7. 绑定参数
rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
sizeof(dept), 0, dept, sizeof(dept), NULL);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 8. 执行查询
rc = SQLExecute(stmt);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 9. 绑定结果列
SQLBindCol(stmt, 1, SQL_C_CHAR, empno, sizeof(empno), &empno_ind);
SQLBindCol(stmt, 2, SQL_C_CHAR, ename, sizeof(ename), &ename_ind);
SQLBindCol(stmt, 3, SQL_C_DOUBLE, &salary, 0, &salary_ind);
// 10. 获取结果
printf("Employee List:\n");
while ((rc = SQLFetch(stmt)) == SQL_SUCCESS) {
printf("EmpNo: %s, Name: %s, Salary: %.2f\n", empno, ename, salary);
}
// 11. 清理资源
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return 0;
}
3.3 CLI中的事务控制
// 事务控制示例
// 开始事务(自动模式,DB2默认自动提交为开启状态)
SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
// 执行多个操作
SQLExecute(insert_stmt);
SQLExecute(update_stmt);
// 提交事务
SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT);
// 或者回滚
SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_ROLLBACK);
// 恢复自动提交
SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_ON, 0);
4. 存储过程调用
4.1 存储过程基础
存储过程是预编译的SQL代码集合,可以提高性能、增强安全性并减少网络流量。DB2支持多种语言编写的存储过程,包括SQL PL、C/C++、Java等。
4.2 SQL PL存储过程示例
-- 创建一个简单的SQL PL存储过程
CREATE PROCEDURE GET_EMPLOYEE_SALARY (
IN p_empno VARCHAR(7),
OUT p_salary DECIMAL(10,2),
OUT p_ename VARCHAR(50)
)
LANGUAGE SQL
BEGIN
SELECT ENAME, SALARY INTO p_ename, p_salary
FROM EMPLOYEE
WHERE EMPNO = p_empno;
END;
-- 调用存储过程
CALL GET_EMPLOYEE_SALARY('000010', ?, ?);
4.3 C语言编写的存储过程
// C语言存储过程示例
#include <sqludf.h>
#include <string.h>
#include <stdio.h>
// 存储过程函数定义
void SQL_API_FN GET_EMPLOYEE_INFO(
SQLUDF_VARCHAR *empno,
SQLUDF_VARCHAR *ename,
SQLUDF_DECIMAL *salary,
SQLUDF_NULLIND *empno_ind,
SQLUDF_NULLIND *ename_ind,
SQLUDF_NULLIND *salary_ind,
SQLUDF_TRAIL_ARGS) {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN rc;
SQLCHAR sql[256];
SQLLEN ename_len, salary_len;
// 分配句柄
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
// 连接数据库(使用DB2连接字符串)
rc = SQLConnect(dbc, (SQLCHAR*)"MYDB", SQL_NTS,
(SQLCHAR*)"myuser", SQL_NTS,
(SQLCHAR*)"mypass", SQL_NTS);
if (rc == SQL_SUCCESS) {
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
sprintf((char*)sql, "SELECT ENAME, SALARY FROM EMPLOYEE WHERE EMPNO = '%s'", empno);
rc = SQLExecDirect(stmt, sql, SQL_NTS);
if (rc == SQL_SUCCESS) {
SQLBindCol(stmt, 1, SQL_C_CHAR, ename, 50, &ename_len);
SQLBindCol(stmt, 2, SQL_C_DOUBLE, salary, 0, &salary_len);
SQLFetch(stmt);
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
SQLDisconnect(dbc);
}
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
}
4.4 调用存储过程
-- 在SQL中调用
CALL GET_EMPLOYEE_INFO('000010', ?, ?);
-- 在CLI/C中调用存储过程
SQLCHAR call_sql[] = "CALL GET_EMPLOYEE_INFO(?, ?, ?)";
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLPrepare(stmt, call_sql, SQL_NTS);
// 绑定输入参数
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
7, 0, empno, 7, NULL);
// 绑定输出参数
SQLBindParameter(stmt, 2, SQL_PARAM_OUTPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, ename, 50, &ename_ind);
SQLBindParameter(stmt, 3, SQL_PARAM_OUTPUT, SQL_C_DOUBLE, SQL_DECIMAL,
10, 2, &salary, 0, &salary_ind);
SQLExecute(stmt);
5. 高级应用实战
5.1 批量操作优化
批量操作可以显著提高数据处理效率,特别是在数据加载和批量更新场景中。
-- 批量插入(使用参数标记)
INSERT INTO EMPLOYEE (EMPNO, ENAME, DEPT, SALARY) VALUES (?, ?, ?, ?);
-- 在CLI中实现批量插入
#define BATCH_SIZE 1000
SQLINTEGER empno_batch[BATCH_SIZE];
SQLCHAR ename_batch[BATCH_SIZE][51];
SQLCHAR dept_batch[BATCH_SIZE][4];
SQLDOUBLE salary_batch[BATCH_SIZE];
SQLLEN empno_ind[BATCH_SIZE], ename_ind[BATCH_SIZE],
dept_ind[BATCH_SIZE], salary_ind[BATCH_SIZE];
// 准备语句
SQLPrepare(stmt, sql, SQL_NTS);
// 绑定参数数组
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, empno_batch, 0, empno_ind);
SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, ename_batch, 50, ename_ind);
SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
3, 0, dept_batch, 3, dept_ind);
SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DECIMAL,
10, 2, salary_batch, 0, salary_ind);
// 执行批量插入
SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (void*)BATCH_SIZE, 0);
SQLExecute(stmt);
5.2 分布式事务处理
在分布式环境中,需要使用两阶段提交(2PC)来保证数据一致性:
// 分布式事务示例
// 连接两个数据库
SQLHDBC dbc1, dbc2;
// 开始事务
SQLSetConnectAttr(dbc1, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
SQLSetConnectAttr(dbc2, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
// 执行操作
SQLExecute(stmt1); // 在dbc1上执行
SQLExecute(stmt2); // 在dbc2上准备
// 准备提交(两阶段提交的第一阶段)
SQLEndTran(SQL_HANDLE_DBC, dbc1, SQL_PREPARE);
SQLEndTran(SQL_HANDLE_DBC, dbc2, SQL_PREPARE);
// 检查所有节点是否准备就绪
// 如果都成功,则提交
SQLEndTran(SQL_HANDLE_DBC, dbc1, SQL_COMMIT);
SQLEndTran(SQL_HANDLE_DBC, dbc2, SQL_COMMIT);
5.3 性能优化技巧
5.3.1 索引优化
-- 创建合适的索引
CREATE INDEX idx_emp_dept ON EMPLOYEE(DEPT);
CREATE INDEX idx_emp_salary ON EMPLOYEE(SALARY DESC);
-- 查看执行计划
EXPLAIN PLAN FOR SELECT * FROM EMPLOYEE WHERE DEPT = 'D01' AND SALARY > 50000;
-- 使用索引提示(如果需要)
SELECT * FROM EMPLOYEE WHERE DEPT = 'D01' --+ INDEX(idx_emp_dept) */;
5.3.2 查询优化
-- 避免SELECT *
SELECT EMPNO, ENAME FROM EMPLOYEE WHERE DEPT = 'D01';
-- 使用EXISTS代替IN
SELECT * FROM DEPARTMENT D WHERE EXISTS (
SELECT 1 FROM EMPLOYEE E WHERE E.DEPT = D.DEPTNO AND E.SALARY > 50000
);
-- 避免在WHERE子句中对列进行函数操作
-- 不好:WHERE YEAR(HIRE_DATE) = 2020
-- 好:WHERE HIRE_DATE >= '2020-01-01' AND HIRE_DATE < '2021-01-01'
5.3.3 连接池管理
// CLI连接池示例
SQLHENV env;
SQLHDBC dbc;
SQLINTEGER pool_size = 10;
// 设置连接池属性
SQLSetEnvAttr(env, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_DRIVER, 0);
SQLSetEnvAttr(env, SQL_ATTR_CP_MATCH, (void*)SQL_CP_STRICT_MATCH, 100);
// 获取连接
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
SQLConnect(dbc, ...);
// 使用连接...
// 释放连接(实际返回到池中)
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
6. 常见问题解决方案
6.1 连接问题
问题1:无法连接到数据库
症状:返回错误代码 -5005 或 “Unable to connect to database”
解决方案:
# 1. 检查数据库实例是否运行
db2level
db2 get instance
# 2. 检查数据库是否已启动
db2 activate database MYDB
# 3. 检查通信配置
db2set -all
db2 get dbm cfg | grep SVCE
# 4. 测试连接
db2 connect to MYDB user myuser using mypass
# 5. 在代码中增加详细错误信息
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), NULL);
printf("Connection Error: %s - %s\n", sqlstate, message);
问题2:连接超时
解决方案:
-- 在数据库配置中调整超时参数
UPDATE DBM CFG USING SVCename DB2c_db2inst1;
UPDATE DBM CFG USING KEEPTCPALIVE YES;
-- 在连接字符串中增加超时参数
DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;TIMEOUT=30;
6.2 性能问题
问题1:查询执行缓慢
诊断步骤:
-- 1. 查看当前活动
SELECT * FROM SYSIBMADM.LONG_RUNNING_QUERIES;
-- 2. 查看锁等待
SELECT * FROM SYSIBMADM.LOCKWAITS;
-- 3. 查看执行计划
EXPLAIN PLAN FOR SELECT * FROM EMPLOYEE WHERE DEPT = 'D01';
-- 4. 查看统计信息
SELECT * FROM SYSIBMADM.GET_DBSIZE_INFO;
问题2:内存泄漏
解决方案:
// 确保正确释放所有CLI句柄
// 错误示例(会导致内存泄漏):
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLExecute(stmt);
// 忘记调用 SQLFreeHandle
// 正确示例:
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLExecute(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt); // 必须释放
// 使用RAII模式(C++)或try-finally(Java)确保资源释放
6.3 数据一致性问题
问题1:死锁
解决方案:
-- 1. 查看死锁图
SELECT * FROM SYSIBMADM.GET_LOCKWAITS();
-- 2. 设置死锁检测频率
UPDATE DBM CFG USING DLCHKTIME 5000; -- 5秒
-- 3. 在应用程序中设置锁超时
SET CURRENT LOCK TIMEOUT = 30; -- 30秒
-- 4. 优化事务设计,减少锁持有时间
-- 避免在事务中进行用户交互
-- 按相同顺序访问表
问题2:数据完整性约束违反
解决方案:
-- 1. 查看约束定义
SELECT * FROM SYSCAT.TABCONST WHERE TABNAME = 'EMPLOYEE';
-- 2. 查看外键依赖
SELECT * FROM SYSCAT.REFERENCES WHERE REFTABNAME = 'EMPLOYEE';
-- 1. 使用DEFERRED约束检查
-- 在创建表时指定
CREATE TABLE ORDERS (
ORDERNO INT NOT NULL,
CUSTNO INT,
CONSTRAINT FK_CUST FOREIGN KEY (CUSTNO) REFERENCES CUSTOMER(CUSTNO)
ON DELETE CASCADE
ON UPDATE NO ACTION
ENFORCED
ENABLE QUERY OPTIMIZATION
);
6.4 编程错误
问题1:SQLCODE错误处理
常见SQLCODE及其含义:
// SQLCODE = 0: 成功
// SQLCODE = 100: 未找到数据
// SQLCODE = -803: 违反唯一约束
// SQLCODE = -805: 未找到程序包
// SQLCODE = -911: 死锁或超时
// SQLCODE = -30081: 连接错误
void handle_sql_error(SQLRETURN rc, SQLSMALLINT handle_type, SQLHANDLE handle) {
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLSMALLINT length;
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), &length);
printf("SQL Error: %s\n", sqlstate);
printf("Message: %s\n", message);
// 根据错误类型采取不同措施
if (strcmp((char*)sqlstate, "23505") == 0) {
// 违反唯一约束
printf("Duplicate key error\n");
} else if (strcmp((char*)sqlstate, "40001") == 0) {
// 事务回滚
printf("Transaction rolled back\n");
}
}
问题2:字符集编码问题
解决方案:
-- 1. 检查数据库编码
SELECT GET_DB_CODEPAGE() FROM SYSIBM.SYSDUMMY1;
-- 2. 在连接字符串中指定编码
DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;CODEPAGE=1208; -- UTF-8
-- 3. 在CLI中设置环境属性
SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, (void*)SQL_TRUE, 0);
7. 最佳实践与性能优化
7.1 代码组织最佳实践
// 封装数据库操作
typedef struct {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
int connected;
} db2_context;
// 初始化函数
int db2_init(db2_context *ctx) {
SQLRETURN rc;
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ctx->env);
if (rc != SQL_SUCCESS) return -1;
rc = SQLSetEnvAttr(ctx->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (rc != SQL_SUCCESS) return -1;
rc = SQLAllocHandle(SQL_HANDLE_DBC, ctx->env, &ctx->DBC);
if (rc != SQL_SUCCESS) return -1;
ctx->connected = 0;
return 0;
}
// 连接函数
int db2_connect(db2_context *ctx, const char *db, const char *user, const char *pass) {
SQLRETURN rc = SQLConnect(ctx->dbc, (SQLCHAR*)db, SQL_NTS,
(SQLCHAR*)user, SQL_NTS,
(SQLCHAR*)pass, SQL_NTS);
if (rc == SQL_SUCCESS) {
ctx->connected = 1;
return 0;
}
return -1;
}
// 清理函数
void db2_cleanup(db2_context *ctx) {
if (ctx->stmt) SQLFreeHandle(SQL_HANDLE_STMT, ctx->stmt);
if (ctx->connected) SQLDisconnect(ctx->dbc);
if (ctx->dbc) SQLFreeHandle(SQL_HANDLE_DBC, ctx->dbc);
if (ctx->env) SQLFreeHandle(SQL_HANDLE_ENV, ctx->env);
}
7.2 性能监控
-- 监控SQL执行性能
SELECT
STMT_TEXT,
NUM_EXECUTIONS,
TOTAL_SECTION_TIME,
AVG_SECTION_TIME,
ROWS_READ,
ROWS_WRITTEN
FROM SYSIBMADM.GET_ACTIVITY_LOG()
WHERE ACTIVITY_TYPE = 'SQL'
ORDER BY AVG_SECTION_TIME DESC
FETCH FIRST 10 ROWS ONLY;
-- 查看缓冲池命中率
SELECT
BP_NAME,
DATA_READS,
DATA_WRITES,
(1 - (DATA_READS / NULLIF(PAGE_READS, 0))) * 100 AS HIT_RATIO
FROM SYSIBMADM.BP_HITRATIO;
7.3 安全最佳实践
-- 1. 使用参数化查询防止SQL注入
-- 错误:直接拼接字符串
SELECT * FROM EMPLOYEE WHERE ENAME = ' || input_name || ';
-- 正确:使用参数标记
SELECT * FROM EMPLOYEE WHERE ENAME = ?;
-- 2. 最小权限原则
CREATE USER myappuser FOR DATA ACCESS;
GRANT SELECT, INSERT ON EMPLOYEE TO myappuser;
REVOKE DELETE ON EMPLOYEE FROM myappuser;
-- 3. 审计敏感操作
-- 启用审计
UPDATE DBM CFG USING DFT_AUDIT_BUF_SZ 1024;
-- 审计特定表
AUDIT SELECT ON EMPLOYEE BY USER myappuser;
8. 总结
DB2调用方法涵盖了从基础SQL到高级CLI编程的多个层面。掌握这些技术需要理解不同方法的适用场景、性能特点和最佳实践。通过本文的详细讲解和代码示例,读者应该能够:
- 基础层面:熟练使用SQL语句进行数据操作,理解参数标记和游标的概念
- 编程层面:掌握嵌入式SQL和CLI编程,能够编写健壮的数据库应用程序
- 高级应用:实现批量操作、分布式事务和性能优化
- 问题解决:快速诊断和解决常见的连接、性能和数据一致性问题
在实际项目中,建议根据具体需求选择合适的调用方式:
- 简单查询和报表:使用直接SQL
- 复杂业务逻辑:使用存储过程
- 高性能要求:使用CLI批量操作
- 跨平台应用:使用ODBC/CLI标准接口
记住,良好的错误处理、资源管理和性能优化是编写高质量DB2应用程序的关键。持续监控和调优将帮助您构建稳定、高效的数据库应用系统。# DB2调用方法详解从基础语法到高级应用实战与常见问题解决方案
引言:DB2调用方法的重要性
DB2作为IBM公司开发的企业级关系型数据库管理系统,广泛应用于金融、制造、零售等关键行业。掌握DB2的调用方法对于开发人员、DBA和数据分析师来说至关重要。本文将从基础语法开始,逐步深入到高级应用实战,并提供常见问题的解决方案,帮助读者全面掌握DB2的调用技术。
在现代企业应用中,DB2调用通常涉及多种场景:从简单的CRUD操作到复杂的存储过程调用,从单机应用到分布式系统集成。理解不同调用方式的优缺点和适用场景,能够帮助我们设计出更高效、更可靠的数据库交互方案。本文将涵盖SQL调用、CLI调用、嵌入式SQL、存储过程调用等多种方式,并提供详细的代码示例和最佳实践。
1. 基础语法:SQL调用入门
1.1 基本SQL语句结构
DB2的SQL调用是最基础也是最常用的方式。首先,我们需要了解基本的SQL语句结构:
-- 连接到数据库
CONNECT TO 数据库名 USER 用户名 USING 密码;
-- 基本查询语句
SELECT 列名1, 列名2 FROM 表名 WHERE 条件;
-- 插入语句
INSERT INTO 表名 (列名1, 列名2) VALUES (值1, 值2);
-- 更新语句
UPDATE 表名 SET 列名1 = 值1 WHERE 条件;
-- 删除语句
DELETE FROM 表名 WHERE 条件;
1.2 参数标记的使用
在实际应用中,为了避免SQL注入和提高性能,我们通常使用参数标记(?)来代替直接的值:
-- 使用参数标记的查询
SELECT * FROM EMPLOYEE WHERE DEPT = ? AND SALARY > ?;
-- 使用参数标记的插入
INSERT INTO EMPLOYEE (EMPNO, ENAME, DEPT, SALARY) VALUES (?, ?, ?, ?);
1.3 游标操作
对于需要逐行处理结果集的情况,我们需要使用游标:
-- 声明游标
DECLARE emp_cursor CURSOR FOR
SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?;
-- 打开游标
OPEN emp_cursor;
-- 获取数据
FETCH emp_cursor INTO :empno, :ename, :salary;
-- 关闭游标
CLOSE emp_cursor;
2. 嵌入式SQL编程
2.1 嵌入式SQL基础
嵌入式SQL是将SQL语句直接嵌入到宿主语言(如C/C++、COBOL等)代码中的一种方式。DB2通过预编译器处理这些SQL语句,将其转换为宿主语言的函数调用。
// C语言中的嵌入式SQL示例
#include <sqlca.h>
EXEC SQL BEGIN DECLARE SECTION;
char empno[7];
char ename[51];
double salary;
char dept[4];
EXEC SQL END DECLARE SECTION;
// 连接数据库
EXEC SQL CONNECT TO MYDB USER myuser USING mypass;
// 查询员工信息
EXEC SQL SELECT EMPNO, ENAME, SALARY
INTO :empno, :ename, :salary
FROM EMPLOYEE
WHERE DEPT = 'D01';
// 处理结果
printf("Employee: %s, Salary: %.2f\n", ename, salary);
// 断开连接
EXEC SQL DISCONNECT CURRENT;
2.2 游标在嵌入式SQL中的应用
// 使用游标处理多行结果
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?;
EXEC SQL OPEN emp_cursor USING :input_dept;
while (1) {
EXEC SQL FETCH emp_cursor INTO :empno, :ename, :salary;
if (SQLCODE != 0) break; // SQLCODE=0表示成功,100表示无数据
// 处理每一行数据
printf("Employee: %s, Salary: %.2f\n", ename, salary);
}
EXEC SQL CLOSE emp_cursor;
2.3 错误处理
在嵌入式SQL中,我们需要检查SQLCA(SQL通信区)来处理错误:
// 错误处理示例
if (SQLCODE < 0) {
printf("SQL Error: %ld\n", SQLCODE);
printf("Error Message: %s\n", sqlca.sqlerrm.sqlerrmc);
// 执行回滚操作
EXEC SQL ROLLBACK;
} else if (SQLCODE == 0) {
// 成功执行
EXEC SQL COMMIT;
}
3. CLI/ODBC调用方法
3.1 CLI基础概念
CLI(Call Level Interface)是DB2提供的标准编程接口,基于X/Open和SQL/CLI标准。它比嵌入式SQL更灵活,不需要预编译,是现代应用开发的推荐方式。
3.2 CLI编程步骤
以下是使用DB2 CLI连接和查询数据库的完整示例:
#include <sqlcli1.h>
#include <stdio.h>
#include <string.h>
void check_error(SQLRETURN rc, SQLSMALLINT handle_type, SQLHANDLE handle) {
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLSMALLINT length;
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), &length);
printf("Error: %s - %s\n", sqlstate, message);
}
}
int main() {
SQLRETURN rc;
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLCHAR connstr[] = "DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;PROTOCOL=TCPIP;UID=myuser;PWD=mypass;";
SQLCHAR sql[] = "SELECT EMPNO, ENAME, SALARY FROM EMPLOYEE WHERE DEPT = ?";
SQLCHAR dept[4] = "D01";
SQLCHAR empno[7];
SQLCHAR ename[51];
SQLDOUBLE salary;
SQLLEN empno_ind, ename_ind, salary_ind;
// 1. 分配环境句柄
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
check_error(rc, SQL_HANDLE_ENV, env);
// 2. 设置属性
rc = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
check_error(rc, SQL_HANDLE_ENV, env);
// 3. 分配连接句柄
rc = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
check_error(rc, SQL_HANDLE_DBC, dbc);
// 4. 连接数据库
rc = SQLDriverConnect(dbc, NULL, connstr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
check_error(rc, SQL_HANDLE_DBC, dbc);
// 5. 分配语句句柄
rc = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 6. 准备SQL语句
rc = SQLPrepare(stmt, sql, SQL_NTS);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 7. 绑定参数
rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
sizeof(dept), 0, dept, sizeof(dept), NULL);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 8. 执行查询
rc = SQLExecute(stmt);
check_error(rc, SQL_HANDLE_STMT, stmt);
// 9. 绑定结果列
SQLBindCol(stmt, 1, SQL_C_CHAR, empno, sizeof(empno), &empno_ind);
SQLBindCol(stmt, 2, SQL_C_CHAR, ename, sizeof(ename), &ename_ind);
SQLBindCol(stmt, 3, SQL_C_DOUBLE, &salary, 0, &salary_ind);
// 10. 获取结果
printf("Employee List:\n");
while ((rc = SQLFetch(stmt)) == SQL_SUCCESS) {
printf("EmpNo: %s, Name: %s, Salary: %.2f\n", empno, ename, salary);
}
// 11. 清理资源
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return 0;
}
3.3 CLI中的事务控制
// 事务控制示例
// 开始事务(自动模式,DB2默认自动提交为开启状态)
SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
// 执行多个操作
SQLExecute(insert_stmt);
SQLExecute(update_stmt);
// 提交事务
SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT);
// 或者回滚
SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_ROLLBACK);
// 恢复自动提交
SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_ON, 0);
4. 存储过程调用
4.1 存储过程基础
存储过程是预编译的SQL代码集合,可以提高性能、增强安全性并减少网络流量。DB2支持多种语言编写的存储过程,包括SQL PL、C/C++、Java等。
4.2 SQL PL存储过程示例
-- 创建一个简单的SQL PL存储过程
CREATE PROCEDURE GET_EMPLOYEE_SALARY (
IN p_empno VARCHAR(7),
OUT p_salary DECIMAL(10,2),
OUT p_ename VARCHAR(50)
)
LANGUAGE SQL
BEGIN
SELECT ENAME, SALARY INTO p_ename, p_salary
FROM EMPLOYEE
WHERE EMPNO = p_empno;
END;
-- 调用存储过程
CALL GET_EMPLOYEE_SALARY('000010', ?, ?);
4.3 C语言编写的存储过程
// C语言存储过程示例
#include <sqludf.h>
#include <string.h>
#include <stdio.h>
// 存储过程函数定义
void SQL_API_FN GET_EMPLOYEE_INFO(
SQLUDF_VARCHAR *empno,
SQLUDF_VARCHAR *ename,
SQLUDF_DECIMAL *salary,
SQLUDF_NULLIND *empno_ind,
SQLUDF_NULLIND *ename_ind,
SQLUDF_NULLIND *salary_ind,
SQLUDF_TRAIL_ARGS) {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN rc;
SQLCHAR sql[256];
SQLLEN ename_len, salary_len;
// 分配句柄
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
// 连接数据库(使用DB2连接字符串)
rc = SQLConnect(dbc, (SQLCHAR*)"MYDB", SQL_NTS,
(SQLCHAR*)"myuser", SQL_NTS,
(SQLCHAR*)"mypass", SQL_NTS);
if (rc == SQL_SUCCESS) {
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
sprintf((char*)sql, "SELECT ENAME, SALARY FROM EMPLOYEE WHERE EMPNO = '%s'", empno);
rc = SQLExecDirect(stmt, sql, SQL_NTS);
if (rc == SQL_SUCCESS) {
SQLBindCol(stmt, 1, SQL_C_CHAR, ename, 50, &ename_len);
SQLBindCol(stmt, 2, SQL_C_DOUBLE, salary, 0, &salary_len);
SQLFetch(stmt);
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
SQLDisconnect(dbc);
}
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
}
4.4 调用存储过程
-- 在SQL中调用
CALL GET_EMPLOYEE_INFO('000010', ?, ?);
-- 在CLI/C中调用存储过程
SQLCHAR call_sql[] = "CALL GET_EMPLOYEE_INFO(?, ?, ?)";
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLPrepare(stmt, call_sql, SQL_NTS);
// 绑定输入参数
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
7, 0, empno, 7, NULL);
// 绑定输出参数
SQLBindParameter(stmt, 2, SQL_PARAM_OUTPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, ename, 50, &ename_ind);
SQLBindParameter(stmt, 3, SQL_PARAM_OUTPUT, SQL_C_DOUBLE, SQL_DECIMAL,
10, 2, &salary, 0, &salary_ind);
SQLExecute(stmt);
5. 高级应用实战
5.1 批量操作优化
批量操作可以显著提高数据处理效率,特别是在数据加载和批量更新场景中。
-- 批量插入(使用参数标记)
INSERT INTO EMPLOYEE (EMPNO, ENAME, DEPT, SALARY) VALUES (?, ?, ?, ?);
-- 在CLI中实现批量插入
#define BATCH_SIZE 1000
SQLINTEGER empno_batch[BATCH_SIZE];
SQLCHAR ename_batch[BATCH_SIZE][51];
SQLCHAR dept_batch[BATCH_SIZE][4];
SQLDOUBLE salary_batch[BATCH_SIZE];
SQLLEN empno_ind[BATCH_SIZE], ename_ind[BATCH_SIZE],
dept_ind[BATCH_SIZE], salary_ind[BATCH_SIZE];
// 准备语句
SQLPrepare(stmt, sql, SQL_NTS);
// 绑定参数数组
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, empno_batch, 0, empno_ind);
SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, ename_batch, 50, ename_ind);
SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
3, 0, dept_batch, 3, dept_ind);
SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DECIMAL,
10, 2, salary_batch, 0, salary_ind);
// 执行批量插入
SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (void*)BATCH_SIZE, 0);
SQLExecute(stmt);
5.2 分布式事务处理
在分布式环境中,需要使用两阶段提交(2PC)来保证数据一致性:
// 分布式事务示例
// 连接两个数据库
SQLHDBC dbc1, dbc2;
// 开始事务
SQLSetConnectAttr(dbc1, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
SQLSetConnectAttr(dbc2, SQL_ATTR_AUTOCOMMIT, (void*)SQL_AUTOCOMMIT_OFF, 0);
// 执行操作
SQLExecute(stmt1); // 在dbc1上执行
SQLExecute(stmt2); // 在dbc2上准备
// 准备提交(两阶段提交的第一阶段)
SQLEndTran(SQL_HANDLE_DBC, dbc1, SQL_PREPARE);
SQLEndTran(SQL_HANDLE_DBC, dbc2, SQL_PREPARE);
// 检查所有节点是否准备就绪
// 如果都成功,则提交
SQLEndTran(SQL_HANDLE_DBC, dbc1, SQL_COMMIT);
SQLEndTran(SQL_HANDLE_DBC, dbc2, SQL_COMMIT);
5.3 性能优化技巧
5.3.1 索引优化
-- 创建合适的索引
CREATE INDEX idx_emp_dept ON EMPLOYEE(DEPT);
CREATE INDEX idx_emp_salary ON EMPLOYEE(SALARY DESC);
-- 查看执行计划
EXPLAIN PLAN FOR SELECT * FROM EMPLOYEE WHERE DEPT = 'D01' AND SALARY > 50000;
-- 使用索引提示(如果需要)
SELECT * FROM EMPLOYEE WHERE DEPT = 'D01' --+ INDEX(idx_emp_dept) */;
5.3.2 查询优化
-- 避免SELECT *
SELECT EMPNO, ENAME FROM EMPLOYEE WHERE DEPT = 'D01';
-- 使用EXISTS代替IN
SELECT * FROM DEPARTMENT D WHERE EXISTS (
SELECT 1 FROM EMPLOYEE E WHERE E.DEPT = D.DEPTNO AND E.SALARY > 50000
);
-- 避免在WHERE子句中对列进行函数操作
-- 不好:WHERE YEAR(HIRE_DATE) = 2020
-- 好:WHERE HIRE_DATE >= '2020-01-01' AND HIRE_DATE < '2021-01-01'
5.3.3 连接池管理
// CLI连接池示例
SQLHENV env;
SQLHDBC dbc;
SQLINTEGER pool_size = 10;
// 设置连接池属性
SQLSetEnvAttr(env, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_DRIVER, 0);
SQLSetEnvAttr(env, SQL_ATTR_CP_MATCH, (void*)SQL_CP_STRICT_MATCH, 100);
// 获取连接
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
SQLConnect(dbc, ...);
// 使用连接...
// 释放连接(实际返回到池中)
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
6. 常见问题解决方案
6.1 连接问题
问题1:无法连接到数据库
症状:返回错误代码 -5005 或 “Unable to connect to database”
解决方案:
# 1. 检查数据库实例是否运行
db2level
db2 get instance
# 2. 检查数据库是否已启动
db2 activate database MYDB
# 3. 检查通信配置
db2set -all
db2 get dbm cfg | grep SVCE
# 4. 测试连接
db2 connect to MYDB user myuser using mypass
# 5. 在代码中增加详细错误信息
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), NULL);
printf("Connection Error: %s - %s\n", sqlstate, message);
问题2:连接超时
解决方案:
-- 在数据库配置中调整超时参数
UPDATE DBM CFG USING SVCename DB2c_db2inst1;
UPDATE DBM CFG USING KEEPTCPALIVE YES;
-- 在连接字符串中增加超时参数
DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;TIMEOUT=30;
6.2 性能问题
问题1:查询执行缓慢
诊断步骤:
-- 1. 查看当前活动
SELECT * FROM SYSIBMADM.LONG_RUNNING_QUERIES;
-- 2. 查看锁等待
SELECT * FROM SYSIBMADM.LOCKWAITS;
-- 3. 查看执行计划
EXPLAIN PLAN FOR SELECT * FROM EMPLOYEE WHERE DEPT = 'D01';
-- 4. 查看统计信息
SELECT * FROM SYSIBMADM.GET_DBSIZE_INFO;
问题2:内存泄漏
解决方案:
// 确保正确释放所有CLI句柄
// 错误示例(会导致内存泄漏):
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLExecute(stmt);
// 忘记调用 SQLFreeHandle
// 正确示例:
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
SQLExecute(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt); // 必须释放
// 使用RAII模式(C++)或try-finally(Java)确保资源释放
6.3 数据一致性问题
问题1:死锁
解决方案:
-- 1. 查看死锁图
SELECT * FROM SYSIBMADM.GET_LOCKWAITS();
-- 2. 设置死锁检测频率
UPDATE DBM CFG USING DLCHKTIME 5000; -- 5秒
-- 3. 在应用程序中设置锁超时
SET CURRENT LOCK TIMEOUT = 30; -- 30秒
-- 4. 优化事务设计,减少锁持有时间
-- 避免在事务中进行用户交互
-- 按相同顺序访问表
问题2:数据完整性约束违反
解决方案:
-- 1. 查看约束定义
SELECT * FROM SYSCAT.TABCONST WHERE TABNAME = 'EMPLOYEE';
-- 2. 查看外键依赖
SELECT * FROM SYSCAT.REFERENCES WHERE REFTABNAME = 'EMPLOYEE';
-- 3. 使用DEFERRED约束检查
-- 在创建表时指定
CREATE TABLE ORDERS (
ORDERNO INT NOT NULL,
CUSTNO INT,
CONSTRAINT FK_CUST FOREIGN KEY (CUSTNO) REFERENCES CUSTOMER(CUSTNO)
ON DELETE CASCADE
ON UPDATE NO ACTION
ENFORCED
ENABLE QUERY OPTIMIZATION
);
6.4 编程错误
问题1:SQLCODE错误处理
常见SQLCODE及其含义:
// SQLCODE = 0: 成功
// SQLCODE = 100: 未找到数据
// SQLCODE = -803: 违反唯一约束
// SQLCODE = -805: 未找到程序包
// SQLCODE = -911: 死锁或超时
// SQLCODE = -30081: 连接错误
void handle_sql_error(SQLRETURN rc, SQLSMALLINT handle_type, SQLHANDLE handle) {
SQLCHAR sqlstate[6];
SQLCHAR message[1024];
SQLSMALLINT length;
SQLError(SQL_NULL_ENV, SQL_NULL_DBC, SQL_NULL_STMT,
sqlstate, NULL, message, sizeof(message), &length);
printf("SQL Error: %s\n", sqlstate);
printf("Message: %s\n", message);
// 根据错误类型采取不同措施
if (strcmp((char*)sqlstate, "23505") == 0) {
// 违反唯一约束
printf("Duplicate key error\n");
} else if (strcmp((char*)sqlstate, "40001") == 0) {
// 事务回滚
printf("Transaction rolled back\n");
}
}
问题2:字符集编码问题
解决方案:
-- 1. 检查数据库编码
SELECT GET_DB_CODEPAGE() FROM SYSIBM.SYSDUMMY1;
-- 2. 在连接字符串中指定编码
DATABASE=MYDB;HOSTNAME=localhost;PORT=50000;CODEPAGE=1208; -- UTF-8
-- 3. 在CLI中设置环境属性
SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, (void*)SQL_TRUE, 0);
7. 最佳实践与性能优化
7.1 代码组织最佳实践
// 封装数据库操作
typedef struct {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
int connected;
} db2_context;
// 初始化函数
int db2_init(db2_context *ctx) {
SQLRETURN rc;
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ctx->env);
if (rc != SQL_SUCCESS) return -1;
rc = SQLSetEnvAttr(ctx->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (rc != SQL_SUCCESS) return -1;
rc = SQLAllocHandle(SQL_HANDLE_DBC, ctx->env, &ctx->DBC);
if (rc != SQL_SUCCESS) return -1;
ctx->connected = 0;
return 0;
}
// 连接函数
int db2_connect(db2_context *ctx, const char *db, const char *user, const char *pass) {
SQLRETURN rc = SQLConnect(ctx->dbc, (SQLCHAR*)db, SQL_NTS,
(SQLCHAR*)user, SQL_NTS,
(SQLCHAR*)pass, SQL_NTS);
if (rc == SQL_SUCCESS) {
ctx->connected = 1;
return 0;
}
return -1;
}
// 清理函数
void db2_cleanup(db2_context *ctx) {
if (ctx->stmt) SQLFreeHandle(SQL_HANDLE_STMT, ctx->stmt);
if (ctx->connected) SQLDisconnect(ctx->dbc);
if (ctx->dbc) SQLFreeHandle(SQL_HANDLE_DBC, ctx->dbc);
if (ctx->env) SQLFreeHandle(SQL_HANDLE_ENV, ctx->env);
}
7.2 性能监控
-- 监控SQL执行性能
SELECT
STMT_TEXT,
NUM_EXECUTIONS,
TOTAL_SECTION_TIME,
AVG_SECTION_TIME,
ROWS_READ,
ROWS_WRITTEN
FROM SYSIBMADM.GET_ACTIVITY_LOG()
WHERE ACTIVITY_TYPE = 'SQL'
ORDER BY AVG_SECTION_TIME DESC
FETCH FIRST 10 ROWS ONLY;
-- 查看缓冲池命中率
SELECT
BP_NAME,
DATA_READS,
DATA_WRITES,
(1 - (DATA_READS / NULLIF(PAGE_READS, 0))) * 100 AS HIT_RATIO
FROM SYSIBMADM.BP_HITRATIO;
7.3 安全最佳实践
-- 1. 使用参数化查询防止SQL注入
-- 错误:直接拼接字符串
SELECT * FROM EMPLOYEE WHERE ENAME = ' || input_name || ';
-- 正确:使用参数标记
SELECT * FROM EMPLOYEE WHERE ENAME = ?;
-- 2. 最小权限原则
CREATE USER myappuser FOR DATA ACCESS;
GRANT SELECT, INSERT ON EMPLOYEE TO myappuser;
REVOKE DELETE ON EMPLOYEE FROM myappuser;
-- 3. 审计敏感操作
-- 启用审计
UPDATE DBM CFG USING DFT_AUDIT_BUF_SZ 1024;
-- 审计特定表
AUDIT SELECT ON EMPLOYEE BY USER myappuser;
8. 总结
DB2调用方法涵盖了从基础SQL到高级CLI编程的多个层面。掌握这些技术需要理解不同方法的适用场景、性能特点和最佳实践。通过本文的详细讲解和代码示例,读者应该能够:
- 基础层面:熟练使用SQL语句进行数据操作,理解参数标记和游标的概念
- 编程层面:掌握嵌入式SQL和CLI编程,能够编写健壮的数据库应用程序
- 高级应用:实现批量操作、分布式事务和性能优化
- 问题解决:快速诊断和解决常见的连接、性能和数据一致性问题
在实际项目中,建议根据具体需求选择合适的调用方式:
- 简单查询和报表:使用直接SQL
- 复杂业务逻辑:使用存储过程
- 高性能要求:使用CLI批量操作
- 跨平台应用:使用ODBC/CLI标准接口
记住,良好的错误处理、资源管理和性能优化是编写高质量DB2应用程序的关键。持续监控和调优将帮助您构建稳定、高效的数据库应用系统。
