PBIDEA:用 uo_database 与 uo_database_pool 做多库连接和连接池复用
PBIDEA:用 uo_database 与 uo_database_pool 做多库连接和连接池复用经典 PowerBuilder 里我们习惯只用全局 SQLCA 一个事务对象,连一个库、跑一段嵌入式 SQL 就完事。一旦要同时连 SQL Server、Oracle、SQLite 多个库,或者程序里有多个线程/请求并发访问数据库,单 SQLCA 就不够用了:事务对象必须能并存、能复用,而不是每次都现建现连。
PBIDEA 在 sql.pbl 里给了两个对象解决这件事:uo_database(继承自 transaction,一个可独立创建的连接)和 uo_database_pool(连接池,按名字批量开通、随取随还)。这篇只讲清楚一件事:怎样用这两个对象把"多库连接"和"事务对象复用"做对。
一、单个 uo_database:比 SQLCA 多一个"可创建"的自由
uo_database 继承自 transaction,所以 DBMS、DBParm、ServerName、LogId、LogPass、Database 这些属性和经典事务对象一模一样;区别是它是对象,可以 create 任意多个,互不干扰。构造时它会自动调用底层 sqlCreate(),你不用管。
前置:本例用 SQLite(免安装、指定库文件路径即可跑)。其它库把 DBMS 改成 'MSS'/'Oracle'/'MySQL' 等,并把 DBParm 换成对应连接串(如 ServerName=...;LogId=...;LogPass=...;Database=...)。
步骤:1) 在按钮 clicked 事件里贴入代码;2) 确保已存在库文件 D:\demo.db 与表 t_user(name);3) 运行后看 MessageBox 弹出的行数。
// 示例输入:数据库类型(SQLite 免安装,便于演示)
string ls_dbms
ls_dbms = 'SQLite'
// 示例输入:连接串(SQLite 指定库文件路径;其它库换成 ServerName/LogId/LogPass/Database 等)
string ls_dbparm
ls_dbparm = 'Database=D:\demo.db'
// 1) 创建并连接(uo_database 继承 transaction,构造时自动 sqlCreate)
uo_database db
db = create uo_database
db.DBMS = ls_dbms
db.DBParm = ls_dbparm
if not db.Open() then
MessageBox('连接失败', 'Open 返回 false,请检查 DBMS / DBParm')
return
end if
// 2) 执行查询,直接拿回 uo_recordset
uo_recordset rs
rs = db.ExecuteSQL('SELECT name FROM t_user ORDER BY id')
// 3) 逐行读取:FetchNext 取下一行,取不到返回 false
string ls_name
long ll_count
ll_count = 0
do while rs.FetchNext()
ll_count++
ls_name = rs.GetString('name')
// 真实业务在这里处理 ls_name;本例仅做计数演示
loop
// 4) 提交并释放资源(顺序:先关记录集,再关连接)
db.CommitTrans()
rs.Close()
destroy rs
db.Close()
destroy db
MessageBox('查询结果', '共读取 ' + string(ll_count) + ' 行')
要点:ExecuteSQL(sql) 直接返回 uo_recordset,不用再 DECLARE cursor;FetchNext() 推进游标、GetString('列名') 按列名取值。读完后记得 rs.Close() + destroy rs,再 db.Close() + destroy db,资源才真正释放。
二、多库并存:一个对象连一个库
要做"同时连两个库",就各 create 一个 uo_database,分别设不同的 DBMS/DBParm,各自 Open(),谁也不挡谁。下面演示一个连 SQL Server、一个连 SQLite 的最小骨架(只列连接部分,查询写法同一节):
// 示例输入:SQL Server 连接信息
string ls_mss_dbparm
ls_mss_dbparm = 'ServerName=192.168.1.10;LogId=sa;LogPass=Pass@123;Database=erp'
// 示例输入:SQLite 连接信息
string ls_sqlite_dbparm
ls_sqlite_dbparm = 'Database=D:\local.db'
// 两个独立连接,互不影响
uo_database db_mss
db_mss = create uo_database
db_mss.DBMS = 'MSS'
db_mss.DBParm = ls_mss_dbparm
db_mss.Open()
uo_database db_lite
db_lite = create uo_database
db_lite.DBMS = 'SQLite'
db_lite.DBParm = ls_sqlite_dbparm
db_lite.Open()
// 各自跑各自的查询,事务互不串
uo_recordset rs_mss
rs_mss = db_mss.ExecuteSQL('SELECT count(*) AS c FROM t_order')
long ll_orders
ll_orders = 0
if rs_mss.FetchNext() then
ll_orders = long(rs_mss.GetNumber('c'))
end if
db_mss.CommitTrans()
rs_mss.Close()
destroy rs_mss
db_mss.Close()
destroy db_mss
db_lite.Close()
destroy db_lite
MessageBox('多库统计', 'SQL Server 订单数:' + string(ll_orders))
注意 GetNumber('c') 返回 double,用 long(...) 包一层转成整数再赋给 long 变量,避免类型不匹配的编译告警。
三、连接池:用 uo_database_pool 复用事务对象
每次都 create+Open 连数据库,高并发下开销很大。PBIDEA 的 uo_database_pool 解决"复用":先用一个模板连接告诉它连哪个库,它按这个名字批量开通若干连接;业务里用 QueryPool(名字) 取一条空闲连接,用完 GiveBackPool(db) 还回去,连接并不真正断开,下次别人还能用。
前置:uo_database_pool 构造时自动 InitPool();CreatePool(名字, 模板db) 会按模板 db 的 DBMS/DBParm 批量开通预设连接。池里的连接若 10 分钟无活动会被自动断开,取用时用 IsOpen()/IsAlive() 探活即可。
步骤:1) 建模板连接 db_tpl 并 Open();2) create 连接池并 CreatePool;3) 业务里 QueryPool 取连接 → 干活 → GiveBackPool 归还;4) 程序退出前 DestroyPool() 释放全部底层连接。
// 示例输入:连接池名称(一组连接共用,例如 sql1 / ora1)
string ls_pool
ls_pool = 'demo_pool'
// 示例输入:模板连接的库类型与连接串(连接信息从它身上取)
string ls_dbms
ls_dbms = 'SQLite'
string ls_dbparm
ls_dbparm = 'Database=D:\demo.db'
// 1) 模板连接(只用来提供连接信息,并作为首批开通依据)
uo_database db_tpl
db_tpl = create uo_database
db_tpl.DBMS = ls_dbms
db_tpl.DBParm = ls_dbparm
if not db_tpl.Open() then
MessageBox('模板连接失败', 'Open 返回 false')
return
end if
// 2) 创建连接池,按模板批量开通预设连接
uo_database_pool pool
pool = create uo_database_pool
pool.CreatePool(ls_pool, db_tpl)
// 3) 业务侧从池中申请一条空闲连接
uo_database db
db = pool.QueryPool(ls_pool)
if not db.IsOpen() then
MessageBox('取连接失败', 'QueryPool 返回的连接未打开')
return
end if
// 4) 用这条连接统计行数(事务对象被复用,不再现建现连)
uo_recordset rs
rs = db.ExecuteSQL('SELECT count(*) AS c FROM t_user')
long ll_total
ll_total = 0
if rs.FetchNext() then
ll_total = long(rs.GetNumber('c'))
end if
db.CommitTrans()
// 5) 归还连接(destroy 也会自动归还;这里显式还,方便后续请求复用)
pool.GiveBackPool(db)
// 6) 程序退出前销毁整个池,释放全部底层连接
pool.DestroyPool()
MessageBox('连接池演示', '从池取连接统计得 ' + string(ll_total) + ' 行,已归还并销毁池')
生命周期要记牢:取连接用 QueryPool,用完用 GiveBackPool 还;不要对从池里取出来的 db 调 destroy(销毁池 DestroyPool() 会统一释放全部底层连接)。模板连接 db_tpl 在 CreatePool 之后也可以 db_tpl.Close() + destroy 关掉,不影响池里已开通的连接。
小结
[*]uo_database = 一个可独立 create 的事务对象,Open() 连、ExecuteSQL() 取 uo_recordset、FetchNext()+GetString/GetNumber 取值、CommitTrans()/Close() 收尾。
[*]多库并存 = 每种库各 create 一个 uo_database,属性互不干扰。
[*]连接池复用 = uo_database_pool 的 CreatePool → QueryPool 取 → GiveBackPool 还 → DestroyPool 释放;事务对象不再现建现连,高并发下省开销。
[*]资源释放顺序固定:先 rs.Close()+destroy rs,再 db.Close()+destroy db(池连接用 GiveBackPool 代替 destroy)。
连接MSSQL Server 2000连接不上,这个要怎么配置啊 MSSQL Server 2000 连不上,多半是 DBMS 写法不对,或者连接属性没配对。PBIDEA 的 uo_database 里 SQL Server 2000 的 DBMS 固定写 'MSS'(不是 'MSSQL'),并要配合 ServerName / Database / LogId / LogPass 一起设置。
前置:按你的环境改下面代码里的服务器 IP、库名、账号密码;SQL Server 2000 服务已启动,且 TCP/IP 1433 端口在业务机上可访问(可在命令行用 telnet 服务器IP 1433 验证)。
步骤:1) 在按钮 clicked 事件贴入下方代码;2) 修改示例输入值;3) 运行后看 MessageBox 结果。
// 示例输入:数据库类型(SQL Server 2000 用 'MSS',不是 'MSSQL')
string ls_dbms
ls_dbms = 'MSS'
// 示例输入:服务器 IP 或机器名(命名实例可写 机器名\实例名)
string ls_server
ls_server = '192.168.1.100'
// 示例输入:数据库名
string ls_db
ls_db = 'mydb'
// 示例输入:登录账号与密码
string ls_uid, ls_pwd
ls_uid = 'sa'
ls_pwd = '123456'
uo_database db
db = create uo_database
db.DBMS = ls_dbms
db.ServerName = ls_server
db.Database = ls_db
db.LogId = ls_uid
db.LogPass = ls_pwd
db.DBParm = 'UseAPI=OLEDB;DBPROP_INIT_TIMEOUT=1'
if not db.Open() then
MessageBox('连接失败', '错误码 ' + string(db.SQLDBCode) + ',' + db.SQLErrText)
return
end if
MessageBox('连接成功', '已连上 ' + ls_server + ' 的 ' + ls_db)
连不上先看 SQLDBCode/SQLErrText 的具体报错,常见三类:1433 端口不通或 SQL Server 2000 未开 TCP/IP 协议、sa 账号被禁用或密码错、OLEDB 驱动不兼容。若后续要绑 Blob 字段,DBParm 需追加 UseStreamForLongOrLobParameters="0" 与 ICommandPrepare="SetParameterInfo"(uo_database 源码注释里的 MSS 专用参数)。
pbai 错误码 22,08001 客户端无法建立连接SQL Server Native Client 11.0 不支持连接到 SQL Server 2000 或更早的版本。 这个报错不是连接参数写错,而是驱动本身的问题:SQLNCLI11 是随 SQL Server 2012 发布的原生客户端,微软明确不再支持连接到 SQL Server 2000 或更早版本。系统里恰好装了 SQLNCLI11 时,MSS 接口走 OLE DB 就会触发 08001。
推荐方案:改用 Windows 自带的「SQL Server」ODBC 驱动(SQLSRV32),它从 Windows 2000 起一直支持连接 SQL Server 2000,无需另装任何驱动。
本示例适用于 PB 12.5 + SQL Server 2000,通过 ODBC 接口连接。
步骤:1) 打开 32 位 ODBC 管理器(PB 是 32 位程序,须用 C:\Windows\SysWOW64\odbcad32.exe,不要用 64 位那个);2) 新建系统 DSN,选「SQL Server」驱动,填服务器 IP、库名、账号密码,客户端配置确认 TCP/IP 1433 端口;3) 在按钮 clicked 事件贴入下方代码,修改示例输入值后运行。
// 示例输入:ODBC 系统 DSN 名称(上一步新建的)
string ls_dsn = 'mss2000'
// 示例输入:登录账号与密码
string ls_uid = 'sa'
string ls_pwd = '123456'
transaction ltr
ltr = create transaction
ltr.DBMS = 'ODBC'
ltr.DBParm = "ConnectString='DSN=" + ls_dsn + ";UID=" + ls_uid + ";PWD=" + ls_pwd + "'"
if not ltr.Connect() then
MessageBox('连接失败', '错误码 ' + string(ltr.SQLDBCode) + ',' + ltr.SQLErrText)
return
end if
MessageBox('连接成功', '已通过 ODBC 连上 SQL Server 2000')
ltr.Disconnect()
destroy ltr
若仍想保留 MSS 接口,可安装 SQL Server 2008 自带的 SQL Native Client 10.0(SQLNCLI10,最后一个支持 SQL Server 2000 的版本),装好后再用昨天的配置重试。
pbai 谢谢了!
再问一下,ADO.Net支持吗? 支持。PB 从 11 起就有 ADO.NET 数据库接口(DBMS 写 'ADO.Net'),走 .NET Framework 的 System.Data.SqlClient,它能连 SQL Server 2000(须打 SP4),这条路线可行。
本示例适用于 PB 12.5 + .NET Framework 2.0+(装 4.0 也可)+ SQL Server 2000 SP4。
前置:目标机已安装 .NET Framework;部署时须带上 ADO.NET 接口相关 DLL(pbado125.dll 及 Sybase.PowerBuilder.Db.dll 等)。
步骤:1) 在按钮 clicked 事件贴入下方代码;2) 修改示例输入值;3) 运行后看 MessageBox 结果。若在 IDE 的 Database Profiles 里新建 ADO.Net 连接,Preview 页生成的标准代码与本例一致,可直接用。
// 示例输入:服务器 IP(命名实例写 机器名\实例名)
string ls_server
ls_server = '192.168.1.100'
// 示例输入:数据库名
string ls_db
ls_db = 'mydb'
// 示例输入:登录账号与密码
string ls_uid, ls_pwd
ls_uid = 'sa'
ls_pwd = '123456'
transaction ltr
ltr = create transaction
ltr.DBMS = 'ADO.Net'
ltr.LogId = ls_uid
ltr.LogPass = ls_pwd
ltr.AutoCommit = False
ltr.DBParm = "DataSource='" + ls_server + "',Database='" + ls_db + "',Namespace='System.Data.SqlClient'"
if not ltr.Connect() then
MessageBox('连接失败', '错误码 ' + string(ltr.SQLDBCode) + ',' + ltr.SQLErrText)
return
end if
MessageBox('连接成功', '已通过 ADO.NET 连上 ' + ls_server + ' 的 ' + ls_db)
ltr.Disconnect()
destroy ltr
两点提醒:
① 对 SQL Server 2000 这种老库,昨天给的 ODBC(SQLSRV32)方案部署最省事,不用带 .NET 那一堆 DLL;ADO.NET 接口的主要优势是可和 DataWindow .NET / C# 共享连接(如 sqlca.GetAdoConnection()),若只是普通 C/S 存取,ODBC 仍更稳妥。
② 连不上先看 SQLDBCode/SQLErrText,常见还是 1433 端口不通或账号密码错。
pbai 感谢楼主! transaction ltr 改为uo_database db后提示错误0, 报错 0 的根因,是只改了对象名、连接方法没跟着换。
uo_database 是 PbIdea.dll 封装的对象(源码里 function boolean Open() ... alias for "sqlOpen"),它的连接入口是 Open() / Close(),不是 transaction 基类的 Connect() / Disconnect()。昨天那份 ODBC 示例写的是 ltr.Connect(),你把 transaction ltr 换成 uo_database db 后如果还调 db.Connect(),事务对象的驱动绑定对不上,Open 返回 false 时 SQLDBCode=0、SQLErrText 为空——这就是你看到的「错误 0」。它不代表成功,而是连接流程压根没走起来。
本示例适用于 PB 12.5 + PbIdea.dll + ODBC 连接 SQL Server 2000。
步骤:1) 在按钮 clicked 事件贴入下方代码;2) 修改示例输入值;3) 运行后看 MessageBox 结果。注意 create uo_database 时构造函数已自动 sqlCreate,不要再手动调用 sqlCreate()。
// 示例输入:ODBC 系统 DSN 名称(32 位 ODBC 里新建的那个)
string ls_dsn = 'mss2000'
// 示例输入:登录账号与密码
string ls_uid = 'sa'
string ls_pwd = '123456'
uo_database db
db = create uo_database
db.DBMS = 'ODBC'
db.DBParm = "ConnectString='DSN=" + ls_dsn + ";UID=" + ls_uid + ";PWD=" + ls_pwd + "'"
if not db.Open() then
MessageBox('连接失败', '错误码 ' + string(db.SQLDBCode) + ',' + db.SQLErrText)
destroy db
return
end if
MessageBox('连接成功', '已通过 uo_database 连上 SQL Server 2000')
db.Close()
destroy db
改完这两个方法名(Open/Close)错误 0 就会消失。如果后面想直接用 uo_database 查数据,还可以 uo_recordset rs = db.ExecuteSQL('SELECT ...') 再 rs.FetchNext() 逐行取,这是它比裸 transaction 更方便的地方。
pbai
页:
[1]