ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++用ODBC连接MySQL 8.0完整指南:环境搭建与代码实战

C++用ODBC连接MySQL 8.0完整指南:环境搭建与代码实战 简介一份面向C开发者的MySQL ODBC连接示例工程适合需要掌握ODBC标准接口或在Visual Studio中集成数据库操作的初学者。资源为一个Visual Studio项目压缩包共26个文件包含8个头文件.h、7个C源文件.cpp以及.dsp、.dsw、.rc等工程与资源文件整体体积仅38KB。已有1017人学习浏览。项目涉及ODBC环境初始化、连接数据库、执行SQL语句、遍历结果集及断开连接等核心流程并通过ODBCTestDlg.cpp、AddRecord.cpp、EditRecord.cpp等源码展示对话框界面下添加、编辑记录的具体实现MyListCtrl.cpp用于自定义列表展示查询结果。细致阅读这些代码可掌握SQLAllocHandle、SQLConnect、SQLExecDirect、SQLFetch等API的配合方式并体会MFC工程中管理ODBC连接的常见写法。 最近有朋友在群里问C 项目要连 MySQL是直接用官方 C API 还是走 ODBC。这事我当年接手遗留系统时也纠结过后来在 Windows 上用 ODBC 连 MySQL 8.0 跑了好几年期间踩过 [IM002] 未发现数据源名称、认证插件不兼容、中文乱码这些经典坑。今天把这条完整的链路整理出来新手可以直接照着做老手也可以看看有没有被你忽略的细节。这套方案解决的痛点很明确你的 C 程序需要通过统一的数据库接口访问 MySQL而不是绑定某一种数据库客户端库。ODBC 作为中间层让同一套代码在 MySQL、SQL Server、PostgreSQL 之间切换成为可能。如果你正在做跨平台、可移植性要求高的数据层开发或者公司软件要同时兼容多种数据库这篇文章就是给你准备的。1. 为什么用 ODBC 而不是直接调 MySQL API1.1 几套主流方案的对比在 C 里连 MySQL绕不开几种选择官方提供的 C APIlibmysqlclient、MySQL Connector/C、还有通用性最强的 ODBC。C API 的特点是性能好、直接但接口偏底层而且一旦换了数据库所有代码都得推倒重来。Connector/C 优点是有面向对象的接口可以配合标准库使用但它的版本和 MySQL 服务器版本存在兼容性要求部署时还得多带一个动态库。ODBCOpen Database Connectivity出现得早Windows 下几乎是系统级标配Linux 下也有 unixODBC 作为对应实现。它的核心思路是把“数据库差异”挡在驱动层后面你的代码只跟 ODBC 驱动管理器打交道具体连的是 MySQL 还是别的数据库由驱动决定。实际开发中最直观的好处就是今天连 MySQL明天客户要换 SQL Server你只需要改连接配置SQL 语句兼容的情况下代码几乎不用动。1.2 ODBC 的工作流程ODBC 的调用链路分四层应用程序、驱动管理器、ODBC 驱动、数据源。应用程序调用 ODBC API驱动管理器根据数据源名DSN找到对应驱动并加载驱动再通过自己的协议跟 MySQL 服务器通信。这就是为什么 ODBC 程序第一步往往是分配环境句柄和连接句柄而不是直接打开网络连接。这套机制带来的额外好处是连接池、超时控制、游标设置都能在 ODBC 层统一处理不需要每个业务模块重复实现。代价就是多一层跳转性能上会有微小损耗但在绝大多数业务系统里多出来的这几毫秒完全感知不到换来的是可维护性和移植性的巨大提升。我自己做数据层封装时优先选 ODBC 就是因为这个原因。1.3 什么场景适合用 ODBC不是所有项目都适合 ODBC。如果你的程序只服务 MySQL、性能要求极高、已经在用 MySQL 特有的协议特性直接用官方 C API 或 Connector/C 更省事。但如果你的软件是面向企业客户的通用产品客户数据库可能是 MySQL 也可能是 SQL Server甚至可能是 PostgreSQL那 ODBC 就是最稳的选择。还有一个典型场景是混合架构前端 C 写的客户端后端数据服务用 MySQL中间还要做数据同步。这种架构下ODBC 提供的标准化接口能显著降低不同模块之间的耦合度。我后来做的一个中间件就是这么设计的数据源切换时只改配置文件代码零改动运维省了大量心。2. 环境搭建从零到能连上数据库2.1 MySQL 8.0 安装时的两个关键选项网上 MySQL 8.0 的安装教程很多我只提醒两个直接影响 ODBC 连接的选项。第一个是认证方式MySQL 8.0 默认使用 caching_sha2_password而比较老的 ODBC 驱动只支持 mysql_native_password。如果你手头的驱动版本偏旧装完驱动后很可能出现连接成功但是认证失败的情况。解决办法有两个升级 ODBC 驱动到支持 caching_sha2_password 的版本或者在 MySQL 里把用户改回旧认证方式。第二个是端口和防火墙。默认端口 3306如果 MySQL 装在远程服务器上Windows 防火墙和云安全组都要放行这个端口。很多人本地连没问题换到服务器就连不上九成是安全组没开端口。MySQL 安装时还会要求设置 root 密码建议用强密码并单独创建业务账号别用 root 连业务库这是基本的安全习惯。2.2 安装 ODBC 驱动版本和位数都不能错MySQL 官方提供的驱动叫 MySQL Connector/ODBC也叫 MyODBC。下载时要重点看两个信息驱动版本和位数。版本上推荐 8.0 系列因为它同时支持 caching_sha2_password 和 mysql_native_password向下兼容老数据库8.0 以下的老版本在 MySQL 8.0 面前容易出认证问题。位数这个坑巨大。ODBC 驱动管理器和你的应用程序位数必须一致程序编译成 64 位就必须配置 64 位的 DSN并确保加载的是 64 位驱动程序是 32 位就反过来。Windows 64 位系统上ODBC 数据源管理器其实有两个版本控制面板里看到的通常是 64 位的32 位的数据源管理器一般在C:\Windows\SysWOW64\odbcad32.exe。程序连不上数据源时先别怀疑代码第一时间检查位数是否匹配。2.3 配置 DSN 数据源DSN 的作用是把连接信息集中管理让你的 C 代码不用手写冗长的连接字符串。Windows 下打开 ODBC 数据源管理器选择“系统 DSN”或“用户 DSN”点击“添加”选中 MySQL ODBC 8.0 Unicode Driver然后填写数据源名称、服务器地址、端口、用户名、密码和数据库名。这里我建议勾选“测试连接”确认驱动和认证都没问题再开始写代码。Unicode 驱动和 ANSI 驱动的选择也有讲究程序内部用的是宽字符wchar_t选 Unicode 驱动用的是 char选 ANSI 驱动。混用会导致字符转换出错这是一个很容易被忽略的细节。配置完成后在命令行里用小工具测试 DSN 能通就可以进入写代码环节了。3. C 代码实操连接、查询与写入3.1 初始化与建立连接先看一个完整的连接示例包含必要的头文件和初始化流程。ODBC 的 API 都以SQL开头核心流程是分配环境句柄、设置 ODBC 版本、分配连接句柄、建立连接、最后记得释放。#include windows.h #include sql.h #include sqlext.h #include cstdio int main() { SQLHENV env SQL_NULL_HENV; SQLHDBC dbc SQL_NULL_HDBC; SQLRETURN ret; // 1. 分配环境句柄 ret SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, env); // 2. 设置 ODBC 3.8 版本 SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3_8, 0); // 3. 分配连接句柄 ret SQLAllocHandle(SQL_HANDLE_DBC, env, dbc); // 4. 使用 DSN 连接 ret SQLDriverConnect(dbc, NULL, (SQLWCHAR*)LDSNMyMySQLDSN;UIDroot;PWD123456;, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT); if (SQL_SUCCESS ret || SQL_SUCCESS_WITH_INFO ret) { printf(connect ok\n); } else { printf(connect failed\n); } // 释放顺序与分配顺序相反 if (dbc ! SQL_NULL_HDBC) SQLDisconnect(dbc); if (dbc ! SQL_NULL_HDBC) SQLFreeHandle(SQL_HANDLE_DBC, dbc); if (env ! SQL_NULL_HENV) SQLFreeHandle(SQL_HANDLE_ENV, env); return 0; }SQLDriverConnect里的连接字符串可以指定 DSN也可以走 DSN-less 方式直接写驱动名和服务器信息例如DRIVER{MySQL ODBC 8.0 Unicode Driver};SERVER127.0.0.1;PORT3306;UIDroot;PWD123456;DATABASEtest。DSN-less 的好处是程序分发时不用每台机器都配数据源直接读配置文件即可我推荐这种方式。连接字符串配置项很多常用到的有OPTION4194304禁用 ODBC 缓存读实时数据、CHARSETutf8mb4解决中文乱码关键。3.2 执行 SQL 并读取结果集建立连接后用 SQL 语句做增删改查。查询的核心步骤是分配语句句柄、执行 SQL、绑定结果集列、循环取行。看代码SQLHSTMT stmt SQL_NULL_HSTMT; SQLAllocHandle(SQL_HANDLE_STMT, dbc, stmt); SQLWCHAR query[] LSELECT id, name, age FROM users WHERE age 20; ret SQLExecDirect(stmt, query, SQL_NTS); if (SQL_SUCCESS ret || SQL_SUCCESS_WITH_INFO ret) { int id; SQLWCHAR name[64]; int age; // 绑定列到变量 SQLBindCol(stmt, 1, SQL_C_LONG, id, 0, NULL); SQLBindCol(stmt, 2, SQL_C_WCHAR, name, sizeof(name), NULL); SQLBindCol(stmt, 3, SQL_C_LONG, age, 0, NULL); // 循环取行 while (SQLFetch(stmt) SQL_SUCCESS) { printf(id%d name%ls age%d\n, id, name, age); } } SQLFreeHandle(SQL_HANDLE_STMT, stmt);SQLBindCol负责把列位置、C 语言类型、目标变量地址关联起来。类型对应关系要特别注意MySQL 的 int 对应SQL_C_LONGvarchar 对应SQL_C_CHAR或SQL_C_WCHARbigint 对应SQL_C_SBIGINTdatetime 对应SQL_C_TIMESTAMP。类型绑定错了返回的就是 NULL 或者乱码而且这种问题很难通过报错发现只能逐个字段排查。取值还有一个SQLGetData方式适合处理变长字段或不知道列数的情况。SQLGetData不绑定列而是每次取一列指定长度的数据类似 C 语言里动态读文件适合读取 TEXT、BLOB 这种大字段。绑定方式适合列固定、行数多的场景性能更好。项目里我通常混用固定字段用 BindCol大字段用 GetData。3.3 参数化写入与事务控制执行非查询 SQL 时核心推荐用参数绑定而不是拼接字符串。参数化一方面能彻底避免 SQL 注入风险另一方面也省去了字符串转义和拼接的麻烦。示例SQLWCHAR insertSQL[] LINSERT INTO users(name, age) VALUES(?, ?); SQLAllocHandle(SQL_HANDLE_STMT, dbc, stmt); SQLWCHAR name[] L张三; int age 30; SQLLEN ageLen 0; // 绑定参数 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WVARCHAR, 50, 0, name, sizeof(name), NULL); SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, age, 0, ageLen); ret SQLExecute(stmt);SQLBindParameter的参数比较多第 4、5 个参数是 C 语言类型和 SQL 类型第 6 个是列宽第 7 个是小数位数最后一个是长度指针。字符串类型必须传正确长度否则可能产生截断错误。字符类型要严格区分SQL_C_WCHAR和SQL_C_CHAR对应宽字符和窄字符。事务控制也很重要ODBC 默认是自动提交模式每条 SQL 单独提交。如果你希望多条 SQL 要么全成功要么全失败需要关闭自动提交SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_NTS); // 执行多条 SQL... // 成功后提交 SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT); // 失败后回滚 // SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_ROLLBACK);事务用完后记得把自动提交改回来否则连接释放时未提交的事务会一直挂着后续复用连接时可能出现意想不到的脏数据。如果是调用 MySQL 存储过程同样是SQLExecDirect执行CALL procedure(?)参数和普通语句一样用SQLBindParameter绑定这样可以拿到存储过程的输出参数。3.4 完整封装示例与编译配置裸写 ODBC API 啰嗦是出了名的实际项目里不封装没法用。一个比较实用的封装思路是把所有连接细节放进一个Database类提供connect、executeQuery、executeUpdate、callProcedure方法语句执行放进Statement类内部自动管理句柄的分配和释放。封装时最需要注意的坑是句柄泄漏ODBC 不像 C 标准库那样自动回收每次SQLBindCol、SQLBindParameter分配的资源必须在语句句柄释放前处理干净。编译配置这块Windows Visual Studio 最简单项目属性里C/C 常规附加包含目录加上 Windows SDK 默认路径即可链接器输入附加依赖项加上odbc32.lib。我习惯在源码里直接写#pragma comment(lib, odbc32.lib)这样可以少改一个配置文件。头文件方面sql.h和sqlext.h是 Windows 系统自带的不需要额外下载。Linux 下要装 unixODBC 开发包sudo apt install unixodbc unixodbc-dev g main.cpp -o main -lodbc代码里的SQLWCHAR在 Linux 下可能没那么好使建议用char和SQL_C_CHAR作为跨平台默认类型或者统一走自己的字符封装层。我自己在跨平台项目里都是定义一个DB_CHAR别名Windows 用宽字符Linux 用窄字符省去大量条件编译。4. 高频问题实录与排查思路4.1 [IM002] 未发现数据源名称这个错误几乎是所有 ODBC 新手的第一课。错误信息是[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动。出现这个错误先检查三件事DSN 名称是否拼写正确DSN 位数是否和程序位数一致DSN 是系统级还是用户级如果程序以管理员权限运行用户 DSN 可能读不到。如果你走的是 DSN-less 连接那DRIVER名称必须和 ODBC 数据源管理器里显示的驱动名完全一致包括括号和空格。比如DRIVER{MySQL ODBC 8.0 Unicode Driver}少一个空格都会导致驱动管理器找不到。遇到这个错误先打开odbcad32.exe确认驱动存在再检查连接字符串基本都能解决。4.2 MySQL 8 认证插件导致的 Access denied错误信息一般是[HY000] [MySQL][ODBC] Access denied for user xxxlocalhost。明明是本地连接用户名密码也没错为什么被拒多半是认证方式问题。MySQL 8.0 默认使用 caching_sha2_password而旧版本 ODBC 驱动或默认配置不兼容。最直接的排查方法是登录 MySQL 执行SELECT user, host, plugin FROM mysql.user;如果业务用户的 plugin 是 caching_sha2_password而你的 ODBC 驱动是 5.x 老版本就会认证失败。解决办法要么把 ODBC 驱动升到 8.0.x要么把用户改成旧插件ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;从安全角度建议升级驱动而不是降级认证方式。不过有些老系统确实暂时动不了那也只能改认证方式过渡。4.3 中文乱码与字符集处理中文乱码的坑分为两种读出来乱码和写进去乱码。读出来乱码多半是结果集绑定成了SQL_C_CHAR而数据库返回的是 UTF-8 字节流两边的编码对不上。写进去乱码一般是连接字符串里没指定CHARSET或者表的字符集不是utf8mb4。我的标准配置是数据库和表都用utf8mb4连接字符串加上CHARSETutf8mb4代码里统一用宽字符绑定。这样一套下来基本不会遇到乱码问题。需要注意utf8mb4和utf8的区别MySQL 的utf8实际最多存 3 字节遇到 emoji 这种 4 字节字符会报错或者静默截断而utf8mb4才是完整实现。4.4 64 位与 32 位混用的连环坑这个问题隐蔽性很强。现象是 DSN 测试连接成功程序运行却报找不到驱动或数据源。原理也很简单ODBC 驱动管理器、驱动、应用程序三者必须同位数。64 位程序加载 64 位驱动管理器和 64 位驱动32 位程序加载 SysWOW64 里的 32 位版本。很多人只在程序里改了平台忘了 DSN 也要重新建一份对应位数的自然就连不上。排查方法确认程序平台后用对应位数的odbcad32.exe查看是否有对应 DSN。在 Visual Studio 里切换平台时务必检查是否已经在该位数的 DSN 列表里建过数据源。这个问题在开发和测试机器上经常交替出现建议把 DSN 建系统级并保持开发机、测试机配置一致。最后分享一点个人体会ODBC 的 API 确实繁琐函数名难记参数还多但好处是你只需要认真折腾一次。一旦把连接、查询、参数绑定、事务、日志这些基础能力封装成类后续加任何功能都只是在这个框架上填代码。我手头维护的旧项目最开始用的是 MySQL 专用接口后来客户要接另一种数据库底层整整重写了一个月换成 ODBC 之后加新数据库只是加驱动和改配置的事。如果你的项目有长期维护和扩展的打算建议早点把这一层抽象做好省得后面积累太多技术债。本文还有配套的精品资源点击获取
返回列表