我正在为类似博客的网站上的帖子创建一个投票系统。
每个帖子都有一个评级,存储在数据库中。用户可以单击upvote或downvote,数据库中的数字将增加或减少。简单的东西。
但我如何检查当前用户是否已经投票?
我最初的想法是在数据库中为每个帖子创建两个额外的列,“UsersUp”和“UsersDown”,存储已经投票并为每个帖子投票的用户名。我会检查用户名是否存在,如果存在,则会删除投票而不是添加其他投票。 我甚至可以完全删除“评级”列,并通过计算UsersUp中的用户名数量并减去UsersDown中的用户名数来实现相同的目的。
我是以错误的方式接近这个吗?
我不是要求任何代码,只是我如何实现这一目标的理论。
我需要用户不能上下两次投票,他们需要再次点击即可收回投票。
答案 0 :(得分:2)
您应该使用normalized database design。
您可能有一个User
表,根据问题,您有一个Post
表。
您可以使用第三个UserVote
表来表示用户,帖子和投票之间的关系。这允许精细跟踪投票的人,投票的时间和方式(VoteTypeId可以包含upvote或downvote的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
我们的序列变为:
虽然这听起来很复杂,并且锁定听起来像是瓶颈,但它实际上表现良好。几年前,我对这种设计进行了压力测试,很容易实现每秒2500次投票操作的吞吐量。
调用代码应确保: