【问题标题】:SQL Server logging row visits best practiceSQL Server 记录行访问最佳实践
【发布时间】:2020-02-11 04:20:09
【问题描述】:

我目前有一个文章数据库,它通过增加 page_load 上的“访问次数”计数器来跟踪一段时间内阅读次数最多的文章。当前的“访问”计数器是articles 表中的一列(见下文):

id | title  | description | visits | creation_date
---+--------+-------------+--------+-----------------
1  | test1  | test test.. | 10     | 2019-01-01
2  | test2  | test test.. | 20     | 2019-01-01

有时,我遇到了连接超时,并且我怀疑“访问”写入过程会出现死锁(如果并发用户一次增加同一行,则会锁定数据库)。我认为下面的场景是一种增强:

  1. 从表Articles 中删除Visits 计数器
  2. 创建一个包含两列的新表article_visitsarticle_iddate

文章

id | title | desc | creation_date
---+-------+------+---------------
1  | test1 | desd | 2019-01-01
2  | test1 | desd | 2019-01-01

article_visits

article_id | visit_date
-----------+----------------------
1          | 2019-01-01
1          | 2019-01-01
1          | 2019-01-01
1          | 2019-01-01
1          | 2019-01-01
1          | 2019-01-01
2          | 2019-01-01
2          | 2019-01-01
2          | 2019-01-01

作为替代选项,一旦触发新访问,我会在articles_visits 表中插入一个新行,以避免articles 表上的任何死锁。此解决方案将使articles_visits 表很快变大,但我认为表大小不是问题。

我想知道这是否是记录文章访问的正确方法,以及优化是否是比原始解决方案更好的选择。

