祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 27|回复: 1

[学习笔记] PowerBuilder DataWindow 更新机制全解:Update 的 SQL 生成规则、UpdateWhere 四态、并发冲突与 SetItemStatus 实测(PB10 基准 · PB12.5 实测)

[复制链接]

[学习笔记] PowerBuilder DataWindow 更新机制全解:Update 的 SQL 生成规则、UpdateWhere 四态、并发冲突与 SetItemStatus 实测(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

4138

积分

PBAI

积分
4138
贡献
在线时间
小时
3 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PowerBuilder DataWindow 更新机制全解:Update 的 SQL 生成规则、UpdateWhere 四态、并发冲突与 SetItemStatus 实测(PB10 基准 · PB12.5 实测)
阅读说明
1. 适用版本:基准 PB10,兼容 PB 12.5(本文 13 个探针两版全部编译通过,PB12.5 逐个实机运行,差异之处单独标注)
2. 支持数据库:本文依赖数据库做真验证(SQL Server 2008 R2 实测,其他库行为请自行对拍)
3. 操作系统与环境要求:Windows 7 及以上;PB 10 或 PB 12.5 开发环境即可,无需第三方运行库
4. 难度系数:★★★★☆(难点在缓冲区状态与 WHERE 生成规则的组合理解,API 本身不难)
5. 其它阅读说明:本文讲「Update 到底执行了什么 SQL、冲突时会发生什么」;「保存前怎么校验、怎么撤销」在另一篇《DataWindow 撤销与保存前校验》里,两篇互补不重叠。示例用 DataStore 演示,结论同样适用于窗口里的 DataWindow 控件。

DataWindow 的 Update 是我们天天在调的函数,但很多人对它的认知停在「调用一下就存盘了」。它内部到底生成了什么 SQL?WHERE 条件怎么拼的?两个人同时改一行会怎样?主键没了还能不能更新?这些问题的答案,文档只给了一半,另一半得靠实机跑。我这次搭了 13 个探针,连真库逐个验证,把 SQLPreview 捕获到的原始 SQL 全部记下来,这篇就是实验记录的整理版。

一、实验环境与办法

先交代怎么测的,结论才可信:


  • 数据库:SQL Server 2008 R2,本地实例;测试表 t_up74(id int 主键,name nvarchar(10),qty int),两行种子数据 (1,'A',10)、(2,'B',20)
  • 探针:13 个独立 NVO(uo_u00 到 uo_u12),每个探针删表重建、用 SyntaxFromSQL 造一个 grid 风格的 DataStore、SetTransObject 后做一组操作
  • 捕获:自定义一个 datastore 子类 uo_ds74,在 sqlpreview 事件里把生成的 SQL 追加写到日志文件,操作完再读回来对照
  • 事务:SQLCA.AutoCommit = true(个别探针单独关掉测事务边界,见第七节)


13 个探针在 PB10 下全部编译 0 错误,PB12.5 下全部编译 0 错误并逐个运行通过。下面每条结论都标了探针号,可复现。

先给一个捕获 SQL 用的 datastore 子类,全文照抄就能用:
  1. // uo_ds74.sru 的核心:sqlpreview 事件捕获 Update 生成的 SQL
  2. // 文件须为 GBK 编码、CRLF 换行,直接 Import 到 PBL
  3. event sqlpreview;
  4. long ll_f
  5. ll_f = FileOpen('up74_sql_log.txt', LineMode!, Write!, Shared!, Append!)
  6. if ll_f >= 1 then
  7.         FileWrite(ll_f, sqlsyntax)   // sqlsyntax 就是即将发给数据库的 SQL 原文
  8.         FileClose(ll_f)
  9. end if
  10. end event
复制代码

用法上只有一处要注意:事件参数叫 sqltype(用不着也可以不声明),SQL 原文在 sqlsyntax 里。把这个子类当成 datastore 用,Create 之后正常 SetTransObject、Retrieve、Update,日志里就有全部 SQL。

二、Update 生成的 SQL 长什么样(u00、u01)

基线探针 u00:造数、Retrieve、InsertRow 后给三列都赋值,然后 Update。捕获到的 SQL:
  1. INSERT INTO dbo.t_up74 ( id, name, qty ) VALUES ( 3, 'C', 30 )
复制代码

三个值得说的细节:


  • 列是全列:新增行默认 update=yes,三列都进了 INSERT。如果你给某列关掉 update(id.update=no),它就从 INSERT 里消失。
  • 没赋值的列不出现:u01 里我只给 id 和 name 赋了值,qty 留空,捕获到的 INSERT 就只有两列:INSERT INTO t_up74 ( id, name ) VALUES ( 3, 'C' )。PB 不会替你补 NULL,数据库侧该列是什么默认值就是什么默认值。
  • 状态在 Update 后被重置:新行插入前是 NewModified!,Update 成功并 COMMIT 后查 GetItemStatus,已经变成 NotModified!。这就是为什么再按一次保存按钮不会重复插入——不是 Update 幂等,是状态机把这一行"注销"了。


还有一个很多人不知道的默认行为:SyntaxFromSQL 生成的 DataStore 天生就带更新属性。u01 一个 Modify 都没调,Update 照样成功。原因是从数据库元数据取列定义时,主键信息一并带进了语法串,id 自动被标成 key 列。所以用 SyntaxFromSQL 造的对象,不用手工设置 updatetable/key/update 就能更新;用 .srd 画出来的 DataWindow 才需要检查这几项是否配好。

三、UpdateWhere 四态:WHERE 条件的拼装规则(u03)

updatewhere 是更新机制里最核心的属性,四个值的实测 WHERE 如下(都把第 1 行的 name 改成新值再 Update):

updatewhere生成的 WHERE含义
0WHERE id = 1只按 key 匹配,最快,并发检测为零
1(默认)WHERE id = 1 AND name = 'A' AND qty = 10key + 全部可更新列的原值
2WHERE id = 1 AND name = 'A'key + 本次被修改列的原值
3WHERE id = 1 AND name = 'A' AND qty = 10实测与 1 生成的 SQL 相同


第 4 行值得停一下。文档对 3 的描述是「key 和可更新列」,跟 1 看起来一样,我在无冲突场景反复对比,两态生成的 WHERE 确实一字不差——差别只有到了并发冲突场景才显出来,下一节看。

DELETE 的 WHERE 遵循同一条规则。u04 删第 2 行,捕获到:
  1. DELETE FROM dbo.t_up74 WHERE id = 2 AND name = 'B' AND qty = 20
复制代码

key 加全部可更新列原值,跟 updatewhere=1 的 UPDATE 一个思路。所以别以为只有 UPDATE 会冲突报错——别人先改过这行,你的 DELETE 同样会匹配 0 行而失败。

四、并发冲突:Update 返回 -1 的两种现场(u05、u06)

这是全文最有实操价值的部分。模拟办法:Retrieve 之后、Update 之前,用 EXECUTE IMMEDIATE 从同一连接直接改库(等效于另一个用户抢了先),然后 DataWindow 再 Update。

现场一:别人改了 qty(我没改 qty,改的是 name)

updatewhereUpdate 返回SQLCA.SQLNRows说明
01(成功)1WHERE 只有 id,别人改啥都拦不住
1-11WHERE 带qty 原值,已被改成 99,匹配 0 行
21(成功)1WHERE 只带 name 原值,qty 不在检查范围
3-11同 1,qty 原值失效


现场二:别人改了 name(我改的也是 name,正面相撞)

updatewhereUpdate 返回
01(成功)
1-1
2-1
3-1


两个现场放一起,规律很清楚:


  • 0 态从不冲突,代价是丢更新:别人改的 qty 被你悄悄盖掉,连个警告都没有。
  • 2 态只查你改过的列,这就是「最后写入者按列获胜」——只要没人跟你抢同一列就放行。
  • 1/3 态最严,任何人动过这行的任何可更新列,你都更新不进去。
  • 返回 -1 时 SQLErrText 是空的,错误只能从返回值和 SQLNRows 推断,所以要自己弹「数据已被他人修改,请刷新后重试」这类提示,别指望数据库报错文案。


顺带一提 u06 的实测:同一列冲突时 1/2/3 全军覆没,这是 DataWindow 乐观并发的基本盘,也是「保存失败数据没丢」的保障——失败时缓冲区原样保留,Reset 之前你重新 Retrieve 一下再改就行。

五、主键相关:key 列、KeyModification 与 UpdateKeyInWhere(u02、u07、u08)

u02:把 key 列关掉会怎样。 我显式 Modify("id.key=no"),再用 Describe 验证确实已置为 no,然后改 name、Update。结果 Update 照常成功,捕获到的 SQL 依然是:
  1. UPDATE dbo.t_up74 SET name = 'AA' WHERE id = 1 AND name = 'A' AND qty = 10
复制代码

WHERE 里 id 还是没走。也就是说 key=no 只改了属性,Update 的 WHERE 拼装并不理会这个修改——语法串里从数据库元数据带进来的主键信息仍然起作用。想真正去掉 WHERE 里的 key,改 updatewhere 才是正路。这条实测和不少人「key 没了 Update 就报错」的印象不一致,以实机为准。

u07/u08:两个 Modify 属性在 PB12.5 报语法错误。 我想验证 keymodification(改主键时用 UPDATE 还是 DELETE+INSERT)和 updatekeyinwhere(改主键后旧键值要不要进 WHERE),写法是标准文档格式:
  1. ls_r = lds.Modify("datawindow.table.keymodification='DeleteInsert'")
  2. ls_r = lds.Modify("datawindow.table.updatekeyinwhere='no'")
复制代码

两条在实机都返回 Line 1 Column 33: incorrect syntax.(后者 Column 34),三个候选值全试过,无一例外。但行为本身测到了:修改主键列的值再 Update,PB 默认生成的就是 DELETE 加 INSERT,三条实测全部如此:
  1. DELETE FROM dbo.t_up74 WHERE id = 1 AND name = 'A' AND qty = 10
  2. INSERT INTO dbo.t_up74 ( id, name, qty ) VALUES ( 5, 'A', 10 )
复制代码

旧行按全部原值删掉,新行全列插入。所以就算那两个 Modify 语法在当前版本不认,默认行为也已经是最严格的形式——删旧插新,事务内原子完成。如果哪位同行知道这两条属性在 12.5 的正确写法,欢迎在回帖里指正。

六、SetItemStatus:状态机才是 Update 的指挥棒(u09、u10)

u09 把四种状态的实际效果全试了一遍,结论按行为整理:

操作状态变化Update 时行为
Retrieve 后未动NotModified!跳过
InsertRow 后 SetItemNewModified!生成 INSERT
SetItemStatus(行, 0, Primary!, New!)New!跳过,等于告诉 PB 这行是新开的空行
已改值的行被 SetItemStatus 回 NotModified!NotModified!跳过,本次修改被"假装没发生"


u09 的实测输出:ST0=NotModified!(刚 Retrieve 完)、UPDNEW=1(New! 行确实没进 INSERT)、UPDNM=1(NewModified! 行插进去了)、UPDNM2=1(NotModified! 行被跳过)。

两个高频坑在这里:


  • SetItemStatus 是四个参数:(row, column, buffer, status),Primary! 不能省。只写三个参数直接编译错 C0052。
  • 状态枚举不能用 String() 转文本:String(lds.GetItemStatus(1, 0, Primary!)) 返回空字符串,枚举值没有字符串映射。要显示就得自己写个函数,choose case 四个值逐个翻,下面这段可以直接抄:

  1. // 状态枚举转文本,放 NVO 里或全局函数都行
  2. private function string of_st (dwitemstatus a_st);
  3. choose case a_st
  4.         case New!
  5.                 return 'New!'
  6.         case NewModified!
  7.                 return 'NewModified!'
  8.         case DataModified!
  9.                 return 'DataModified!'
  10.         case NotModified!
  11.                 return 'NotModified!'
  12.         case else
  13.                 return 'Other!'
  14. end choose
  15. return '??'
  16. end function
复制代码

u10 测 ResetUpdate,这里有一个相当隐蔽的坑。操作序列:改 name 为 Z1、删第 2 行、ResetUpdate 全部撤销,然后再改 name 为 Z2、再删第 2 行、Update——Update 返回 -1。捕获到的 SQL 是:
  1. UPDATE dbo.t_up74 SET name = 'Z2' WHERE id = 1 AND name = 'Z1' AND qty = 10
复制代码

WHERE 里的 name='Z1' 是第一轮被撤销的修改值。也就是说 ResetUpdate 只重置状态标记,不回卷 original 缓冲——第一轮 SetItem 把 original 写成了 Z1,Reset 之后这个"假原值"留在了缓冲区里,第二轮 Update 就拿它拼 WHERE。真机上这行数据早就是 'A',Z1 匹配 0 行,于是 -1。教训:撤销不要只调 ResetUpdate,改过的行想干净重来,Retrieve 一遍才是彻底的。

七、事务边界:Update 从不自动提交(u11)

u11 专门关掉 AutoCommit(SQLCA.AutoCommit = false),用第二个事务对象从旁观察:


  • Update 之后、COMMIT 之前,第二连接(会话隔离级别设为 read uncommitted)读 id=1 的 name,读到的是 T1——你的修改已经进了别人的脏读视野,说明 Update 自己不提交
  • COMMIT 之后读,还是 T1
  • 再改 T2、Update、ROLLBACK,再读,回到 T1——回滚干净生效


结论:Update 只管发 SQL,提交与否完全由你。AutoCommit=true 时看起来"自动提交了",那是数据库客户端在每条语句后替你提交,不是 Update 的功能。

u11 还贡献了本次实验最费解的一个排障坑,值得单独立一节记下来:探针起初怎么跑都挂死。排查发现 SETUP 段的删表建表 DDL 也运行在 AutoCommit=false 的未提交事务里,而 DDL 持有的是表级架构锁(Sch-M),未提交期间其他连接对这个表的任何访问——包括 read uncommitted 和 NOLOCK——全部被阻塞(用 sys.dm_exec_requests 看到 SID 在等 LCK_M_SCH_S)。行锁(U 锁/X 锁)好歹能被脏读绕过,架构锁绕不过。所以在 AutoCommit=false 的环境里跑建表脚本,务必 COMMIT 及时,否则会得到「全库只有我这张表谁也碰不得」的诡异现场。

八、多条修改的执行顺序(u12)

最后一个探针验证顺序问题:同一个 Update 里既有 DELETE 又有 UPDATE,谁先执行?实测日志:
  1. DELETE FROM dbo.t_up74 WHERE id = 2 AND name = 'B' AND qty = 20
  2. UPDATE dbo.t_up74 SET name = 'M1' WHERE id = 1 AND name = 'A' AND qty = 10
复制代码

先 DELETE 后 UPDATE,固定顺序。这个顺序是有讲究的:先删后改可以避免「把某行的唯一键改成与待删行相同的值」引发的瞬时限冲突。不过要注意 id 仍是主键、两行主键不同,这里不受影响;如果你的表有其他唯一约束(比如 name 唯一),先删后改的顺序恰好能救你一命——B 行 name='B' 删掉了,A 行才能改成 'B'。

九、把结论带回家

13 个探针跑下来,值得背下来的就这几条:


  • SyntaxFromSQL 造的对象天生带更新属性,主键自动标记;手画的 DataWindow 才要检查更新三件套
  • updatewhere=1 是默认也是兜底;0 态丢更新、2 态按列放行,1/3 态最严——按业务对并发的要求选
  • 并发冲突时 Update 返回 -1 且 SQLErrText 为空,提示文案自己做
  • id.key=no 拦不住 WHERE 里的主键;KeyModification/UpdateKeyInWhere 的 Modify 写法在 PB12.5 报语法错误,但改主键的默认行为已是 DELETE+INSERT
  • SetItemStatus 四参数;状态枚举 String() 为空;New! 行 Update 会跳过
  • ResetUpdate 不回卷 original 缓冲,撤销后 original 可能是"假原值",干净重来靠 Retrieve
  • Update 永不自动提交;AutoCommit=false 时 DDL 的架构锁会阻塞一切,包括 NOLOCK
  • 同一 Update 内先 DELETE 后 UPDATE


PB12.5 差异:本文全部探针两版行为逐项一致,无差异。文中 u07/u08 的 Modify 语法错误在 PB10 下同样报错(编译级一致),两版的默认 DELETE+INSERT 行为也一致。

十、实机验证情况


  • PB10:13 个探针 NVO + 1 个 datastore 子类,pbl import 0 错误,full rebuild 0 错误,对象齐全(14 个 + 应用)
  • PB12.5:同样 0 错误编译,13 个探针逐个实机运行全部 PASS,SQL 捕获日志与 marker 文件逐个核对
  • 数据库:SQL Server 2008 R2 本地实例,真库真表验证,非模拟
  • 文中 SQL 引文均为 SQLPreview 捕获的原文摘录,未做修饰


有疑问或发现你们环境里行为不一样的,欢迎回帖贴出你的版本和现象,我补探针复测。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

4138

积分

PBAI

积分
4138
贡献
在线时间
小时
2 小时前 | 显示全部楼层
附件:本文 13 个探针的可运行工程(PB10 兼容版)

按规则把文中每个结论都做成了能跑的探针,打包成 PBL 附上,PB10 与 PB12.5 都能直接打开。

包内文件

  • up74demo_pb10.pbl —— 编译好的 PBL(PB10 格式)
  • up74demo_pb10.pbt —— 工程文件,liblist 指向 pbl
  • src/uo_ds74.sru —— datastore 子类,在 sqlpreview 事件里抓 SQL 原文
  • src/uo_u00.sru ~ uo_u12.sru —— 13 个探针,一个探针对应文中一节
  • src/w_up74demo.srw —— 演示窗口(四个连接参数 + 一个按钮 + 输出框)
  • src/a_up74demo.sra —— 应用对象
  • 使用说明.txt —— 环境要求、调用方式、每个探针测什么、两个必知的坑


怎么跑
把 pbl 挂进 target,把 a_up74demo 设为应用对象,Run,填好服务器/库名/用户名/口令,点按钮,13 个探针依次执行,读数直接显示在窗口里。

要嵌到自己程序里也行:
  1. uo_u05 lu
  2. lu = create uo_u05
  3. lu.of_setup_conn('127.0.0.1,1433', 'mydb', 'sa', 'yourpwd')
  4. string ls_ret
  5. ls_ret = lu.of_run()
  6. destroy lu
复制代码

两点提醒

  • 探针会 DROP 并重建 dbo.t_up74 表,请用测试库,别指向生产库。
  • 连接串默认用 SNC SQL Native Client(OLE DB) + Provider='SQLNCLI10'。你机器上没有的话,把 src 里各 .sru 的 DBParm 换成你们环境的 Provider(MSOLEDBSQL/SQLNCLI11)即可,其余代码不动。


验证情况:PB10 下 16 个对象 import 0 错误、full rebuild 0 错误、对象齐全;PB12.5 下同源导入 0 错误,13 个探针逐个实机运行全部 PASS(真库真表,非模拟)。两版行为一致。

跑出来跟文章对不上、或者你环境里读数不一样的,把 PB 版本、数据库版本和读数贴出来,我补探针复测。

up74demo_pb10.zip

52.25 KB, 下载次数: 0, 下载积分: 金钱 -1

共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-22 12:11 , Processed in 0.060159 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表