提到c/c++ 开发与数据库相关的部分,我们总是会第一时间想到ODBC接口,但官方文档中还有c/c++接口并不只有ODBC,下边我们来将一下其中的ODBC/DPI如何使用以及它们的不同。
DM ODBC 3.0 遵照 Microsoft ODBC 3.0 规范设计与开发,符合大家使用的使用习惯,也比较容易上手。以c++为例:
#include <iostream>
#include <stdexcept>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
using namespace std;
/* 检测返回代码是否为成功标志,当为成功标志返回 true,否则返回 false */
#define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO)
#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))
void PrintODBCError(SQLSMALLINT handleType, SQLHANDLE handle) {
SQLCHAR sqlState[6];
SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER nativeError;
SQLSMALLINT textLength;
SQLRETURN ret;
SQLSMALLINT i = 1;
while ((ret = SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError,
message, sizeof(message), &textLength)) != SQL_NO_DATA) {
if (RC_SUCCESSFUL(ret)) {
cerr << "SQLSTATE: " << sqlState << ", Native Error Code: " << nativeError
<< ", Message: " << message << endl;
}
i++;
}
}
class ODBCConnection {
public:
ODBCConnection(const string& driverPath, const string& user, const string& pass)
: henv(SQL_NULL_HENV), hdbc(SQL_NULL_HDBC), hstmt(SQL_NULL_HSTMT) {
// 申请环境句柄
if (SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv) != SQL_SUCCESS) {
throw runtime_error("Failed to allocate environment handle.");
}
// 设置 ODBC 版本
if (SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER) != SQL_SUCCESS) {
throw runtime_error("Failed to set ODBC version.");
}
// 申请连接句柄
if (SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) != SQL_SUCCESS) {
throw runtime_error("Failed to allocate connection handle.");
}
// 动态加载驱动程序并连接到数据库
string connStr = "DRIVER={" + driverPath + "};"
"SERVER=localhost;PORT=5236;"
"UID=" + user + ";"
"PWD=" + pass + ";";
SQLRETURN sret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr.c_str(), SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_COMPLETE);
if (RC_NOTSUCCESSFUL(sret)) {
PrintODBCError(SQL_HANDLE_DBC, hdbc);
throw runtime_error("ODBC: Fail to connect to server!");
}
cout << "ODBC: Connected to server successfully!" << endl;
}
~ODBCConnection() {
// 释放资源
if (hstmt != SQL_NULL_HSTMT) {
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
if (hdbc != SQL_NULL_HDBC) {
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
if (henv != SQL_NULL_HENV) {
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
}
void executeQuery(const string& query) {
// 申请一个语句句柄
if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS) {
throw runtime_error("Failed to allocate statement handle.");
}
// 执行 SQL 查询
SQLRETURN sret = SQLExecDirect(hstmt, (SQLCHAR*)query.c_str(), SQL_NTS);
if (RC_NOTSUCCESSFUL(sret)) {
PrintODBCError(SQL_HANDLE_STMT, hstmt);
throw runtime_error("ODBC: Query execution failed.");
}
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
void fetchResults() {
SQLLEN out_c1_ind = 0, out_c2_ind = 0;
int out_c1 = 0;
SQLCHAR out_c2[20] = { 0 };
// 申请一个语句句柄
if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS) {
throw runtime_error("Failed to allocate statement handle.");
}
// 执行 SELECT 查询
SQLRETURN sret = SQLExecDirect(hstmt, (SQLCHAR*)"select * from TEST.MY_TEST", SQL_NTS);
if (RC_NOTSUCCESSFUL(sret)) {
PrintODBCError(SQL_HANDLE_STMT, hstmt);
throw runtime_error("ODBC: SELECT query failed.");
}
// 绑定列
SQLBindCol(hstmt, 1, SQL_C_SLONG, &out_c1, sizeof(out_c1), &out_c1_ind);
SQLBindCol(hstmt, 2, SQL_C_CHAR, &out_c2, sizeof(out_c2), &out_c2_ind);
cout << "odbc: select from table..." << endl;
while (SQLFetch(hstmt) != SQL_NO_DATA) {
cout << "c1 = " << out_c1 << ", c2 = " << out_c2 << endl;
}
cout << "odbc: select success" << endl;
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
private:
HENV henv; // 环境句柄
HDBC hdbc; // 连接句柄
HSTMT hstmt; // 语句句柄
};
int main() {
try {
// 创建连接对象并执行操作
ODBCConnection conn("/home/dmdba/dmdbms/bin/libdodbc.so", "SYSDBA", "SYSDBA");
// 执行插入、删除、更新操作
conn.executeQuery("delete from TEST.MY_TEST");
conn.executeQuery("insert into TEST.MY_TEST(C2) values('语文'), ('数学'), ('英语'), ('体育')");
conn.executeQuery("delete from TEST.MY_TEST where C2='数学'");
conn.executeQuery("update TEST.MY_TEST set C2 = '英语-新课标' where C2='英语'");
// 查询数据
conn.fetchResults();
} catch (const runtime_error &e) {
cerr << "Error: " << e.what() << endl;
}
return 0;
}
提前准备环境,搭建DW,建表
CREATE TABLE "TEST"."MY_TEST"
(
"C1" INT,
"C2" VARCHAR(40)) STORAGE(ON "MAIN", CLUSTERBTR) ADD LOGIC LOG ;
安装基础软件
[root@localhost ~]# yum install unixODBC-devel gcc-c++ [root@localhost ~]# g++ --version g++ (GCC) 7.3.0 Copyright © 2017 Free Software Foundation, Inc. 本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保; 包括没有适销性和某一专用目的下的适用性担保。
编译运行
[dmdba@localhost c++]$ g++ -std=c++17 -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldodbc -o curd curd.cpp [dmdba@localhost c++]$ ./curd ODBC: Connected to server successfully! odbc: select from table... c1 = 0, c2 = 语文 c1 = 0, c2 = 英语-新课标 c1 = 0, c2 = 体育 odbc: select success
#include <sql.h>
#include <sqlext.h>
#include <iostream>
#include <unistd.h>
// 定义成功标志的宏
#define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO)
#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))
// 连接数据库函数
SQLRETURN connect_to_db(SQLHDBC *hdbc, const char *connectionString) {
SQLRETURN ret = SQLDriverConnect(*hdbc, NULL, (SQLCHAR *)connectionString, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
return ret;
}
// 检查数据库连接状态的函数
bool check_connection(SQLHDBC hdbc) {
SQLHSTMT hstmt;
SQLRETURN ret;
// 创建语句句柄
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
// 执行简单查询检查连接
ret = SQLExecDirect(hstmt, (SQLCHAR *)"SELECT 1 FROM DUAL", SQL_NTS);
// 释放语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
// 如果查询成功则认为连接有效
return (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO);
}
// 打印ODBC错误信息的函数
void PrintODBCError(SQLSMALLINT handleType, SQLHANDLE handle) {
SQLCHAR sqlState[6];
SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER nativeError;
SQLSMALLINT textLength;
SQLRETURN ret;
SQLSMALLINT i = 1;
while ((ret = SQLGetDiagRec(handleType, handle, i, sqlState, &nativeError,
message, sizeof(message), &textLength)) != SQL_NO_DATA) {
if (RC_SUCCESSFUL(ret)) {
std::cerr << "SQLSTATE: " << sqlState << ", Native Error Code: " << nativeError
<< ", Message: " << message << std::endl;
}
i++;
}
}
// 查询 test 表的函数
void query_test_table(SQLHDBC hdbc) {
SQLHSTMT hstmt;
SQLRETURN ret;
SQLCHAR query[] = "SELECT * FROM TEST.MY_TEST LIMIT 1"; // 查询 test 表的所有记录
SQLINTEGER c1;
SQLCHAR c2[40];
// 创建语句句柄
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
// 执行查询
ret = SQLExecDirect(hstmt, query, SQL_NTS);
if (RC_SUCCESSFUL(ret)) {
// 绑定列
SQLBindCol(hstmt, 1, SQL_C_LONG, &c1, sizeof(c1), NULL);
SQLBindCol(hstmt, 2, SQL_C_CHAR, c2, sizeof(c2), NULL);
// 逐行提取结果
while (SQLFetch(hstmt) == SQL_SUCCESS) {
std::cout << "C1: " << c1 << ", C2: " << c2 << std::endl;
}
} else {
std::cerr << "查询失败!" << std::endl;
}
// 释放语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
int main() {
SQLHENV henv;
SQLHDBC hdbc;
SQLRETURN ret;
const char *dsn_primary =
"DRIVER={/home/dmdba/dmdbms/bin/libdodbc.so};SERVER=192.168.131.152;PORT=5236;UID=SYSDBA;PWD=SYSDBA;";
const char *dsn_standby =
"DRIVER={/home/dmdba/dmdbms/drivers/odbc/libdodbc.so};SERVER=192.168.131.153;PORT=5237;UID=SYSDBA;PWD=SYSDBA;";
std::string current_db_ip;
// 申请环境句柄
SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
// 申请连接句柄
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 尝试连接主库
ret = connect_to_db(&hdbc, dsn_primary);
if (RC_NOTSUCCESSFUL(ret)) {
std::cout << "主库连接失败,尝试连接从库..." << std::endl;
// 断开当前连接并释放资源
SQLDisconnect(hdbc); // 断开当前连接
SQLFreeHandle(SQL_HANDLE_DBC, hdbc); // 释放数据库连接句柄
// 重新申请连接句柄
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 尝试连接从库
ret = connect_to_db(&hdbc, dsn_standby);
if (RC_NOTSUCCESSFUL(ret)) {
std::cerr << "无法连接到主库或从库。" << std::endl;
PrintODBCError(SQL_HANDLE_DBC, hdbc);
return 1;
} else {
current_db_ip = "192.168.131.153"; // 设置为从库的 IP
std::cout << "连接到从库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl;
}
} else {
current_db_ip = "192.168.131.152"; // 设置为主库的 IP
std::cout << "连接到主库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl;
}
// 持续监控主库连接
while (true) {
// 检查主库连接是否正常
if (!check_connection(hdbc)) {
std::cout << "主库不可达,切换到从库..." << std::endl;
// 断开当前连接并释放资源
SQLDisconnect(hdbc); // 断开当前连接
SQLFreeHandle(SQL_HANDLE_DBC, hdbc); // 释放数据库连接句柄
// 重新申请连接句柄
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 主库不可用时,尝试连接到从库
ret = connect_to_db(&hdbc, dsn_standby);
if (RC_SUCCESSFUL(ret)) {
current_db_ip = "192.168.131.153"; // 设置为从库的 IP
std::cout << "切换到从库成功! 当前连接的数据库 IP: " << current_db_ip << std::endl;
} else {
std::cerr << "无法连接到从库。" << std::endl;
PrintODBCError(SQL_HANDLE_DBC, hdbc);
}
}
// 持续查询 test 表,展示自动切换的过程
query_test_table(hdbc);
// 休眠 5 秒再执行下一次查询
sleep(5); // 每5秒查询一次
}
// 释放句柄
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
代码为了演示,连接配置,表名等都是硬编码,生产中切忌这样做,可以作为参数传入,或使用配置文件。
从ODBC实现来看,数据库主备切换时,驱动中的功能明显不够用,需要我们自己去写一个连接池来实现。
做一个连接池的工作量是比较大的,需要考虑的事情比较多:
1. 基本功能
1.连接复用:维护一定数量的连接对象,减少频繁建立和关闭连接的开销。
2.最大连接数限制:防止连接池无限增长导致资源耗尽。
3.连接超时管理:处理长时间未使用或卡死的连接。
4.负载均衡:当后端是分布式数据库时,需要智能分配连接到不同的节点。
5.连接生命周期:检测连接的健康状态并在必要时重新建立。
2. 支持复杂架构
1.主备架构:
主库读写分离:为写操作优先选择主库,为读操作选择备库。
主库故障切换:实时监控主库健康状况,自动切换到备库。
2.共享存储:
确保每个实例对同一存储的一致性操作。
提供全局锁机制防止竞争条件。
3.分布式架构:
节点发现机制:动态感知新增或失效的数据库节点。
数据分片:根据请求的分片键选择目标节点。
全局事务支持:需要集成分布式事务管理器(如两阶段提交或三阶段提交)。
3. 性能优化
1.连接池大小:根据并发量和数据库性能调整连接池的最大、最小大小。
2.并发控制:高效的线程安全机制,避免线程争用。
3.缓存机制:对频繁查询的结果进行缓存,减少数据库查询压力。
4.最小化网络延迟:通过优化协议和批量操作减少网络开销。
4. 异常处理
1.数据库连接失败的重试机制。
2.连接泄漏检测:检查应用是否正确归还连接。
3.日志与监控:提供详细的运行日志和监控接口,便于调试和性能分析。
特别c/c++这类基础语言要做的事情就更多了:
1. 系统资源管理
1.内存管理:
连接对象的内存分配和释放需要严格控制,避免内存泄漏。
使用智能指针(如malloc
/free
配对或C库工具)管理动态内存。
2.文件描述符管理:
数据库连接通常会占用大量文件描述符,需要避免句柄泄漏。
定期检查和关闭闲置的连接句柄。
2. 网络通信
1.高效的Socket管理:
支持非阻塞I/O,避免阻塞线程。
使用高效的事件驱动模型(如epoll
、kqueue
或select
)。
2.通信协议实现:
如果使用特定数据库协议,需要自行实现其解析逻辑。
考虑协议压缩和安全性(如TLS)。
3. 多线程编程
1.线程安全:
使用线程安全的数据结构(如队列、哈希表)。
利用同步原语(如互斥锁、条件变量)保护共享资源。
2.无锁编程:
在性能敏感场景下,可以尝试无锁队列或原子操作优化多线程访问。
4. 数据一致性
1.事务支持:
在连接池中维护事务上下文,确保多线程环境下事务的独立性。
2.故障恢复:
当数据库连接中断时,需要重新建立连接并恢复状态。
5. 依赖库与工具
1.使用成熟的C库:
避免从零开发,除非有特殊需求。选择依赖库是尽量选择成熟通用的。
2.调试工具:
使用valgrind
检测内存泄漏。
通过gdb
调试并发问题。
6. 性能优化
1.内存池:
为频繁创建和销毁的对象(如连接结构体)建立内存池。
2.CPU缓存优化:
数据结构设计要考虑缓存友好性(如避免过多的指针跳转)。
3.批量操作:
合并小型数据库操作,减少请求数量。
7. 跨平台兼容性
1.系统API差异:
注意不同操作系统的线程、网络I/O、定时器API差异。
2.编译环境:
建议使用跨平台工具生成编译配置。
那为了实现可以故障自动切换,有没有更好的办法呢?答案:DPI。
上文使用了c++做演示,这里就使用c来做,先来展示如何做最基础的dml操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DPI.h"
#include "DPIext.h"
#include "DPItypes.h"
#define DM_SVR "192.168.131.153:5237"
#define DM_USER "SYSDBA"
#define DM_PWD "SYSDBA"
dhenv henv; /* 环境句柄 */
dhcon hcon; /* 连接句柄 */
dhstmt hstmt; /* 语句句柄 */
dhdesc hdesc; /* 描述符句柄 */
DPIRETURN rt; /* 函数返回值 */
/******************************************************
Notes:
获取错误信息
*******************************************************/
void dpi_err_msg_print(sdint2 hndl_type, dhandle hndl)
{
sdint4 err_code;
sdint2 msg_len;
sdbyte err_msg[SDBYTE_MAX];
/* 获取错误信息集合 */
dpi_get_diag_rec(hndl_type, hndl, 1, &err_code, err_msg, sizeof(err_msg), &msg_len);
printf("err_msg = %s, err_code = %d\n", err_msg, err_code);
}
/*
入口函数
*/
int main(int argc, char *argv[])
{
sdint4 out_c1 = 0;
sdbyte out_c2[20]= { 0 };
slength out_c1_ind = 0;
slength out_c2_ind = 0;
ulength row_num;
//连接数据库
rt = dpi_alloc_env(&henv);
rt = dpi_alloc_con(henv, &hcon);
rt = dpi_login(hcon, (sdbyte *)DM_SVR, (sdbyte *)DM_USER, (sdbyte *)DM_PWD);
if(!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_DBC, hcon);
return rt;
}
rt = dpi_alloc_stmt(hcon, &hstmt);
//清空表,初始化测试环境
rt = dpi_exec_direct(hstmt, (sdbyte*) "delete from TEST.MY_TEST");
//插入数据
rt = dpi_exec_direct(hstmt, (sdbyte*) "insert into TEST.MY_TEST(C2) values('语文'), ('数学'), ('英语'), ('体育') ");
if(!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt);
return rt;
}
printf("dpi: insert success\n");
//删除数据
rt = dpi_exec_direct(hstmt, (sdbyte*) "delete from TEST.MY_TEST where C2='数学' ");
if(!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt);
return rt;
}
printf("dpi: delete success\n");
//更新数据
rt = dpi_exec_direct(hstmt, (sdbyte*) "update TEST.MY_TEST set C2 = '英语-新课标' where C2='英语' ");
if(!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt);
return rt;
}
printf("dpi: update success\n");
//查询数据
dpi_exec_direct(hstmt, (sdbyte*) "select * from TEST.MY_TEST");
dpi_bind_col(hstmt, 1, DSQL_C_SLONG, &out_c1, sizeof(out_c1), &out_c1_ind);
dpi_bind_col(hstmt, 2, DSQL_C_NCHAR, &out_c2, sizeof(out_c2), &out_c2_ind);
printf("dpi: select from table...\n");
while(dpi_fetch(hstmt, &row_num) != DSQL_NO_DATA)
{
printf("c1 = %d, c2 = %s ,\n", out_c1, out_c2);
}
printf("dpi: select success\n");
//断开数据库连接
rt = dpi_logout(hcon);
if(!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_DBC, hcon);
return rt;
}
rt = dpi_free_con(hcon);
rt = dpi_free_env(henv);
return rt;
}
编译运行
[dmdba@localhost c]$ gcc -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldmdpi -o dpiDml dpiDml.c [dmdba@localhost c]$ ./dpiDml dpi: insert success dpi: delete success dpi: update success dpi: select from table... c1 = 0, c2 = 语文 , c1 = 0, c2 = 英语-新课标 , c1 = 0, c2 = 体育 , dpi: select success
接下来演示使用dm_svc.conf连接主备。
export DM_SVC_PATH=/etc
配置dm_svc.conf文件
DMDW=(192.168.131.152:5236,192.168.131.153:5237)
[DMDW]
LOGIN_MODE=(1)
SWITCH_TIMES=(60)
SWITCH_INTERVAL=(1000)
AUTO_RECONNECT=(1)
AUTO_RECONNECT
连接发生异常或一些特殊场景下连接处理策略。0:关闭连接;1:当连接发生异常时自动切换到其他库,无论切换成功还是失败都会抛一个 SQLException,用于通知上层应用进行事务执行失败时的相关处理;2:配合 EP_SELECTOR>=1 使用,如果服务名列表前面的节点恢复了,将当前连接切换到前面的节点上;4:保持各节点会话动态均衡,通过后台线程检测节点及会话数变化,并切换连接使之保持均衡。也可以将 AUTO_RECONNECT 置为上述几个值的组合值,表示同时进行多项配置,如置为 3 表示同时配置 1 和 2。缺省值为 0。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "DPI.h"
#include "DPIext.h"
#include "DPItypes.h"
#define DM_SVR "DMDW"
#define DM_USER "SYSDBA"
#define DM_PWD "SYSDBA"
dhenv henv; /* 环境句柄 */
dhcon hcon; /* 连接句柄 */
DPIRETURN rt; /* 函数返回值 */
/******************************************************
Notes:
获取错误信息
*******************************************************/
void dpi_err_msg_print(sdint2 hndl_type, dhandle hndl)
{
sdint4 err_code;
sdint2 msg_len;
sdbyte err_msg[SDBYTE_MAX];
/* 获取错误信息集合 */
dpi_get_diag_rec(hndl_type, hndl, 1, &err_code, err_msg, sizeof(err_msg), &msg_len);
printf("err_msg = %s, err_code = %d\n", err_msg, err_code);
}
/*
入口函数
*/
int main(int argc, char *argv[])
{
sdbyte sess_id[20] = {0};
sdbyte user_name[50] = {0};
sdbyte instance_name[50] = {0};
sdbyte clnt_type[20] = {0};
sdbyte clnt_ip[50] = {0};
sdbyte last_recv_time[50] = {0};
slength sess_id_ind = 0, user_name_ind = 0, instance_name_ind = 0, clnt_type_ind = 0;
slength clnt_ip_ind = 0, last_recv_time_ind = 0;
//连接数据库
/* 申请环境句柄 */
rt = dpi_alloc_env(&henv);
if (!DSQL_SUCCEEDED(rt))
{
printf("Failed to allocate environment handle\n");
return rt;
}
/* 申请连接句柄 */
rt = dpi_alloc_con(henv, &hcon);
if (!DSQL_SUCCEEDED(rt))
{
printf("Failed to allocate connection handle\n");
dpi_free_env(henv);
return rt;
}
/* 连接数据库服务器 */
rt = dpi_login(hcon, (sdbyte *)DM_SVR, (sdbyte *)DM_USER, (sdbyte *)DM_PWD);
if (!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_DBC, hcon);
dpi_free_con(hcon);
dpi_free_env(henv);
return rt;
}
printf("dpi: connect to server success!\n");
/* 构造循环查询 */
while (1)
{
dhstmt hstmt; /* 循环内申请的语句句柄 */
/* 申请语句句柄 */
rt = dpi_alloc_stmt(hcon, &hstmt);
if (!DSQL_SUCCEEDED(rt))
{
printf("Failed to allocate statement handle\n");
dpi_err_msg_print(DSQL_HANDLE_DBC, hcon);
sleep(1);
continue;
}
// 执行查询
rt = dpi_exec_direct(hstmt,
(sdbyte *)"select a.SESS_ID, a.USER_NAME, b.instance_name, a.CLNT_TYPE, "
"a.CLNT_IP, a.LAST_RECV_TIME "
"from v$sessions a, v$instance b "
"where SESS_ID = SYS_CONTEXT('USERENV', 'sessionid')");
if (!DSQL_SUCCEEDED(rt))
{
// 打印错误信息,不退出程序
printf("Query failed, continuing to next iteration...\n");
dpi_err_msg_print(DSQL_HANDLE_STMT, hstmt);
dpi_free_stmt(hstmt); // 释放语句句柄
sleep(1); // 延迟1秒,防止频繁查询
continue;
}
// 绑定列
dpi_bind_col(hstmt, 1, DSQL_C_NCHAR, sess_id, sizeof(sess_id), &sess_id_ind);
dpi_bind_col(hstmt, 2, DSQL_C_NCHAR, user_name, sizeof(user_name), &user_name_ind);
dpi_bind_col(hstmt, 3, DSQL_C_NCHAR, instance_name, sizeof(instance_name), &instance_name_ind);
dpi_bind_col(hstmt, 4, DSQL_C_NCHAR, clnt_type, sizeof(clnt_type), &clnt_type_ind);
dpi_bind_col(hstmt, 5, DSQL_C_NCHAR, clnt_ip, sizeof(clnt_ip), &clnt_ip_ind);
dpi_bind_col(hstmt, 6, DSQL_C_NCHAR, last_recv_time, sizeof(last_recv_time), &last_recv_time_ind);
printf("Querying session information...\n");
// 获取结果
while (dpi_fetch(hstmt, NULL) != DSQL_NO_DATA)
{
printf("SESS_ID: %s, USER_NAME: %s, INSTANCE_NAME: %s, CLNT_TYPE: %s, "
"CLNT_IP: %s, LAST_RECV_TIME: %s\n",
sess_id, user_name, instance_name, clnt_type, clnt_ip, last_recv_time);
}
// 释放游标以准备下次查询
dpi_close_cursor(hstmt);
// 释放语句句柄
dpi_free_stmt(hstmt);
// 延迟1秒
sleep(1);
}
//断开数据库连接
rt = dpi_logout(hcon);
if (!DSQL_SUCCEEDED(rt))
{
dpi_err_msg_print(DSQL_HANDLE_DBC, hcon);
}
printf("dpi: disconnect from server success!\n");
/* 释放连接句柄和环境句柄 */
rt = dpi_free_con(hcon);
rt = dpi_free_env(henv);
return rt;
}
gcc -I/home/dmdba/dmdbms/include -L/home/dmdba/dmdbms/bin -ldmdpi -o dpiConn dpiConn.c ./dpiConn
运行期间关闭当前连接的数据库,我这里首先连接的DMSTANDBY,所以我直接关闭了192.168.131.153虚拟机。也可以关掉数据库。
systemctl stop DmServiceDMSERVICE.service
可见运行过程中在原主库挂掉时,在短暂等待后,程序自动切换了数据库。而代码中并没有做重连的操作,这是DPI帮我们实现的。大家在开发过程中要注意申请语句句柄的时机。
文章
阅读量
获赞