【问题讨论】:

    标签: sql sql-server database-design deadlock


    【解决方案1】:

    这是记录文章访问的好方法。它更不容易(或根本不会)出现死锁,因为您基本上只是在追加新行。

    它更灵活。例如,您可以获得两个日期之间的访问次数。这可以在查询时定义。您可以存储准确的时间,因此确定是否有视图的时间偏好。

    缺点是查询性能。如果您经常需要计数,那么计算可能会很昂贵。

    如果这是一个问题,有多种可能的方法:

    • 定期汇总所有数据(例如数据)的过程。
    • 一种基于该期间的期间汇总数据的过程(例如每日汇总)。
    • 允许数据库保持数据最新的物化/索引视图。

    【讨论】:

    • 大多数情况下,在发送推送通知时会发生死锁(用户同时访问文章)。这可能是由于增加了“访问”计数器对吗?我想记录过去 7 天访问量最大的文章。您是否建议我创建一个“视图”来提取这些数据?
    • @RamiZebian 。 . .您的方法不适用于过去 7 天内访问量最大的文章。第 8 天,您需要删除第 1 天。 . .这变得复杂了。使用更详细的解决方案。然后只在需要时关注性能改进。
    • @Godon Linoff 我的意思是,我可以通过创建一个视图来获取文章,同时计算当前日期和访问日期之间的差异,同时按天分组,从而在过去 7 天内获得最多浏览量。唯一需要担心的是articles_visits 表的大小会变得非常大。这有关系吗?
    • @RamiZebian 。 . .根据您的用例,您可以按天对它进行分区并删除旧分区,从而限制大小。
    • @Godron Linoff 如果我将文章 ID 和访问次数保存在“article_visits”表的一行中,而不是在每次访问时插入新行,这是一个不错的选择吗?这样,我在单独的表上增加访问次数
    【解决方案2】:

    这当然是有效的,尽管您可能希望对数据库服务器需要多少额外的存储和内存负载进行一些范围界定。

    此外,我可能会为实际时间戳添加一个完整的 datetimedatetime2 列(除了当前日期列而不是它,因为您只想按日期进行聚合并拥有它value pre-computed 可以提高性能),也许还有一些其他的列,例如 IP Address 和 Referrer。然后您可以将这些数据用于其他目的,例如审核、跟踪引荐来源网址/广告客户的投资回报率等。

    【讨论】:

    • 感谢您的意见。表“article_visits”的数据库大小不会有问题吗?当触发页面访问时插入新行,这将迅速增加大小。
    • 可能是个问题,但你还不知道。这就是为什么我建议做一些范围界定工作。您可能会发现您想要继续,但您还需要添加一个数据库作业来归档数据,例如,每个月。不过,在您完成工作以衡量这将如何影响您的系统之前,您并不知道。
    【解决方案3】:

    我很想知道您为什么会遇到死锁。应该是数据库平台应该能够同时处理update tablename set field = field + 1 就好了。此处表或行将锁定然后释放,但时间不应长到导致死锁错误。

    如果您使用跨多个表的事务更新或锁定多个表,尤其是多个表,您可能会遇到死锁错误。如果您以不同的顺序执行它们。

    所以问题是......在您的原始代码中,当您执行更新语句时,您是否链接到多个表?解决方案可能很简单,只需将更新原子化到一张表即可。

    不过,我同意——你描述的表格是一个更实用的设计。

    【讨论】:

    • 感谢您的关注。确切地说,更新查询与您所说的相同:update tablename set field = field + 1 where id=@id。就我而言,如果我只使用一张表“文章”。在页面加载时,我执行获取文章详细信息的查询(SELECT title,desc from article where id=@id)并增加访问计数器。当多个用户同时访问该文章时,我遇到了连接超时问题。我唯一怀疑的是“访问”计数器,因为它会锁定表,直到计数器增加。
    • @RamiZebian 如果您按照您的描述进行选择然后更新可能会导致死锁 - 如果您只是将单个更新作为您的第一个语句,那么它不会(您也可以使用平台具体提示不要在 select 上锁定表,你应该没问题,但你可能会在更新中丢失计数。)
    • 在每次页面加载时,我都会在同一连接中执行 SELECT,然后执行 UPDATE。但是为什么会导致死锁呢?将访问保存在单独的表中是否可以解决问题,或者至少被认为是更好的选择?
    • @RamiZebian -- 我们可以在这里讨论更多 chat.stackoverflow.com/rooms/200850/help-with-question
    【解决方案4】:

    当前Articles 表不在Normalized form 中。

    我会说将visits 列放在Articles 表中不是正确的方法 De-Normalization.

    当前Articles 表不仅给您带来死锁问题,而且您无法获得这么多其他类型的报告。 Daily Visit Report, Weekly Visit Report.

    创建Article_visits 表是非常好的举措。 它会非常频繁地更新。

    我的Article_visits 设计

    article_visit_id |   article_id | visit_date           | visit_count
    -----------------+--------------+----------------------+----------------------
    1                |    1         | 2019-01-01           | 6
    2                |    2         | 2019-01-01           | 3
    

    这里Article_Visit_idint identity(1,1) 也是Clustered Index

    Create NonClustered Index NCI_Articleid_date ON Article_visits(article_id,visit_date)
    GO
    

    简而言之,在article_id,visit_date 上创建 CI 会很昂贵。

    如果该日期不存在该article 的记录,则插入visit_count 1 如果存在则更新visit_count,即增加1。

    1. 已标准化。
    2. 您可以创建任何类型的报告、当前需求 + 任何未来需求。
    3. 您可以按文章计数显示。查询非常简单且高效。
    4. 你可以得到周报,甚至得到年报都是如此简单,没有 Indexed View

    实际的表设计,

    Create Table Article(Articleid int identity(1,1) primary key
    ,title varchar(100) not null,Descriptions varchar(max) not null
     ,CreationDate Datetime2(0))
        GO
    
     Create Table Article_Visit(Article_VisitID int identity(1,1) primary key,Articleid int not null ,Visit_Date datetime2(0) not null,Visit_Count int not null) 
        GO
    
    --Create Trusted FK
        ALTER TABLE Article_Visit
        WITH NOCHECK
        ADD CONSTRAINT FK_Articleid FOREIGN KEY(Articleid) 
        REFERENCES Article(Articleid) NOT FOR REPLICATION;
        GO
    
    
        --Create NonClustered Index NCI_Articleid_Date on 
        -- Article_Visit(Articleid,Visit_Date)
        --Go
    
        Create NonClustered Index NCI_Articleid_Date1 on 
         Article_Visit(Visit_Date)include(Articleid)
        Go
    

    创建 Trusted FK 以获得 Index Seek Benefit(简而言之)。 我认为,NCI_Articleid_Date 不再需要,因为ArticleidTrusted FK

    Deadlock Issue: Trusted FK 也是为了克服死锁问题而创建的。 它经常由于糟糕的Application codeUN-Optimized Sql queryBad Table Design 而发生。除此之外还有其他一些正当的原因,比如处理Race Condition。这是相当DBA 的事情。如果死锁伤害太大,那么在解决上述原因之后,您可能需要Isolation Level

    很多死锁问题都是由 Sql server 自己自动处理的。

    DEADLOCK REASON网上有很多文章。

    我不认为桌子大小是个问题

    Table size 是大问题。两种设计中Deadlock 的机会非常非常少。但是您将始终面对Big Size 表中的其他demerit

    我告诉你再读几篇文章。

    我希望这是您的完全相同的真实表,具有相同的数据类型?

    两个表的插入/更新频率如何?

    哪个表会被更频繁地查询?

    同时使用每个表。

    只能最小化死锁,以免出现性能问题或事务问题。

    VisitoridArtcileid 之间有什么关系?

    【讨论】:

    • 这不会导致您在更新 visit_count 时创建的 Article_visits 表出现死锁吗?
    • 感谢您的编辑。如果你检查这个答案。我不知道受信任的 FK 可以克服死锁。另外,我完全同意桌子的设计。但是,如果您检查此答案,stackoverflow.com/a/58378589/6859050 发帖人说您可能会在另一张桌子上遇到死锁。这有多真实?
    • @RamiZebian ,不要认为只有设计才能帮助您消除死锁。死锁的机会只能通过最小化。阅读我的编辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-30
    相关资源
    最近更新 更多