【问题标题】:Primary key error Vs select Command主键错误与选择命令
【发布时间】:2011-04-13 07:16:52
【问题描述】:

我正在创建一个应用程序作为 URL 计数器。 我创建了一个表来存储 url 及其计数。

CREATE TABLE [dbo].[tblurlcounter](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [type] [varchar](500) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [count] [bigint] NULL,
 CONSTRAINT [PK_tblurlcounter] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

我创建了一个存储过程来在表中插入/更新 url。 意味着当使用存储过程将值“URL”插入到该表中时,我会检查它是否存在然后更新它的计数,如果不存在则将其插入到 count=1 的表中。

我的存储过程是这样的:

declare @count int

select @count= [count] from tblurlcounter where [type] = @type
if @count > 0
begin
update tblurlcounter set [count]=@count + 1 where [type] = @type
select @count + 1

end
else 
begin
INSERT INTO [dbcounter].[dbo].[tblurlcounter]
           ([type]
           ,[count])
     VALUES
           (@type
           ,1)
end

此应用程序将在一分钟内获得大约 80,000 到 100,000 次点击。所以我希望我的存储过程应该以良好的速度执行操作。我的意思是我的解决方案应该优化。

有人建议我更改我的表并创建其名为“type”(我用来存储 url)的文件作为主键,在存储过程中我应该首先尝试插入记录,如果它抛出错误然后在下一行检查错误并执行更新操作。

所以我很困惑哪个会更快,主键错误方法或者我应该使用选择查询并基于选择的结果我应该执行插入/更新操作

现在我需要专家的建议,哪种方法是正确的,如果有其他好的方法可用,请给我建议。

谢谢

【问题讨论】:

  • 你的代码当前有一个竞争条件(不使用事务并且使用比默认更强的隔离):两次执行都可以观察到计数为 0,因此尝试插入。

标签: sql sql-server-2005 stored-procedures query-optimization


【解决方案1】:

对于这种负载,您需要有点聪明。我以前发过这个

基本上,不要先测试:尝试 INSERT。如果失败,运行更新

https://stackoverflow.com/search?q=user%3A27535+JFDI

【讨论】:

  • @gbn:因为我的名为“type”的文件长度为 500。我应该把它设为主键吗?
  • 500 可能对于索引来说太大了,也许创建另一个列来保存类型列的哈希值和索引?
  • @Magnus - 每个索引键的最大字节数 = 900,它是 varchar,而不是 nvarchar,所以应该没问题。
  • @Damien_The_Unbeliever 如果有这么大的聚集索引,碎片会不会很大?
  • @Magnus - 我不建议将它用于 聚集 索引,不。
【解决方案2】:

您可以尝试进行更新,如果它不存在,则不会更新任何行,您可以通过@@rowcount 进行检查。如果没有,那么您可以添加它,否则该值已经增加。您不需要 @count 变量,因为您必须锁定该行,以便在分配它之后但在更新表之前没有任何东西可以更改值。

update tblurlcounter set [count] = [count] + 1 where [type] = @type

if @@rowcount = 0 
begin
  insert into tblurlcounter 
  ([type],[count])
  values
  (@type, 1)
end

【讨论】:

  • 这也遭受了与我在评论中提到的类似的竞争 - 当类型不存在时,两个执行可以运行更新,然后都尝试插入。
【解决方案3】:

当您执行多个语句时,您的方法将无法正常工作。即,在这条线之间发生:

select @count = [count] from tblurlcounter where [type] = @type

并且您的 INSERT 或 UPDATE 实际正在执行,存储过程的单独执行也可能会添加一行,因此您最终可能会几乎同时发生两个 INSERT。

相反,试试这个:

INSERT INTO [dbcounter].[dbo].[tblurlcounter]
           ([type]
           ,[count])
     VALUES
           (@type
           ,0)
WHERE NOT EXISTS(select 1 from tblurlcounter where [type] = @type)

UPDATE tblurlcounter SET [count]=[count] + 1 where [type] = @type

如果匹配的行不存在,这将添加一个新行,将 INSERT 与存在检查相结合。在知道已经有一行要更新的情况下,可以安全地运行更新语句。

你还真的需要在你的类型列上建立一个索引。

【讨论】:

  • 您的解决方案只需要一次查找时需要两次查找。但我同意类型列的索引
  • 插入然后更新是避免竞争的正确顺序。我可能会插入 (@type,1) 并在插入之后退出 @@ROWCOUNT=1,但除此之外,这似乎是 2005 年解决方案的最佳选择。
【解决方案4】:

这篇文章很好地解释了如果存在更新与更新如果不存在插入方法。

显然由于表锁的原因,首先进行选择以检查它是否存在的成本较低。这样我们就可以避免使用更新,它会比选择锁定更多的数据。

http://weblogs.sqlteam.com/mladenp/archive/2007/07/30/60273.aspx

还有,

你可以使用:

如果存在 (select * from tblurlcounter where [type] = @type)

【讨论】:

  • 如果您的主键也是您的集群键(默认情况下),这是一个可怕的想法,主要有两个原因:type 列都是可变宽度(对集群键不利),而且太宽了。此外,type 列可能会改变 - 对于主键来说也是个坏主意!
猜你喜欢
  • 2011-07-12
  • 1970-01-01
  • 1970-01-01
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-12
  • 2022-10-04
相关资源
最近更新 更多