使用 ExecuteNonQuery 的并发问题
本文关键字:问题 并发 ExecuteNonQuery 使用 | 更新日期: 2023-09-27 18:30:55
我正在将简单的文本日志条目写入SQL表,如下所示:
private SqlConnection sqlcon;
// ...
try {
sqlcon.Open();
SqlCommand cmd = sqlcon.CreateCommand();
cmd.CommandText = this.logString;
cmd.ExecuteNonQuery();
}
/* catch SqlException, InvalidOperationException, Exception */
/* finally sqlcon.Close() */
异步处理设置为 false,因此我认为日志条目将按执行顺序出现在表中。但事实并非如此,如果在大约 5 毫秒的时间内触发 2 个或更多日志条目。
所以我的猜测是 ExecuteNonQuery 在不同的线程上运行,并且不知何故不同的事件会混淆。我已经尝试过将异步处理与BeginExecuteNonQuery和EndExecuteNonQuery一起使用,但它弄乱了我的代码,并且无论如何都不起作用。
所以我的问题是:有没有办法确保非查询完全按照触发顺序执行?
编辑:也许这很重要,我正在使用这样的时间戳
cmd.Parameters.Add("timestamp", SqlDbType.DateTime ).Value = System.DateTime.Now;
异步?
如果您每次都无法真正确定时打开/关闭连接,那么调用BeginExecuteNonQuery
(这是 MARS 之前使用的方法)在某种程度上是等效的(请原谅我,我知道这并不准确)。实际上每个请求都是同步的,但是,如果您以 5 毫秒的间隔记录某些内容,则无法确定 SQL Server 为该请求提供服务的顺序(这里的数据库人员会考虑 IDENTITY 列的老问题)。
时间戳
然后,解决方案应该是使用 TimeStamp 列并根据该列对结果进行排序(写入顺序无关紧要)。这是一个完美的解决方案(正如其他答案所指出的那样,不要忘记使用正确的数据类型来存储数据)。
如何获取实际时间?您可以使用DateTime.Now
函数在客户端(如示例中)或在服务器端使用 SYSDATETIME
(如 @PetrAbdulin 所示)执行此操作。如果您不需要高分辨率,这是一个完美的解决方案。
精度
这两个函数都依赖于 Windows 系统计时器,并且它的分辨率不会超过 10 毫秒(然后,如果您确实需要 5 毫秒的粒度,则应避免使用它们)。
在MSDN上,您可以读到Windows NT上的DateTime.Now分辨率为10毫秒,SYSDATETIME调用GetSystemTimeAsFileTime,FILETIME
结构具有100毫秒的精度,但计时器本身没有被授予来实现该结果!!在某些情况下,您甚至可能会得到 1 毫秒的时钟周期,但它根本不可靠。在有关秒表计时器的文档中,您可以阅读:
秒表类使用的计时器取决于系统硬件 和操作系统。如果秒表计时器为真,则高分辨率为真 基于高分辨率性能计数器。否则 Is高分辨率为 false,表示秒表计时器为 基于系统计时器。
什么意思?系统计时器永远不会被授予具有高分辨率(不要被用于存储时间的结构的精度所混淆)。
这可能是一个问题,也可能不是,这取决于你将使用什么日志。如果您必须在备份期间记录从一个文件夹复制到另一个文件夹的文件列表,那么您可能不需要这样的精度。如果您的日志可能用于合法的东西(是的,我知道它们不应该有任何法律价值)或调试一个微妙的线程问题,那么你将需要它。
解决 方案
如果您需要高分辨率计时器(在 Windows 上,您可以将 10 毫秒<的任何计时器视为高分辨率),则必须处理性能计数器。看看这篇关于时间的好文章。当然,你不需要所有这些东西,但它指出了问题所在。>
在 .NET 中,可以将DateTime.Now
与StopWatch
一起使用(检查 IsHighResolution
属性)。在SO上阅读这篇关于使用StopWatch
来提高DateTime.Now
精度的好文章。
常见错误
首先,不要将用于存储时间的数据类型的精度与计时器的分辨率混淆。如果将该值存储在低分辨率字段中,计时器的精确程度并不重要,但使用高分辨率字段不会将粗略的计时器转换为高分辨率计时器。
此外,您不应使用本地时间作为时间戳,当系统时间因夏令时而更改时,您会混淆日志。想一想:
00:00 02:00 02:01 03:00 02:00日志 #1 #2 #3 #4系统时间 -1
现在,当您读取日志时,您将获得以下顺序:1,2,4,3。 因此,您永远不应该对日志使用DateTime.Now
和SYSDATETIME
,您应该始终首选DateTime.UtcNow
和SYSUTCDATETIME
功能(此外,DateTime.UtcNow
的性能要好一点)。
如果使用 SQL Server SYSDATETIME 插入时间戳会怎样?我认为ExecuteNonQuery
是同步的,但您的时间戳可能有问题。
在Windows中有一个关于时间分辨率的好文档。
SQL Server 的日期时间精度为 3 毫秒。乱序列出的条目是否具有相同的时间戳?(毫秒以 0、3 或 7 结尾)。
您需要一个更高精度的字段,如 datetime2 数据类型(可从 SQL Server 2008 获得),或者需要一个增量计数器来正确排序项目。
你为什么不使用事务? 如果数据很重要,那么你应该使用SqlTransaction。
为什么需要订购?你的桌子上有pk吗?它是如何插入的,还是时间戳是你的PK?
顺便说一下,请不要缓存连接。 .NET 正在为你更好地做到这一点。 在此 MSDN 示例中使用类似。