我们有一段时间以来一直在我们客户的某个网站上看到奇怪的数据完整性问题。经过大量调查后,我们现在将其与数据库调用隔离开来。
如果两个用户同时调用相同的存储过程,偶尔会有一个用户获得另一个用户的结果。
我们已经设置了一个测试来验证这一点,并且我们有一个循环N次调用查询。
如果两个用户调用相同的查询,我们可以验证大约3%的时间,将返回错误的结果。
以下是我们如何调用数据库查询的示例。
public DataTable ExecuteDataTable(string storedProcedureName, int timeout, bool cleanParams, params SqlParameter[] arrParam) {
string connstr = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["LocalSqlServer"].ToString();
DataTable dt = new DataTable();
using (SqlConnection cnn = new SqlConnection(connstr)) {
cnn.Open();
// Define the command
using (SqlCommand cmd = new SqlCommand()) {
cmd.Connection = cnn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcedureName;
cmd.CommandTimeout = timeout;
// Handle the parameters
if (arrParam != null) {
foreach (SqlParameter param in arrParam) {
cmd.Parameters.Add(param);
}
}
// Execute the command and fill the DataTable with the result
using (SqlDataAdapter da = new SqlDataAdapter(cmd)) {
da.Fill(dt);
}
}
}
return dt;
}
如果我注释掉 SQL命令区域,并且只是手动生成数据行,那么结果是正确的。
我们正在为TransactionScopes使用 ReadCommitted 隔离级别。
任何人都可以对此有所了解吗?
编辑:我们已经隔离了存储过程,现在已确定原因是调用 OPTION(RECOMPILE)
显然,在没有某种锁定的情况下,这在多用户环境中是不安全的
编辑:以下是显示问题的存储过程。如果我注释掉OPTION RECOMPILE,这绝对没问题。一旦我重新介绍它,我们就会遇到上述问题。
ALTER PROCEDURE [dbo].[ViewSalesOrderHeadersTest] (
@RowStart int,
@RowCount int,
@SortCol nvarchar(20),
@SortDir nvarchar(10),
@SLOrderNo int
)
AS
SET NOCOUNT ON;
WITH QueryResult AS (
SELECT
ROW_NUMBER() OVER ( ORDER BY
CASE WHEN @SortCol='SLOrderNo' AND @SortDir='ASC' THEN ORD.SLOrderNo END ASC,
CASE WHEN @SortCol='SLOrderNo' AND @SortDir='DESC' THEN ORD.SLOrderNo END DESC,
CASE WHEN @SortCol='OrderDate' AND @SortDir='ASC' THEN ORD.OrderDate END ASC,
CASE WHEN @SortCol='OrderDate' AND @SortDir='DESC' THEN ORD.OrderDate END DESC,
CASE WHEN @SortCol='CustomerName' AND @SortDir='ASC' THEN ORD.CustomerName END ASC,
CASE WHEN @SortCol='CustomerName' AND @SortDir='DESC' THEN ORD.CustomerName END DESC,
CASE WHEN @SortCol='DeliveryDate' AND @SortDir='ASC' THEN ORD.DeliveryDate END ASC,
CASE WHEN @SortCol='DeliveryDate' AND @SortDir='DESC' THEN ORD.DeliveryDate END DESC
) AS ROW_ID,
ORD.SLOrderNo
FROM SalesOrderHeaders ORD
WHERE
(@SLOrderNo = 0 OR @SLOrderNo = ORD.SLOrderNo)
)
SELECT (SELECT MAX(ROW_ID) FROM QueryResult) AS TOTAL_ROWS, SalesOrderHeaders.*
FROM QueryResult
INNER JOIN SalesOrderHeaders ON QueryResult.SLOrderNo=SalesOrderHeaders.SLOrderNo
WHERE ROW_ID BETWEEN @RowStart AND @RowStart + @RowCount -1
ORDER BY ROW_ID
--OPTION (RECOMPILE)