【问题标题】:Check if user has voted on a post检查用户是否对帖子进行了投票
【发布时间】:2017-05-22 00:31:02
【问题描述】:

我正在为类似博客的网站上的帖子创建投票系统。

每个帖子都有一个评分,存储在数据库中。用户可以点击支持或反对,数据库中的数字会增加或减少。简单的东西。

但是如何检查当前用户是否已经投票?

我最初的想法是在数据库中为每个帖子创建两个额外的列,“UsersUp”和“UsersDown”,存储对每个帖子投赞成票和投反对票的用户名。我会检查用户名是否存在,如果存在,投票将被删除,而不是添加另一个投票。 我什至可以完全删除“评分”列并通过计算 UsersUp 中的用户名数量并减去 UsersDown 中的用户名数量来实现相同的效果。

我是否以错误的方式处理这个问题?

我不是要任何代码,只是要我如何实现这一点的理论。

我希望用户不能两次投赞成票或反对票,并且他们需要能够通过再次点击来撤回投票。

【问题讨论】:

  • 如果您想尽量减少执行连接和繁重查询(“简单的东西”)的需要,您应该锁定已登录用户已投票的每个帖子。那么你的投票聚合应该确保在投票之前这个锁不存在,然后再进行+1-1
  • 我会在帖子表中还是在用户表中执行此操作?你可以说得更详细点吗?我会将用户名与帖子一起存储然后查询它们吗?
  • 您在正确的轨道上,但没有上下两列,您只需要一个位列。事实上,看看 Stackoverflow 是如何使用 DataExplorer data.stackexchange.com 的,它有 db 架构
  • 那么我如何将用户名“连接”到帖子 ID?我已经阅读了一对多数据模型,这是我需要的吗?我希望你明白我的困惑。我将在哪里/如何记录对帖子投票的人的用户名/ID/IP?另外,我不知道这个(链接),谢谢!

标签: c# database-design


【解决方案1】:

您应该使用normalized database design

您可能有一个User 表,根据问题,您有一个Post 表。

您可以使用第三个UserVote 表来表示用户、帖子和他们的投票之间的关系。这允许精确跟踪投票者、投票时间和投票方式(VoteTypeId 可以包含赞成或反对票的 ID)。

+--------+--------+------------+----------+
| UserId | PostId | VoteTypeId | VoteDate |
+--------+--------+------------+----------+
|    1   |    1   |      1     | 1/1/2017 |
+--------+--------+------------+----------+
|    2   |    1   |      1     | 2/1/2017 |
+--------+--------+------------+----------+
|    3   |    1   |      2     | 2/2/2017 |
+--------+--------+------------+----------+

这对数据进行建模。你应该如何从 c# 中调用它?

if( !HasVoted( userId, postId ) ){
    SaveVote( userId, postId, voteTypeId );
    UpdateVoteCount( postId, voteTypeId );
}

但是,这很容易出错。如果UpdateVoteCount() 失败了怎么办?

有多种方法可以解决这个问题,其中包括:

  • 在需要时汇总投票计数(不要存储它们)。
  • 将操作包装在事务中。
using( var scope = new TransactionScope() ){    
    if( !HasVoted( userId, postId ) ){
        SaveVote( userId, postId, voteTypeId );
        UpdateVoteCount( postId, voteTypeId );
    }

    scope.Complete();
}

阅读更多:TransactionScope

这有助于数据完整性,但代码中仍有race condition 允许系统被滥用。

我们需要锁定整个操作。实现此目的的一种方法是使用存储过程sp_getapplock,它允许您阻塞指定资源,直到锁的持有者释放它。

您可以使用单个命名锁,或者您可以锁定每个项目(如果对一个项目进行投票可能会产生更广泛的后果,例如声誉计算,这会很棘手)。

阅读更多:sp_getapplock

我们的序列变成:

  1. 获取命名锁。
  2. 开始交易。
  3. 检查用户是否尚未投票。
  4. 登记投票。
  5. 更新其他值(例如总票数、用户声誉增益)。
  6. 提交事务。
  7. 释放锁。

虽然这听起来很复杂,而且锁听起来会成为瓶颈,但它实际上表现良好。几年前我对该设计进行了压力测试,并轻松实现了每秒 2500 多个投票操作的吞吐量。

调用代码应确保:

  • 所取锁的超时时间很短
  • 即使抛出异常,事务也会提交/回滚。
  • 即使抛出异常,锁也会被释放。

【讨论】:

  • 哇,很好的答案。其中很多都超出了我的想象,但我一定会阅读您提供的链接。学习这些东西有时会让人很沮丧,因为事情总是比我想象的要难。任何建议(作为一个明显擅长这些东西的人)将不胜感激:) 你为什么选择保存投票计数然后更新计数?我假设 SaveVote 会写入数据库,UpdateVoteCount 会使用 AJAX 更新页面?
  • 啊,我上面问的问题在你回答的第二部分回答了,我刚开始忽略了。
  • 复杂但通常有人已经解决了这个问题和/或有一个工具可以使它更容易。知道要查找哪些术语会很有帮助,例如“标准化”。
  • 顺便说一句,您可以将整个保存过程编写为数据库中的单个存储过程。存储过程可以获取锁、创建事务并执行所有更新。然后,您可以根据需要从 c# 调用它。
  • 我看了一些关于规范化的视频,我理解了前三种规范化形式,非常感谢。关于锁和事务,我在这篇文章之前没有听说过这些,它们重要和必要吗?你对这些东西有更直观的学习帮助吗?
猜你喜欢
  • 1970-01-01
  • 2017-10-18
  • 2013-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多