我的任务是创建这个组织注册系统。我决定用MySQL和PHP来做。表orgs
中的每个组织都有max_members
列,并且具有唯一ID org_id
。表students
中的每位学生都有一个org
列。每次学生加入组织时,他的org
列都等同于该组织的org_id
。
当有人点击组织页面上的加入时,就会执行PHP文件。
在PHP文件中,查询检索org
等于所加入组织的org_id
的学生总数。
$query = "SELECT COUNT(student_id) FROM students WHERE org = '$org_id'";
还会从orgs
表中检索最大成员。
$query = "SELECT max_members FROM orgs WHERE org_id = '$org_id'";
所以我有变量$total_members
和$max_members
。基本if语句检查$total_members < $max_members
,然后更新学生org
等于org_id
。如果没有,那么它什么都不做,并通知学生该组织已满。
如果发生这种情况,我主要担心的是什么:
Org A only has one slot left. 29/30 members.
Student A clicks join on Org A (and at the same time)
Student B clicks join on Org A
Student A retrieves data: There is one slot left
Student B retrieves data: There is one slot left
Student A's org = Org A's org_id
Student B's org = Org A's org_id
After the scripts have executed, Org A will show up with 31/30 members
这可能发生吗?如果是,我该如何避免呢?
我已经考虑过使用像这样的MySQL变量:
SET @org_id = 'what-ever-org';
SELECT @total_members := COUNT(student_id) FROM students WHERE org_main = @org_id;
SELECT @max_members := max_members FROM orgs WHERE org_id = @org_id;
UPDATE students SET org_main = IF(@total_members < @max_members, @org_id, '') WHERE student_id = 99999;
但我不知道它是否会有所作为。
行锁定不适用于我的情况。我认为。我喜欢被证明是错的。
上面编写的代码是原始代码的简化版本。原始代码包括检查注册日期,组织日等,但是,这些事情与问题无关。
答案 0 :(得分:2)
你所描述的通常被称为竞争条件。它发生,因为您在数据库上执行两个非原子操作。为避免这种情况,您需要使用事务,以确保数据库服务器可以防止此类干扰。另一种方法是在更新触发器之前使用&#34;#34;。
当你正在使用MySQL时,你必须确保你的表运行的数据库引擎是InnoDB,因为MyISAM没有交易。在执行SELECT
之前,您需要开始交易。将START TRANSACTION
手动发送到数据库或使用适当的PHP实现,例如PDO(PDO::beginTransaction())。
在正在运行的事务中,您可以在FOR UPDATE
语句中使用后缀SELECT
,这将锁定查询选择的行:
SELECT COUNT(student_id) FROM students WHERE org = :orgId FOR UPDATE
在您的UPDATE
语句之后,您可以提交事务,该事务将永久性地将更改写入数据库并删除锁定。
如果您希望同时发生大量请求,请注意锁定可能会导致响应延迟,因为数据库可能会等待释放锁定。
您还可以在数据库上创建触发器,而不是使用事务,该触发器将在数据库上执行更新之前运行。在此触发器中,您可以测试是否已超过最大学生数,如果是这样,则会抛出错误。但是,这可能是一种非常具有挑战性的方法,特别是如果要检查的值取决于UPDATE
语句中的某些内容。如果在数据库级别实现这种逻辑是个好主意,那么这也是值得商榷的。
答案 1 :(得分:0)
有两种方法,在PHP中使用synchronized函数执行此操作。但是如果你想在MySQL中实现所有逻辑(我喜欢这种方法),请使用存储过程。
创建一个存储过程(不完全是):
CREATE PROCEDURE join_org(stu_id int, org_id, int, OUT success int)
DECLARE total_members int;
DECLARE max_members_Allowed;
SELECT COUNT(student_id) INTO total_members FROM students WHERE org = 'org_id';
SELECT max_members INTO max_members_Allowed FROM orgs WHERE org_id = 'org_id';
IF(max_members_Allowed > total_members) Then
UPDATE student SET orgid='org_id';
SET success = 1;
ELSE
SET success = 0;
END IF;
然后在PHP代码中注册这个名为'success'的变量,以指示成功或失败。用户单击加入时调用此过程。