简介:这是一份讲解VB6与Access数据库联动开发的PDF文档,面向初学桌面数据库编程的VB6开发者,重点解决如何在传统VB6工程中通过ADO组件高效操作Access数据库。文档开篇说明引用Microsoft ActiveX Data Objects 2.7库的步骤,清晰展示Connection对象建立连接、Recordset对象执行SQL并遍历结果,以及用Execute方法完成增删改查的完整写法;四个实例依次覆盖组合框数据绑定、用户登录校验、窗体加载自动连接和标准SQL执行,代码可直接复制改造。资源为单个PDF文件,大小仅26KB,内容短小精悍,已有98人学习。适合希望快速掌握VB6+Access操作要点的初学者,按照示例修改表名、字段和连接路径后,即可嵌入自己的数据库项目中。
1. VB6+Access 数据库操作:二十年后还在跑,方法得落到命令上
老工厂的产线系统、财务的遗留账单、医院的设备登记模块,至今仍有一批 VB6 编译的 exe 在 Windows 上运行,旁边的 Access 数据库文件从 XP 时代活到现在。所谓数据库操作方法,核心无非是连接怎么建、数据怎么增删改查、界面怎么绑定、并发写入怎么控制这几件事,但每一件都有固定参数和容易踩的坑:ADO 连接串选错 Provider,或 32 位驱动装成 64 位,程序直接报找不到提供程序;DataGrid 编辑完忘了 UpdateBatch,数据看着在界面上,一关窗体全部丢失。这篇按实际接手老项目的顺序,把 VB6+Access 的数据库操作方法完整过一遍,适合维护遗留系统或准备做数据迁移的工程师照着改。
2. ADO 连接 Access:驱动选择、连接串参数与释放规范
2.1 为什么是 ADO 而不是 DAO 或 ODBC
VB6 时代操作 Access 有三条路线:DAO 直接驱动 Jet 引擎,对表结构、索引的控制最细,但离开了 Access 场景就几乎无用;ODBC 是通用底座,要配置 DSN,适合被其他语言混合调用的老系统;ADO 是基于 OLE DB 的中间层,同时兼容 Access、SQL Server、Oracle,Recordset 对象模型统一。今天接手别人的 VB6 项目,十有八九代码里已经是 ADO,新写的维护补丁也建议用 ADO,否则两种对象模型混在一个工程里,Dim 声明、游标规则、错误号全都不一样,排查成本翻倍。
DAO 不是不能用,但它处理 .mdb 时的 Recordset 默认是表类型游标,对排序和过滤的支持和 ADO 差异很大。ODBC 则多绕一层驱动管理,出问题时没法直接看 Provider 层的错误信息。对于向其他系统导数据、做报表这类需求,ADO 一套连接串通吃,是最省心的选择。所以下面所有示例统一走 ADO 2.x 对象库。
2.2 连接串怎么写:Jet 与 ACE 的差异
连接 Access 最常出问题的地方是 Provider 选错。Jet OLEDB 4.0 只能打开 Access 2003 及以前的 .mdb 文件,Windows 自带,不需要安装任何额外组件;ACE OLEDB 12.0 由 Office 2007 以后引入,能打开 .mdb 也能打开 .accdb,但需要单独下载 AccessDatabaseEngine 安装。下表是三种写法的适用场景。
| Provider | 可打开文件 | 适用场景 |
|---|---|---|
| Microsoft.Jet.OLEDB.4.0 | .mdb | XP/Win7 老机器、无 Office 环境 |
| Microsoft.ACE.OLEDB.12.0 | .mdb、.accdb | 需要读 .accdb,或 Jet 驱动被禁用时 |
| MSDASQL | 经 ODBC DSN 访问 | 遗留系统必须走 DSN,不推荐新写 |
需要特别注意的是位数问题。VB6 编译出的 exe 是 32 位进程,即使操作系统是 64 位 Windows,也只能加载 32 位的 ACE 驱动。安装时选了默认的 64 位 AccessDatabaseEngine,程序运行到 conn.Open 就会报"未找到提供程序"。判断方法是在命令提示符里执行cscript "C:\Program Files (x86)\Common Files\Microsoft Shared\OFFICE16\ACEOLEDB.DLL"这类检查,或者直接用 32 位 ODBC 管理器C:\Windows\SysWOW64\odbcad32.exe看驱动列表里有没有 ACE 条目。
2.3 最小连接代码与连接串参数说明
Dim conn As ADODB.Connection Set conn = New ADODB.Connection ' 连接串逐项拼好,方便后续从配置文件读取 conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\erp\data2000.mdb;" & _ "Persist Security Info=False" conn.Open If conn.State = adStateOpen Then Debug.Print "连接成功" End If ' 业务处理完之后必须先 Close 再置空 conn.Close Set conn = Nothing参数说明:Provider指定 OLE DB 提供程序,决定驱动加载路径;Data Source是 mdb 文件的绝对路径,注意 VB6 的当前目录不一定等于 exe 所在目录,建议用App.Path & "\data\data2000.mdb"拼绝对路径;Persist Security Info置为 False,避免连接串里的密码残留在内存或连接缓存中。如果 mdb 设置了数据库密码,在连接串里追加Jet OLEDB:Database Password=你的密码即可。
这里有一个很多老工程师都会忽略的习惯:只调conn.Close不写Set conn = Nothing。在循环创建连接的代码里,这会导致连接对象的引用计数不被清零,Access 的 .ldb 锁文件迟迟不释放,其他客户端写数据时出现"文件正在使用"的假象。关闭顺序严格遵守 Close 再置空,是连接释放的最小规范。
3. 用 Recordset 在 VB6 里做增删改查:游标、锁与参数化
3.1 游标类型和锁类型怎么搭配
Recordset 是 ADO 操作 Access 的核心对象。打开一个 Recordset 时要同时指定游标类型和锁类型,这两个参数决定数据是快照还是实时、能不能更新、更新时锁粒度多大。选错了最常见的现象是:rs.Update 报"当前记录集不支持更新",或者遍历数据时速度奇慢。
| 游标类型 | 行为特征 | 典型场景 |
|---|---|---|
| adOpenForwardOnly | 只能向前移动,速度最快 | 只读报表、批量导出 |
| adOpenKeyset | 能看到其他会话的更新,看不到新增 | 界面增删改查的默认选择 |
| adOpenStatic | 静态快照,不感知任何变化 | 小数据量滚动浏览 |
| adOpenDynamic | 完全实时,开销最大 | Access 场景基本不用 |
锁类型建议与游标配套:只读遍历用adLockReadOnly,单条编辑用adLockOptimistic,批量提交用adLockBatchOptimistic。adLockPessimistic在 Access 文件模式下会导致记录长期被锁,多人系统里慎用。
3.2 查询遍历的标准模板
Dim rs As ADODB.Recordset Set rs = New ADODB.Recordset ' 打开一个只读、可向前遍历的结果集 rs.Open "SELECT OrderID, Amount FROM Orders WHERE CustID=1001", _ conn, adOpenForwardOnly, adLockReadOnly Do While Not rs.EOF Debug.Print rs.Fields("OrderID").Value & " - " & rs.Fields("Amount").Value rs.MoveNext Loop rs.Close Set rs = Nothing逻辑说明:EOF是判断记录集末尾的标配属性,每处理完一条记录必须MoveNext,否则死循环;Fields("OrderID").Value按列名取值,比Fields(0)可读性好,也不怕 SELECT 语句调整列顺序。查询条件里如果有变量,最稳妥的是走参数化,见 3.4。
3.3 增删改的三种路径与选择依据
' 方式一:用 Connection.Execute 执行动作 SQL conn.Execute "INSERT INTO Orders(CustID, Amount, OrderDate) " & _ "VALUES(1001, 88.5, #2025-06-01#)" ' 方式二:用 Recordset 的 AddNew / Update Dim rsEdit As ADODB.Recordset Set rsEdit = New ADODB.Recordset rsEdit.Open "SELECT * FROM Orders WHERE 1=0", conn, _ adOpenKeyset, adLockOptimistic rsEdit.AddNew rsEdit.Fields("CustID").Value = 1001 rsEdit.Fields("Amount").Value = 88.5 rsEdit.Fields("OrderDate").Value = Date rsEdit.Update rsEdit.Close Set rsEdit = Nothing方式一适合批量初始化、不需要回读数据的场景,执行快但拿不到新生成的自增编号;方式二适合界面表单提交,可以先 AddNew 再逐字段赋值,且WHERE 1=0只建空结构不拖数据。注意 Access 的日期字面量必须用#包裹,字符串用单引号;直接拼 SQL 时,字符串里的单引号要写成两个单引号转义,否则一遇到客户姓名里带'就报语法错误。
3.4 参数化查询:防注入也是防引号爆炸
网上搜"access 注入"能看到大量针对老系统的攻击脚本,原理就是拼接 SQL 时把恶意条件带进 WHERE。VB6 里最稳的防线是 Command 对象的参数化查询,把值作为参数传给 Jet 引擎,而不是拼进 SQL 字符串。
Dim cmd As ADODB.Command Set cmd = New ADODB.Command cmd.ActiveConnection = conn cmd.CommandText = "SELECT * FROM Users WHERE UserName=? AND PassWord=?" Dim p1 As ADODB.Parameter Set p1 = cmd.CreateParameter("u", adVarWChar, adParamInput, 50, txtUser.Text) cmd.Parameters.Append p1 Dim p2 As ADODB.Parameter Set p2 = cmd.CreateParameter("p", adVarWChar, adParamInput, 50, txtPass.Text) cmd.Parameters.Append p2 Dim rs As ADODB.Recordset Set rs = cmd.Execute逻辑说明:Jet 4.0 的 SQL 用?作占位符,CreateParameter的四个参数分别指定名称、类型、方向、长度,adVarWChar对应 Access 的文本类型;长度必须不小于表结构里的字段长度,否则参数值会被截断。参数化同时解决了引号转义的老大难问题——值里的单引号、百分号都会被当作普通字符处理。接手旧项目时,不要只给登录查询打补丁,凡是接受用户输入后拼 SQL 的地方,统一换成这个写法。
4. DataGrid 绑定 Access 数据:ADODC 与代码绑定的实操配置
4.1 拖控件方式:ADODC 加 DataGrid
在 VB6 开发环境里,先确认装了 VB6 SP6 的 Common Controls,菜单"工程—部件"勾选 Microsoft ADO Data Control 6.0(SP6)和 Microsoft DataGrid Control 6.0(SP6),工具箱里才会出现 ADODC 和 DataGrid 两个控件。这是老项目里出现频率最高的搭配,但很多新手把连接串填进 ADODC 后,界面却一片空白。
| 控件 | 属性 | 配置值 |
|---|---|---|
| Adodc1 | ConnectionString | OLEDB 连接串,可点"生成"向导 |
| Adodc1 | RecordSource | SELECT 语句,CommandType 选 adCmdText |
| Adodc1 | Refresh | 配置后必须调用一次 |
| DataGrid1 | DataSource | 选 Adodc1 |
关键的坑是顺序:先配置并测试 ADODC,再把 DataGrid 的 DataSource 指向 ADODC,否则 DataGrid 会缓存一个空数据源。ADODC 配置完成后,运行前记得把 DataGrid 的AllowUpdate设为 True,否则界面能看不能改。这种方式适合快速原型,缺点是连接串和 SQL 都写在控件属性里,发布后不好维护。
4.2 代码绑定:运行时赋值与刷新时机
正式项目我更建议代码绑定,连接串统一从 INI 或注册表读取,SQL 写在业务模块里。核心逻辑是先打开 Recordset,再赋给 DataGrid 的 DataSource。
Dim rs As ADODB.Recordset Set rs = New ADODB.Recordset ' 客户端游标是 DataGrid 正常显示的前提 rs.CursorLocation = adUseClient rs.Open "SELECT PartID, PartName, Qty FROM Parts ORDER BY PartID", _ conn, adOpenStatic, adLockBatchOptimistic Set DataGrid1.DataSource = rs参数说明:CursorLocation = adUseClient很关键,DataGrid 依赖客户端游标做排序和分页显示;adOpenStatic提供快照,适合纯展示;adLockBatchOptimistic为后面的批量提交做准备。绑定之后不要再对 rs 执行 Close,否则 DataGrid 立即失去数据源。刷新数据时不要重新绑定,直接调rs.Requery再DataGrid1.Refresh。
4.3 绑定状态下增删改:UpdateBatch 才是真保存
DataGrid 里直接编辑单元格,改动只发生在客户端游标内存中,并没有写回 Access。这是"VB6+Access 数据库操作"里最常被误会的点:看起来改了、也停在界面上了,关窗体重开数据没变。原因就是没有调用批量提交。
' 在保存按钮点击事件里统一提交 Adodc1.Recordset.UpdateBatch adAffectAll Adodc1.RefreshUpdateBatch的参数有三个常用值:adAffectCurrent只提交当前行,adAffectGroup提交满足当前 Filter 的行,adAffectAll提交全部。提交失败时,游标里会产生字段级错误,轮询rs.Fields(i).Status可以定位到具体哪一行哪一列冲突。如果程序里同时用了 DataGrid 编辑和 SQL 更新两条路径,切记不要混用——界面改了没 UpdateBatch,这边 Execute 又按旧值去更新,数据就被覆盖了。
5. Access 并发场景的事务、锁与批量写入边界
5.1 显式事务:三行代码保证多表操作一致性
Access 文件的并发能力远弱于 SQL Server,但事务机制仍然可靠。凡是涉及多表联动写入的操作——比如出库时同时扣库存和写流水——必须包在显式事务里,否则中间任何一条失败,库存下去了流水没记录,对账永远对不平。
conn.BeginTrans On Error GoTo ErrHandler conn.Execute "UPDATE Stock SET Qty = Qty - 1 WHERE PartID='A001'" conn.Execute "INSERT INTO OutLog(PartID, Qty, OutDate) VALUES('A001', 1, Date())" conn.CommitTrans Exit Sub ErrHandler: conn.RollbackTrans MsgBox "操作失败,已回滚:" & Err.Description逻辑说明:BeginTrans之后的所有写操作先进入事务日志,CommitTrans才真正落库;任一步出错跳转后由RollbackTrans撤销全部改动。注意事务开始后,那条连接上不能再执行会影响游标的操作,比如同时打开多个可更新 Recordset 改数据;事务期间连接占用的锁要到提交或回滚才释放,所以事务体越小越好。
5.2 锁等待与命令超时的参数边界
Access 通过 .ldb 文件协调记录锁,两个会话同时改同一条记录时,后到的一方会按延迟时间重试。老系统里常见的"操作被锁定"报错,多半是这几个参数没调过。
| 参数 | 默认值 | 作用 |
|---|---|---|
| ConnectionTimeout | 15 秒 | 建立连接的最长等待时间 |
| CommandTimeout | 30 秒 | 单条命令的执行超时 |
| Jet OLEDB:Lock Delay | 100 毫秒 | 两次抢锁尝试的间隔 |
| Jet OLEDB:Lock Retry | 5 次 | 抢锁的总重试次数 |
在代码里修改的写法是:
conn.ConnectionTimeout = 30 conn.CommandTimeout = 60 conn.Properties("Jet OLEDB:Lock Retry") = 20 conn.Properties("Jet OLEDB:Lock Delay") = 50参数说明:Lock Retry调大适合锁冲突频繁但单次持有时间很短的场景;Lock Delay调小会加重竞争,调大则用户等待感明显。经验值是从默认的 100 毫秒降到 50 毫秒、重试从 5 次加到 20 次,配合查询用只读游标、进入编辑才开可更新游标的方式,能把锁持有时间压到最低。Access 不适合超过 10 到 20 个并发写客户端,这是文件型数据库的物理边界,靠调参数只能缓解,不能根治。
5.3 批量写入:循环 Execute 与 UpdateBatch 的取舍
向 Access 写入上千行数据时,逐条conn.Execute是最慢的写法,因为每条 SQL 都要经过字符串拼接、解析、执行三步。更快的做法是客户端游标批量模式:所有 AddNew 都发生在内存里,最后一次性提交。
Dim rs As ADODB.Recordset Set rs = New ADODB.Recordset rs.CursorLocation = adUseClient rs.Open "SELECT * FROM Journal", conn, adOpenStatic, adLockBatchOptimistic For i = 1 To 1000 rs.AddNew rs.Fields("EntryID").Value = i rs.Fields("Amount").Value = i * 10 rs.Update ' 此时只更新客户端内存 Next i rs.UpdateBatch adAffectAll说明:在adLockBatchOptimistic模式下,每次rs.Update只是把改动标记在客户端缓存里,UpdateBatch才向 Access 发起真正的写操作。实测 1000 行的写入,批量模式比逐条 Execute 快一个数量级,原因就是省掉了 1000 次 SQL 解析。批量提交的代价是失败定位困难,所以提交前要做好数据校验,提交后要检查rs.Filter = adFilterAffectedRecords是否有失败记录。
5.4 写前先看索引与库文件状态
批量写入之前,先确认 WHERE 条件和 ORDER BY 涉及的列上有索引,Access 没有索引的筛选就是全表扫描,数据量过万后速度断崖式下跌。另外 Access 删除记录不会自动回收空间,频繁增删后文件碎片化严重,查询变慢甚至报"数据库无法打开"。常规做法是定期在无人使用时段用菜单里的"压缩和修复数据库"处理一次,压缩前必须确保所有连接都已关闭。这条对老系统尤其重要,很多"越用越慢"的问题根源不在代码,而在 mdb 文件本身。
6. 一个收尾技巧:VB6 打印预览 Access 数据时避开空白页
6.1 DataReport 的最小绑定流程
VB6 打印预览 Access 数据,最常见的就是 DataReport 加 DataEnvironment。先在工程里添加 DataEnvironment 设计器,建一个 Connection 指向第 2 章那条连接串;再建一个 Command,SQL 写 SELECT 查询;然后添加 DataReport,把 DataSource 设为该 DataEnvironment,DataMember 选 Command 名;最后在设计器里把字段从 Command 树拖到细节区。按钮事件里写两行:
If conn.State <> adStateOpen Then conn.Open DataReport1.Show vbModal逻辑说明:DataReport 的数据源在 Show 时才取数,所以调用前必须保证连接是打开状态;vbModal让预览窗体独占,避免用户切回主窗体又触发一次报表取数。
6.2 空白页和乱码的三个高频原因
第一个原因是纸张尺寸。DataReport 设计器默认是 Letter 纸,国内要手动改成 A4,否则右侧留白会被当成第二个页面,打印预览每页后面都跟一张空白页。第二个原因是细节区高度设置过大,报表每个 Section 的高度总和超过页面可打印区域,必然强制分页。第三个是中文字体,VB6 报表控件默认的西文字体在中文系统上显示成方块,把相关控件的 Font.Charset 设为 134(GB2312)后重新预览即可解决。
排查这类问题有个效率技巧:在 Show 之前用代码核对字段绑定,DataReport1.Sections(1).Controls("Text1").DataField = "PartName",比反复开预览窗口翻页找错快得多。字段名对不上时,控件显示的是空值而不是报错,这是报表类问题里最隐蔽的一种。
本文还有配套的精品资源,点击获取