我有一张表用于跟踪客户使用情况。我跟踪用户每天的点击次数,所以我有一个看起来像这样的表:
CustID (uniqueidentifier, not null)
UseDate (smalldatetime, not null)
NumHits (smallint, not null)
我在存储过程中使用此SQL为今天插入行(如果需要),或者为今天增加计数器:
declare @today datetime
set @today = getdate()
/* Try to bump it by one if an entry for today exists */
if (
select count(*) from CustomerUsage
where CustID = @cust_guid and year(UseDate) = year(@today) and month(UseDate) = month(@today) and day(UseDate) = day(@today)
) = 0
insert into CustomerUsage (CustID, UseDate, NumHits) values (@cust_guid, getdate(), 1)
else
update CustomerUsage set NumHits = NumHits + 1
where CustID = @cust_guid and year(UseDate) = year(@today) and month(UseDate) = month(@today) and day(UseDate) = day(@today)
有更好的方法吗?
此外,我想迁移到多个Web服务器可以为同一个客户调用存储过程的环境。我认为这段代码可能容易受到多线程问题的影响。
谢谢!
答案 0 :(得分:1)
首先,您可以使用以下方法将当前日期和时间转换为仅日期值:
DateAdd(d, DateDiff(d, 0, CURRENT_TIMESTAMP), 0)
一种解决方案是使用两个语句。但是,为了防止Phantom Read,您需要将语句包装在事务中并将隔离级别设置为Serializable或Snapshot(如果已实现)。显然,这会损害并发性,但会确保一致性。
Declare @Today datetime
Set @Today = DateAdd(d, DateDiff(d, 0, CURRENT_TIMESTAMP), 0)
Set Transaction Isolation Level Serializable;
Set Xact_Abort On;
Begin Tran;
Update CustomerUsage
Set NumHits = NumHits + 1
Where CustID = @cust_guid
And UseDate >= @Today
And UseDate < DateAdd(d, 1, @Today)
Insert CustomerUsage( CustId, UseDate, NumHits )
Select @CustId, CURRENT_TIMESTAMP, 1
From ( Select 1 As Value ) As Z
Where Not Exists (
Select 1
From Customer_Usage
Where CustID = @cust_guid
And UseDate >= @Today
And UseDate < DateAdd(d, 1, @Today)
)
Commit Tran;
由于每个语句本身都是一个事务,因此可以避免同时调用的问题。如果您使用的是SQL Server 2008,则可以使用Date
数据类型和Merge
语句来实现同样的目标:
Declare @Today date
Set @Today = Cast( CURRENT_TIMESTAMP As date )
Merge CustomerUsage As target
Using (
Select CustId, UseDate
From CustomerUsage
Where CustID = @cust_guid
And UseDate >= @Today
And UseDate < DateAdd(d, 1, @Today)
Union All
Select @cust_guid, CURRENT_TIMESTAMP
From ( Select 1 As Value ) As Z
Where Not Exists (
Select 1
From CustomerUsage
Where CustID = @cust_guid
And UseDate >= @Today
And UseDate < DateAdd(d, 1, @Today)
)
) As source
On source.CustID = target.CustID
And source.UseDate = target.UseDate
When Matched Then
Update Set NumHits = NumHits + 1
When Not Matched Then
Insert ( CustId, UseDate, NumHits )
Values( source.CustId, source.UseDate, 1 )
最终添加
虽然我意识到已经选择了答案,但我发现有一个更好的解决方案。无需每天更新点击计数器。只需执行一个记录发生命中的插入(即只有一个不跟踪NumHits的插入),并在报告端,汇总“每天命中”。
答案 1 :(得分:0)
您有可能发生以下情况:
此外,您存储完整日期和时间然后使用函数检查您今天是否有现有行看起来很奇怪。如果您需要记录行适用的日期以及添加行的时间,我可能会将其分成两列,以便您可以在第一列中使用索引。这些函数不是sargable,这意味着查询优化器将无法使用索引来查找匹配的行。
要解决并发问题,我会:
第二点在普通编程语言中看起来像这样,你必须为SQL重写:
try
{
Insert ...
}
catch (DuplicateKeyException)
{
Update ...
}
答案 2 :(得分:0)
你可以这样做。
declare @Today smalldatetime = dateadd(d, datediff(d, 0, getdate()), 0)
declare @Tomorrow smalldatetime = dateadd(d, 1, @Today)
insert into CustomerUsage(CustId, UseDate, NumHits )
select Data.CustID, Data.UseDate, Data.NumHits
from (select
@cust_guid as CustID,
getdate() as UseDate,
0 as NumHits) as Data
where not exists (select *
from CustomerUsage with (updlock, serializable)
where
CustID = @cust_guid and
UseDate >= @Today and
UseDate < @Tomorrow)
update CustomerUsage
set NumHits = NumHits + 1
where
CustID = @cust_guid and
UseDate >= @Today and
UseDate < @Tomorrow
首先使用NumHits = 0
插入一行。插入检查CustID
上是否存在UseDate
的行,并且仅在必要时插入。
插入后总是将NumHits
增加1。
这与Thomas非合并答案基本相同,但我在更新前进行了插入。
编辑1 在插入语句where not exists
部分添加了表提示。需要serializable
来防止主键约束违规,并且需要updlock
来避免死锁。