引言: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编程的多个层面。掌握这些技术需要理解不同方法的适用场景、性能特点和最佳实践。通过本文的详细讲解和代码示例,读者应该能够:

  1. 基础层面:熟练使用SQL语句进行数据操作,理解参数标记和游标的概念
  2. 编程层面:掌握嵌入式SQL和CLI编程,能够编写健壮的数据库应用程序
  3. 高级应用:实现批量操作、分布式事务和性能优化
  4. 问题解决:快速诊断和解决常见的连接、性能和数据一致性问题

在实际项目中,建议根据具体需求选择合适的调用方式:

  • 简单查询和报表:使用直接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编程的多个层面。掌握这些技术需要理解不同方法的适用场景、性能特点和最佳实践。通过本文的详细讲解和代码示例,读者应该能够:

  1. 基础层面:熟练使用SQL语句进行数据操作,理解参数标记和游标的概念
  2. 编程层面:掌握嵌入式SQL和CLI编程,能够编写健壮的数据库应用程序
  3. 高级应用:实现批量操作、分布式事务和性能优化
  4. 问题解决:快速诊断和解决常见的连接、性能和数据一致性问题

在实际项目中,建议根据具体需求选择合适的调用方式:

  • 简单查询和报表:使用直接SQL
  • 复杂业务逻辑:使用存储过程
  • 高性能要求:使用CLI批量操作
  • 跨平台应用:使用ODBC/CLI标准接口

记住,良好的错误处理、资源管理和性能优化是编写高质量DB2应用程序的关键。持续监控和调优将帮助您构建稳定、高效的数据库应用系统。