迁移至Azure SQL后MS Access更新链接视图遇写入冲突求助
问题概述
我有一个表单(子表单属性不影响问题),其记录源为多表链接视图。此前通过表单更新字段从未出现问题,但将本地SQL数据库迁移至Azure SQL并改用ODBC Driver 17 for SQL Server后,更新字段时频繁出现知名的Write Conflict错误。我曾多次通过确保所有bit字段有默认值且无NULL值解决此类问题,本次还为相关表添加了timestamp字段,但问题仍未解决。另有一个以单表视图为记录源的表单可正常更新,且当前表单无底层代码影响,仅修改单个值并通过写入按钮提交就会报错。我了解到有建议使用查询替代直接绑定视图/表,但该视图包含多个Access难以处理的关联,且此前多年一直正常运行,重构查询难度较大。此外,我已将视图简化为仅包含tblCard,但仍出现相同错误。恳请提供问题原因分析及解决方案建议。
问题根源排查方向
- ODBC驱动兼容性问题:ODBC Driver 17和旧版驱动(或本地SQL使用的驱动)在并发检测、timestamp字段处理上逻辑不同,加上Azure SQL的网络延迟,容易让Access误判记录被修改。
- 视图结构限制:就算简化成只包含tblCard,视图里的
TOP (100) PERCENT、ORDER BY、自定义函数生成的字段(比如crdScope、orderdate)都会让Access无法正确生成更新语句,或者在比对原始数据时出现偏差。 - timestamp字段解析差异:Azure SQL里的timestamp(实际是rowversion)生成逻辑和本地SQL有细微差别,Access更新前后比对timestamp值时,可能因为网络传输或驱动解析的问题导致不匹配。
- 计算字段干扰:tblCard里的
crdEntireNumber是计算字段,Access绑定表单时可能把它当成可更新字段,或者比对记录时包含这个字段,导致误报冲突。 - 无意义自引用外键:tblCard上的
FK_tblCard_tblCard是自引用主键的外键,虽然设置了NOCHECK,但可能干扰Access的记录锁定逻辑。
可行的解决办法
驱动与连接配置调整
- 试试换回ODBC Driver 11或13,如果问题消失,说明是Driver 17的兼容性问题;如果必须用Driver 17,在连接字符串里加
UseFMTONLY=No或DataTypeCompatibility=80,强制兼容旧版SQL Server的元数据解析规则。 - 在Access的ODBC连接设置里开启“使用服务器端游标”,减少本地缓存数据和服务器端的不一致。
视图优化
- 删掉视图里的
TOP (100) PERCENT和ORDER BY,这两个子句会让Access把视图当成不可更新的结果集,排序需求在Access表单的“排序与筛选”里设置就行。 - 视图里同时存在自定义函数生成的
crdScope和tblCard原生的crdScope字段,直接用表原生的字段替换函数生成的,避免字段名冲突或Access混淆。 - 移除视图里不需要的关联(比如tblCardTransaction、tblImageExported的LEFT JOIN),只保留tblCard的必要字段,降低Access解析更新语句的复杂度。
表结构调整
- 检查tblCard的
crdEntireNumber计算字段,确保Access识别它是只读的;要么在表单里取消这个字段的绑定,要么在视图里排除它。 - 删掉tblCard上的
FK_tblCard_tblCard自引用外键,这个外键没有实际业务作用,反而可能干扰Access的锁定逻辑。 - 确认所有bit字段的默认值正确,表单里绑定的控件类型和字段匹配(比如用复选框而不是文本框)。
Access表单设置调整
- 把表单的“记录锁定”改成“编辑的记录”,不要用“所有记录”,减少不必要的锁定冲突。
- 关掉表单的“自动保存”,用代码手动提交更新,更新前先获取最新的timestamp值,确保比对一致。示例代码:
Dim rs As Recordset Set rs = Me.RecordsetClone rs.FindFirst "crdID = " & Me.crdID.Value rs.Edit rs!crdStatus = Me.crdStatus.Value ' 其他需要更新的字段同理赋值 rs.Update Me.Requery
其他排查步骤
- 用SQL Server Profiler(或Azure SQL的查询存储)捕获Access提交的更新语句,检查WHERE子句是否正确包含了主键和timestamp字段。
- 直接在Azure SQL里用UPDATE语句修改记录,验证数据库层面有没有并发问题;如果数据库层面正常,那问题肯定出在Access和ODBC驱动的交互上。
相关SQL代码
视图:vewCardsLatestByPerson
CREATE VIEW [dbo].[vewCardsLatestByPerson] AS SELECT TOP (100) PERCENT dbo.tblCard.crdID, dbo.tblCard.crdStatus, dbo.tblCard.crdDate, dbo.tblCard.crdExpiry, dbo.tblCard.crdType, dbo.tblCard.perID, dbo.tblCard.crdACode, dbo.tblCard.crdNumber, dbo.ufnGetCardScope(dbo.tblCard.crdID) AS crdScope, dbo.tblCard.crdColour, dbo.tblCard.logID, dbo.tblCard.crdWorkStationID, dbo.tblCard.crdUserID, CASE WHEN dbo.tblCard.crdStatus = 12 THEN '01-JAN-1800' ELSE dbo.tblCard.crdDateCreated END AS orderdate, dbo.tblCard.crdDateCreated, dbo.tblCard.batID, dbo.tblCard.crdMinor, dbo.tblCard.crdConditional, dbo.tblCard.crdCardEntireNumber, dbo.tblCard.crdUseIF, dbo.tblImageExported.ID, dbo.tblCardTransaction.trnDate, dbo.tblCard.jobReady FROM dbo.tblCard LEFT OUTER JOIN dbo.tblCardTransaction ON dbo.tblCard.crdID = dbo.tblCardTransaction.crdID AND dbo.tblCardTransaction.ctyID = 4 LEFT OUTER JOIN dbo.tblImageExported ON dbo.tblCard.crdCardEntireNumber = dbo.tblImageExported.CardNumber ORDER BY dbo.tblCard.perID, dbo.tblCard.crdID DESC GO
表:tblCard
CREATE TABLE [dbo].[tblCard]( [crdID] [int] IDENTITY(1,1) NOT NULL, [crdStatus] [smallint] NOT NULL, [crdDate] [smalldatetime] NOT NULL, [crdExpiry] [smalldatetime] NOT NULL, [crdType] [int] NOT NULL, [perID] [int] NOT NULL, [crdACode] [nchar](3) NOT NULL, [crdNumber] [int] NOT NULL, [crdScope] [nchar](3) NOT NULL, [crdColour] [nchar](1) NOT NULL, [logID] [int] NULL, [crdWorkStationID] [nchar](25) NULL, [crdUserID] [nchar](100) NULL, [crdDateCreated] [datetime] NULL, [batID] [int] NULL, [crdMinor] [bit] NOT NULL, [crdConditional] [bit] NOT NULL, [crdEntireNumber] AS (calculated field) [crdUseIF] [bit] NOT NULL, [crdIssuer] [nvarchar](50) NULL, [issBatID] [int] NULL, [crdVersion] [nvarchar](10) NULL, [NewFormat] [bit] NOT NULL, [CASA] [bit] NOT NULL, [jobReady] [bit] NOT NULL, [CRN] [nvarchar](10) NULL, [BatchID] [int] NULL, [timestamp] [timestamp] NOT NULL, CONSTRAINT [PK_tblCard] PRIMARY KEY CLUSTERED ( [crdID] ASC )WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdStatus] DEFAULT ((0)) FOR [crdStatus] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdDate] DEFAULT ('1/1/2000') FOR [crdDate] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdExpiry] DEFAULT ('1/1/2000') FOR [crdExpiry] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdType] DEFAULT ((0)) FOR [crdType] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdNumber] DEFAULT ((0)) FOR [crdNumber] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdDateCreated] DEFAULT ((sysdatetimeoffset() AT TIME ZONE 'AUS Eastern Standard Time')) FOR [crdDateCreated] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdMinor] DEFAULT ((0)) FOR [crdMinor] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdConditional] DEFAULT ((0)) FOR [crdConditional] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_crdUseICAO] DEFAULT ((0)) FOR [crdUseICAO] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_NewFormat] DEFAULT ((1)) FOR [NewFormat] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_CASA] DEFAULT ((0)) FOR [CASA] GO ALTER TABLE [dbo].[tblCard] ADD CONSTRAINT [DF_tblCard_jobReady] DEFAULT ((0)) FOR [jobReady] GO ALTER TABLE [dbo].[tblCard] WITH NOCHECK ADD CONSTRAINT [FK_tblCard_tblCard] FOREIGN KEY([crdID]) REFERENCES [dbo].[tblCard] ([crdID]) GO ALTER TABLE [dbo].[tblCard] CHECK CONSTRAINT [FK_tblCard_tblCard] GO
表:tblCardTransaction
CREATE TABLE [dbo].[tblCardTransaction]( [trnID] [int] IDENTITY(1,1) NOT NULL, [trnDate] [datetime] NOT NULL, [ctyID] [int] NULL, [trnDetails] [nvarchar](500) NULL, [crdID] [int] NOT NULL, [trnwkstationID] [nvarchar](50) NULL, [trnUserID] [nvarchar](50) NULL, [timestamp] [timestamp] NOT NULL, CONSTRAINT [PK_tblCardTransaction] PRIMARY KEY CLUSTERED ( [trnID] ASC )WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] GO
表:tblImageExported
CREATE TABLE [dbo].[tblImageExported]( [Number] [nvarchar](50) NOT NULL, [ID] [nvarchar](50) NOT NULL, [perName] [nvarchar](100) NOT NULL, [ImportFileName] [nvarchar](256) NOT NULL, [timestamp] [timestamp] NULL, CONSTRAINT [PK_tblImageExported] PRIMARY KEY CLUSTERED ( [Number] ASC )WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] GO
内容的提问来源于stack exchange,提问作者ibeme99
相关产品推荐
相关产品推荐

