如果与临时表和更改跟踪结合使用,C# yield return 会导致问题吗?
Can C# yield return cause problems if used in combination with temp tables and Change Tracking?
我们正在使用 Microsoft SQL Server 2016 (SP2-CU12) (KB4536648) - 13.0.5698.0 (X64) 2020 年 2 月 15 日 01:47:30 版权所有 (c) Microsoft Corporation Standard Edition (64-位) Windows Server 2012 R2 Standard 6.3 (Build 9600: )
我们有一个 table 超过一百万行,其中有很多大字段(nvarchar(max)、bigint、uniqueidentifier)。我们在 table 上使用更改跟踪。此 table 由多线程 Web 应用程序和许多其他多线程应用程序访问。
现在在其中一个应用程序中,开发人员做了以下代码。
public IEnumerable<MyObject> GetRecords(long trackingKey)
{
using(SqlConnection conn = new SqlConnection(_connectionString))
{
conn.Open();
using(SqlCommand cmd = conn.CreateCommand())
{
StringBuilder builder = new StringBuilder();
builder.Append("SELECT Columns INTO #TEMP FROM CHANGETABLE(CHANGES BigTable, @TrackingKey) AS CT JOIN BigTable t ON t.Key=CT.Key WHERE Conditions=@Conditions
SELECT * FROM #TEMP ");
cmd.CommandText = builder.ToString();
cmd.Parameters.Add...;
using(SqlDataReader reader = cmd.ExecuteReader())
{
while(reader.Read())
{
yield return (MyObject)ExecuteMyReader(reader);
}
}
}
}
}
在“ExecuteMyReader”中,它们正在从 reader 中读取,并且对于该行,对另一个 table 执行第二个查询(平均执行大约需要 150 毫秒)。
查询 SQL 中的更改 table 查询似乎在从更改跟踪 table 中获取结果时锁定了 table。这就是为什么开发人员存储在执行最终 SELECT 查询之前会产生临时 table 的原因。 (为了尽量避免死锁。)
问题 #1:更改跟踪 table 的锁是否会在第二个 SELECT 查询完成时保持?或者一旦结果进入#Temp table?
就会发布
问题 #2:为 reader 执行 yield 与仅执行将所有内容都存储在变量中的经典方法相比有什么区别吗?例如,RAM 和#Temp table 是否会尽快发布?
问题#3:从多线程的角度来看,如果其他查询试图同时访问同一个table,是否有更好的方法来避免死锁? (有收益率,没有收益率的经典方法)
理论上,它应该只是 insert
,但是用 into
创建临时 tables 会导致问题;也许尝试声明一个 table 变量而不是
迭代器的优点是它们不需要缓冲所有内容(它们可以作为开放序列使用)——因此它们可以更有效地使用 RAM,但是 side-effect那就是 command/connection 可能保持打开的时间更长;如果涉及锁,这可能会特别令人担忧,因为 DAL 代码不知道消费者在每一行之间需要多长时间
你需要那么多隔离吗?也许考虑在较低的隔离级别查询它,并使用乐观并发(即rowversion
)进行故障检测?
我们正在使用 Microsoft SQL Server 2016 (SP2-CU12) (KB4536648) - 13.0.5698.0 (X64) 2020 年 2 月 15 日 01:47:30 版权所有 (c) Microsoft Corporation Standard Edition (64-位) Windows Server 2012 R2 Standard 6.3 (Build 9600: )
我们有一个 table 超过一百万行,其中有很多大字段(nvarchar(max)、bigint、uniqueidentifier)。我们在 table 上使用更改跟踪。此 table 由多线程 Web 应用程序和许多其他多线程应用程序访问。
现在在其中一个应用程序中,开发人员做了以下代码。
public IEnumerable<MyObject> GetRecords(long trackingKey)
{
using(SqlConnection conn = new SqlConnection(_connectionString))
{
conn.Open();
using(SqlCommand cmd = conn.CreateCommand())
{
StringBuilder builder = new StringBuilder();
builder.Append("SELECT Columns INTO #TEMP FROM CHANGETABLE(CHANGES BigTable, @TrackingKey) AS CT JOIN BigTable t ON t.Key=CT.Key WHERE Conditions=@Conditions
SELECT * FROM #TEMP ");
cmd.CommandText = builder.ToString();
cmd.Parameters.Add...;
using(SqlDataReader reader = cmd.ExecuteReader())
{
while(reader.Read())
{
yield return (MyObject)ExecuteMyReader(reader);
}
}
}
}
}
在“ExecuteMyReader”中,它们正在从 reader 中读取,并且对于该行,对另一个 table 执行第二个查询(平均执行大约需要 150 毫秒)。
查询 SQL 中的更改 table 查询似乎在从更改跟踪 table 中获取结果时锁定了 table。这就是为什么开发人员存储在执行最终 SELECT 查询之前会产生临时 table 的原因。 (为了尽量避免死锁。)
问题 #1:更改跟踪 table 的锁是否会在第二个 SELECT 查询完成时保持?或者一旦结果进入#Temp table?
就会发布问题 #2:为 reader 执行 yield 与仅执行将所有内容都存储在变量中的经典方法相比有什么区别吗?例如,RAM 和#Temp table 是否会尽快发布?
问题#3:从多线程的角度来看,如果其他查询试图同时访问同一个table,是否有更好的方法来避免死锁? (有收益率,没有收益率的经典方法)
理论上,它应该只是
insert
,但是用into
创建临时 tables 会导致问题;也许尝试声明一个 table 变量而不是迭代器的优点是它们不需要缓冲所有内容(它们可以作为开放序列使用)——因此它们可以更有效地使用 RAM,但是 side-effect那就是 command/connection 可能保持打开的时间更长;如果涉及锁,这可能会特别令人担忧,因为 DAL 代码不知道消费者在每一行之间需要多长时间
你需要那么多隔离吗?也许考虑在较低的隔离级别查询它,并使用乐观并发(即
rowversion
)进行故障检测?