下面的代码是一个简单的预订系统,他们可以预订/注册。真的没有。我遇到的问题是,如果有多个用户在同一时间范围内注册,则ListAppend
不会因每个发言而锁定。因此,两者都会签约。但是,如果同时烟雾消失,则可能无法注册用户。
我确定这是一个ListAppend
问题,因此他们同时访问了该网站,但附加内容会更新并错过某人。
Kinda喜欢那种现场拍卖,您认为自己是赢家,但最终却不是。
有没有简单的解决方法?使用ColdFusion 2016,MS Access。
<cfset signedup = #add.signedup# + 1>
<cfset temp = ValueList(add.userssigned)>
<cfset temp2 = ListAppend(Temp, "#session.demshinuser_id#", ",")>
<cfquery name=Update DATASOURCE="#ds#">
update shindates
set
signedup = #signedup#,
userssigned = '#temp2#'
where shinid = '#shinid#'
</cfquery>
答案 0 :(得分:3)
问题在于您当前的代码受比赛条件的影响。当多个线程尝试同时读取和并写入共享资源时,只有其中一个可以获胜。 当“约翰”和“鲍勃”尝试在同一时间进行注册时,会发生以下情况:
与此同时,Bob去注册了。由于John的线程尚未完成,该查询再次报告只有一个用户注册:“ Jane ”。
鲍勃的话题获胜,并清除了约翰的话题中的更改。
锁定资源-一次只能有一个线程可以读取和/或修改资源-是解决此问题的唯一可靠方法。
就个人而言,建议您重新设计表格以避免存储列表。除了加剧这种类型的问题之外,数据库根本不是为列表设计的。当数据存储在单独的行中时,它们效果最佳。在可能的情况下,存储列表会导致许多其他问题,例如数据完整性问题和不合格的查询性能。最好创建一个单独的表来将注册存储为单独的记录。
说您的应用程序记录了学生的入学情况,您将有3张表:
CREATE TABLE Course (courseId int identity, courseName varchar(100), ....)
CREATE TABLE Student (studentId int identity, userName varchar(100), ....)
CREATE TABLE Enrollment (courseId int, studentId int)
在添加新记录之前,请检查“注册”表以查看用户是否已经注册,如果已注册,则拒绝该记录。我不使用Access,但是类似这种结构的东西会起作用。用cfqueryparam替换@variables。
SELECT COUNT(*) AS EnrollmentsFound
FROM Enrollment
WHERE courseId = @courseId
AND studentId = @studentId
要确定一门课程是否已满,请执行COUNT(*)
。如果计数小于最大容量,则允许插入。否则,拒绝它。使用SQL Server,您可以执行以下操作-进行适当的lock或可序列化的事务。。如果没有其中之一,它仍然会受到竞争条件的影响。当然,机会的窗口比您当前的代码要小,但它仍然存在。
if ((select count(*) from Enrollment where courseId = @courseId) < @maxCapacity)
begin
insert into Enrollment (courseId, studentId )
values (@courseId, @studentId)
end
不幸的是,MS Access可能不支持“如果存在...”,锁定提示或事务。因此,考虑升级到SQL Server或MySQL,这两种方法都提供了更强大的选项。如果您绝对不能升级,则以上内容可能会被重写为两个单独的查询和一个cfif / cfelse。 但是,您仍然必须应用某种锁定方式来避免出现竞争状况。锁定资源-一次只能有一个线程可以读取或修改资源-是解决问题的唯一可靠方法。
尽管我讨厌这么说,但是由于MS Access不支持事务AFAIK,因此您可以尝试使用专有的“命名” CFLOCK而不是可序列化的事务。可以,但是坦率地说,它不能替代真实的数据库事务。再次重申,如果可能的话,最好将其升级到更可靠的数据库,从而为IMO提供适当的事务支持。
<!--- Exclusive lock to prevent race conditions / Access ONLY --->
<cflock name="Student_Enrollment_Add" type="exclusive" timeout="5000">
<cfquery name="getEnrollments" ...>
SELECT COUNT(*) AS EnrollmentsFound
FROM Enrollment
WHERE courseId = <cfqueryparam value="#form.courseId#" cfsqltype="cf_sql_integer">
</cfquery>
<cfif getEnrollments.EnrollmentsFound lt maxCapacity>
<cfquery ...>
INSERT INTO Enrollment ( .... )
VALUES ( .... )
</cfquery>
</cfif>
</cflock>