我在下面提供了完整的复制品,但这是导致奇怪结果的基本操作。
datetime2(7)
列的SQL Server表中插入一行,并将该列设置为SYSUTCDATETIME()
System.DateTime
类型(使用System.Data.SqlClient
)SELECT * FROM table WHERE DateColumn < @readDate
我的假设是这是一个精度问题(例如,.net datetime
的精度高于SQL Server datetime2(7)
的精度,反之亦然)。
那么我的问题是:
我还发现了以下内容:
GETUTCDATE()
而不是SYSUTCDATETIME()
只会导致20%的时间失败datetime
或datetime2([1-2])
日期类型不会发生此问题,仅当使用datetime2的精度为3或更高时会发生此问题using System;
using System.Data.SqlClient;
namespace DateTimeTesting
{
public class Program
{
public static void Main()
{
const string connectionString = "Server=(localdb)\\mssqllocaldb;Trusted_Connection=True;ConnectRetryCount=0";
const int numRuns = 1000;
const int printLimiter = 100;
var connection = new SqlConnection(connectionString);
connection.Open();
using (var dropDbCommand = connection.CreateCommand())
{
dropDbCommand.CommandText = @"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'DateTimeTesting') BEGIN CREATE DATABASE [DateTimeTesting] END;";
dropDbCommand.ExecuteNonQuery();
}
connection.ChangeDatabase("DateTimeTesting");
using (var createTableCommand = connection.CreateCommand())
{
createTableCommand.CommandText = "IF OBJECT_ID('JustChecking') IS NULL CREATE TABLE JustChecking (id INT IDENTITY(1,1), CreateDateUTC datetime2(7))";
createTableCommand.ExecuteNonQuery();
}
try
{
int weirdCounter = 0;
for (int i = 0; i < numRuns; ++i)
{
if ((i + 1) % printLimiter == 0)
{
Console.WriteLine($"Run #{i + 1}");
}
int id = -1;
DateTime found = DateTime.MinValue;
using (var insertCommand = connection.CreateCommand())
{
insertCommand.CommandText = "INSERT INTO JustChecking (CreateDateUTC) VALUES (SYSUTCDATETIME()); SELECT @@IDENTITY as id;";
using var insertReader = insertCommand.ExecuteReader();
if (insertReader.Read())
{
id = (int)insertReader.GetDecimal(0);
}
}
using (var selectCommand = connection.CreateCommand())
{
selectCommand.CommandText = "SELECT CreateDateUTC FROM JustChecking WHERE id = @id";
selectCommand.Parameters.AddWithValue("@id", id);
using var selectReader = selectCommand.ExecuteReader();
if (selectReader.Read())
{
found = selectReader.GetDateTime(0);
}
}
using (var weirdCommand = connection.CreateCommand())
{
weirdCommand.CommandText = "SELECT id, CreateDateUTC FROM JustChecking WHERE CreateDateUtc < @inputDate AND id = @inputId";
weirdCommand.Parameters.AddWithValue("@inputDate", found);
weirdCommand.Parameters.AddWithValue("@inputId", id);
using var weirdReader = weirdCommand.ExecuteReader();
while (weirdReader.Read())
{
weirdCounter++;
if (weirdCounter % printLimiter == 0)
{
Console.WriteLine($"Weird #{weirdCounter} = id: {weirdReader.GetInt32(0)}, createDateUtc: {weirdReader.GetDateTime(1):O}, inputDate: {found:O}");
}
}
}
}
Console.WriteLine($"Out of {numRuns} runs found {weirdCounter} weird results which accounted for {(double)weirdCounter / (double)numRuns} percent of runs");
connection.ChangeDatabase("master");
using (var dropDbCommand = connection.CreateCommand())
{
dropDbCommand.CommandText = "DROP DATABASE DateTimeTesting";
dropDbCommand.ExecuteNonQuery();
}
}
finally
{
connection.Close();
connection.Dispose();
}
}
}
}
使用的版本:
System.Data.SqlClient
v4.8.1 答案 0 :(得分:2)
SQL Server 2016中引入了一项重大更改,更改了DATETIME值转换为DATETIME2值的方式,因此与DATETIME2列进行比较时始终使用DATETIME2参数至关重要。
在数据库兼容级别130下,来自的隐式转换 datetime到datetime2数据类型通过记帐显示出更高的准确性 小数毫秒,导致不同的转换 价值观。每当混合使用显式强制转换为datetime2数据类型 存在datetime和datetime2数据类型之间的比较方案。 有关更多信息,请参见此Microsoft Support Article。
Breaking changes to Database Engine features in SQL Server 2016
另请参阅此blog
从本质上讲,这是另一个绝不使用AddWithValue的原因,因为它应始终基于SQL Server列类型进行设置,而基于.NET参数值类型来设置参数类型。
要解决此问题,只需使用DATETIME2参数即可。
weirdCommand.Parameters.Add("@inputDate",System.Data.SqlDbType.DateTime2, 7).Value = found;
答案 1 :(得分:1)
像这样使用DateTime很不常见,这表明真正的问题是另外一回事。从评论看来,实际问题是如何删除较旧的记录。
最有效的方法是使用table partitioning。它对应用程序是透明的,自SQL Server 2016起所有版本(从Express到Enterprise)都可用。
删除几乎是瞬时的-您可以使用partition switching between a full table and an empty one,有效地使空分区成为源表的一部分。这只是一个元数据操作,因此非常快。您还可以将分区从实时表移动到存档表,可能存储在速度较慢的介质中