目前我在SQL Server检索过程中遇到了一个问题。
当同一参数同时由多个用户(用户数> 40)执行时,存储过程返回意外结果。对于No of users< = 20,它显示预期结果。使用jMeter测量的错误率为0.07-0.10%。
以下SQL语句应返回一行。但是在负载很重的情况下,它有时不返回任何行。
exec RetrievingNode @Keys='one,two,three'
这是存储过程。
CREATE PROCEDURE [dbo].[RetrievingNode] @Keys nvarchar(max)
as
Begin
SET NOCOUNT ON;
--***************************************
--***************************************
-- Turn On or Off updation of LastAccessDateTime
-- To Turn on set @TurnOnUpdation=1
-- To Turn Off set @TurnOnUpdation=0
declare @TurnOnUpdation as bit
set @TurnOnUpdation=1
--***************************************
--***************************************
declare @Variable as table(
Value nvarchar(max),
KeyedNodes_Id int,
KeyNodeValue nvarchar(max),
IsParent bit,
IsReadOnly bit )
-- this table will hold ids against which LastAccessDateTime can be updated.
declare @tbl_Keys table(
id int identity(1, 1),
Value nvarchar(max))
--following will take all the keys into @tbl_Keys
insert into @tbl_Keys
(Value)
select Data
from Split(@Keys, ',')
DECLARE @count as int
DECLARE @counter as int
SET @counter=1
SELECT @count = COUNT(*)
FROM @tbl_Keys
--Declare @ChildId as int
declare @ChildNodes table(
id int,
Value nvarchar(max),
ParentNode_id int)
declare @ParentId as int
WHILE( @counter <= @count )
BEGIN
DECLARE @Key as nvarchar(max)
SELECT @Key = Value
FROM @tbl_Keys
WHERE id = @counter
if( @counter = 1 )
begin
if not exists(select *
from keyednodes(nolock) KN
where KN.Value = @Key
and KN.ParentNode_Id is null)
BEGIN
---node does not exists
Break;
END
declare @ValueExistsForFirstKey as bit
set @ValueExistsForFirstKey=0
insert into @ChildNodes
select k2.Id,
k2.Value,
k2.ParentNode_Id
from keyednodes(nolock) k1
inner join keyednodes(nolock) k2
on k1.id = k2.ParentNode_id
where K1.value = @Key
and k1.ParentNode_Id is null
if( @count = 1 )
BEGIN
IF EXISTS(select GV.Value,
GV.KeyedNodes_Id,
KN.Value
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Value = @Key
and KN.ParentNode_Id is null)
BEGIN -- If value of node exists
SET @ValueExistsForFirstKey=1
insert into @Variable
select GV.Value,
GV.KeyedNodes_Id,
KN.Value,
1,
GV.ReadOnly
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Value = @Key
and KN.ParentNode_Id is null
END
ELSE
IF NOT EXISTS(select *
from @ChildNodes)
BEGIN
insert into @Variable
select -1,
-1,
-1,
0,
0
--Node exists but No value or children exists
GOTO ExitFromProcedure
END
IF( @counter = @count )
BEGIN
if exists(select Id,
Value,
ParentNode_Id
from keyednodes(nolock) KN
where KN.Value = @Key
and KN.ParentNode_Id is null)
BEGIN
if( @ValueExistsForFirstKey = 0 )
and ( not exists(select *
from @ChildNodes) )
BEGIN
insert into @Variable
select -1,
-1,
-1,
0,
0
GOTO ExitFromProcedure
END
END
END
END
end
else
begin
declare @KeyIsChild as int
set @KeyIsChild=0
if exists(select *
from @ChildNodes
where Value = @Key)
begin
set @KeyIsChild=1
End
ELSE
BEGIN
--Key path does not exist
--print 'key path does not exist'
GOTO ExitFromProcedure
ENd
if not exists(select *
from @ChildNodes
where Value = @Key)
BEGIN
set @ParentId=0
END
ELSE
BEGIN
select @ParentId = Id
from @ChildNodes
where Value = @Key
ENd
delete @ChildNodes
if( @KeyIsChild = 1 )
begin
insert into @ChildNodes
select k2.Id,
k2.Value,
k2.ParentNode_Id
from keyednodes(nolock) k1
inner join keyednodes(nolock) k2
on k1.id = k2.ParentNode_id
where k2.ParentNode_Id = @ParentId
end
end
SET @counter=@counter + 1
END
if exists(select *
from @ChildNodes)
begin
IF EXISTS (select GV.Value,
CN.id,
CN.Value
from @ChildNodes CN
left outer join GlobalVariables(nolock) GV
on CN.Id = GV.KeyedNodes_Id --children
union all
select GV.Value,
GV.KeyedNodes_Id,
KN.Value
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Id = @ParentId--children
)
BEGIN
insert into @Variable
select GV.Value,
CN.id,
CN.Value,
0,
GV.ReadOnly
from @ChildNodes CN
left outer join GlobalVariables(nolock) GV
on CN.Id = GV.KeyedNodes_Id --children
union all
select GV.Value,
GV.KeyedNodes_Id,
KN.Value,
1,
GV.ReadOnly
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Id = @ParentId--children
END
ELSE
BEGIN
if( @count <> 1 )
insert into @Variable
select -1,
-1,
-1,
0,
0
if( @count = 1 )
and not exists(select *
from @Variable)
insert into @Variable
select -1,
-1,
-1,
0,
0
END
end
else
Begin
if ( @KeyIsChild = 1 )
BEGIN
IF EXISTS (select GV.Value,
GV.KeyedNodes_Id,
KN.Value
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Id = @ParentId)
BEGIN
---IF value of the node exists
insert into @Variable
select GV.Value,
GV.KeyedNodes_Id,
KN.Value,
1,
GV.ReadOnly
from keyednodes(nolock) KN
inner join GlobalVariables(nolock) GV
on KN.Id = GV.KeyedNodes_Id
and KN.Id = @ParentId --node
END
ELSE
BEGIN
insert into @Variable
select -1,
-1,
-1,
0,
0 --Node exists but No value or children exists
END
END
End
----
ExitFromProcedure:
select KeyedNodes_Id,
KeyNodeValue,
Value,
IsParent,
Isnull(IsReadOnly, 0)as IsReadOnly
from @Variable
order by IsParent desc,
KeyNodeValue asc
--update LastAccessDateTime
IF( @TurnOnUpdation = 1 )
BEGIN
IF EXISTS(select *
from @Variable)
BEGIN
UPDATE GlobalVariables
SET LastAccessDateTime = getdate()
WHERE KeyedNodes_Id IN (SELECT KeyedNodes_Id
FROM @Variable)
END
END
End
以下是上述过程中引用的Split()UDF:
CREATE FUNCTION [dbo].[Split] ( @RowData nvarchar(max), @SplitOn nvarchar(5) )
RETURNS @RtnValue table
(
Id int identity(1,1),
Data nvarchar(max)
) AS
BEGIN
Declare @Cnt int; Set @Cnt = 1
While (Charindex(@SplitOn,@RowData)>0)
Begin
Insert Into @RtnValue (data)
Select Data = ltrim(rtrim(Substring(@RowData,1,Charindex(@SplitOn,@RowData)-1)))
Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData))
Set @Cnt = @Cnt + 1
End
Insert Into @RtnValue (data)
Select Data = ltrim(rtrim(@RowData))
Return
END
答案 0 :(得分:4)
这几乎肯定是事务隔离级别问题。我没有花时间真正深入研究proc的逻辑,但只是快速浏览一下,似乎这个过程更新状态被其他连接弄脏,导致间歇性的数据问题。
要尝试的东西(在您的TEST数据库上!)是删除所有NOLOCK提示,并在事务中执行此过程。就像我说的,我还没有真正分析过逻辑,但试试这个:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; --Increase isolation level
BEGIN TRAN
... your proc ...
COMMIT
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; --Set back to the default on this connection
您的吞吐量可能会下降,但结果会变得正确。仔细分析proc以找到正确的事务隔离级别。可能SET TRANSACTION ISOLATION LEVEL READ COMMITTED SNAPSHOT
将以更高的吞吐量返回正确的答案,但这是首先要分析和测试的东西。
答案 1 :(得分:3)
首先,IMO David Markle的回答是正确的。但是,FWIW我发布这个答案,为您提供清理,更正(恕我直言)的代码示例,其中删除了所有“(nolock)”提示。
我还没有实现Isolation和Transaction容器语句,但是如果请求我可以。我通常建议使用SNAPSHOT Isolation,因为存在高并发性问题。
下面的代码删除了许多冗余和过时的路径。以下经常使用的(反)模式:
IF EXISTS(
SELECT ...{complex query}
)
begin
INSERT ... SELECT ...{same complex query}
end
ELSE
begin
{do something else}
end
已经变得更简单,更快:
INSERT ... SELECT ...{same complex query}
IF @@ROWCOUNT = 0
begin
{do something else}
end
最后,我当然不能测试代码或确保代码中没有错误。因此,如果您使用它,您应该确保自己彻底测试它。
CREATE PROCEDURE [dbo].[RetrievingNode] @Keys nvarchar(max) as
Begin
SET NOCOUNT ON;
-- Turn On or Off updation of LastAccessDateTime
DECLARE @TurnOnUpdation as bit; SET @TurnOnUpdation=1
DECLARE @Variable as table
(
Value nvarchar(max),
KeyedNodes_Id int,
KeyNodeValue nvarchar(max),
IsParent bit,
IsReadOnly bit
)-- this table will hold ids against which LastAccessDateTime can be updated.
DECLARE @tbl_Keys table
(
id int identity(1,1),
Value nvarchar(max)
)
--following will take all the keys into @tbl_Keys
INSERT INTO @tbl_Keys(Value) SELECT Data FROM Split(@Keys,',');
DECLARE @key_count as int; SELECT @key_count=COUNT(*) FROM @tbl_Keys
DECLARE @key_depth as int;
DECLARE @ChildNodes table
(
id int,
Value nvarchar(max),
ParentNode_id int
)
DECLARE @ParentId as int
DECLARE @KeyIsChild as int; SET @KeyIsChild=0;
--====== Process the First(root) Key ====
SET @key_depth=1;
DECLARE @Key as nvarchar(max); SELECT @Key=Value FROM @tbl_Keys WHERE id=@key_depth
IF @key_count >= 1
AND EXISTS( SELECT *
FROM keyednodes KN
WHERE KN.Value=@Key
and KN.ParentNode_Id is null
)
BEGIN
DECLARE @ValueExistsForFirstKey as bit; SET @ValueExistsForFirstKey=0;
--Load @Key's child nodes
INSERT INTO @ChildNodes
SELECT chld.Id, chld.Value, chld.ParentNode_Id
FROM keyednodes prnt
INNER JOIN keyednodes chld ON prnt.id=chld.ParentNode_id
WHERE prnt.value=@Key
And prnt.ParentNode_Id is null
-- Load first @Key's global Variables
INSERT INTO @Variable
SELECT GV.Value,GV.KeyedNodes_Id,KN.Value,1,GV.ReadOnly
FROM keyednodes KN
INNER JOIN GlobalVariables GV
on KN.Id=GV.KeyedNodes_Id
and KN.Value=@Key
and KN.ParentNode_Id is null
IF @@ROWCOUNT > 0
SET @ValueExistsForFirstKey=1
ELSE IF NOT EXISTS(SELECT * FROM @ChildNodes)
BEGIN
INSERT INTO @Variable SELECT -1,-1,-1,0,0 --Node exists but No value or children exists
GOTO ExitFROMProcedure
END
END
--====== the tree-traversal Loop ====
WHILE @key_depth <= @key_count
BEGIN
SET @key_depth=@key_depth+1
-- get the current Key
SELECT @Key=Value FROM @tbl_Keys WHERE id=@key_depth
--Does Key path exist?
IF NOT EXISTS(SELECT * FROM @ChildNodes WHERE Value=@Key) GOTO ExitFROMProcedure
SELECT @ParentId=Id FROM @ChildNodes WHERE Value=@Key
SET @KeyIsChild=1
delete @ChildNodes
INSERT INTO @ChildNodes
SELECT chld.Id, chld.Value, chld.ParentNode_Id
FROM keyednodes prnt
INNER JOIN keyednodes chld ON prnt.id=chld.ParentNode_id
WHERE chld.ParentNode_Id=@ParentId
END
--BREAK: jumps to here
if exists(SELECT * FROM @ChildNodes)
begin
INSERT INTO @Variable
SELECT GV.Value, CN.id, CN.Value, 0, GV.ReadOnly
FROM @ChildNodes CN
left join GlobalVariables GV ON CN.Id=GV.KeyedNodes_Id --children
union all
SELECT GV.Value, GV.KeyedNodes_Id, KN.Value, 1, GV.ReadOnly
FROM keyednodes KN
inner join GlobalVariables GV ON KN.Id=GV.KeyedNodes_Id and KN.Id=@ParentId--children
IF @@ROWCOUNT = 0
BEGIN
IF @key_count <> 1
OR NOT EXISTS (SELECT * FROM @Variable)
INSERT INTO @Variable SELECT -1,-1,-1,0,0
END
end
else
Begin
IF @KeyIsChild = 1
BEGIN
INSERT INTO @Variable
SELECT GV.Value, GV.KeyedNodes_Id, KN.Value, 1, GV.ReadOnly
FROM keyednodes KN
inner join GlobalVariables GV
ON KN.Id=GV.KeyedNodes_Id
and KN.Id=@ParentId --node
IF @@ROWCOUNT = 0
INSERT INTO @Variable SELECT -1,-1,-1,0,0 --Node exists but No value or children exists
END
End
----
ExitFromProcedure: --Not Found jumps here
SELECT KeyedNodes_Id, KeyNodeValue, Value, IsParent, Isnull(IsReadOnly,0) as IsReadOnly
FROM @Variable
ORDER by IsParent desc, KeyNodeValue asc
IF(@TurnOnUpdation=1)
BEGIN
--update LastAccessDateTime
UPDATE GlobalVariables
SET LastAccessDateTime = getdate()
WHERE KeyedNodes_Id IN (SELECT KeyedNodes_Id FROM @Variable)
END
End
Go
答案 2 :(得分:0)
尝试将隔离级别设置为读取未提交以检查其是否存在阻塞问题
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED