我有一个简单的电力系统,因为用户可以互相借用:
mysql_query("INSERT INTO power (sender, receiver, amount)
VALUES ('$sender', '$receiver', '$amount')");
mysql_query("UPDATE users SET power=power-$amount WHERE user_id='$sender'");
mysql_query("UPDATE users SET power=power+$amount WHERE user_id='$receiver'");
在运行上述一组查询之前,我通过 SELECT
查询检查发件人是否有足够的信用来转账。
问题1:我认为,最好将这四个查询都放在一个Transaction
中;因为 InnoDB ACID 将保证更安全的性能。这样做的最佳交易是什么?
问题 2: 幂为 unsigned int
。如果万一(即使不太可能),用户没有足够的信用 power-$amount
不会设置为 0;相反,它将循环通过 int() 的最大值,即 4294967295。这意味着用户将被授予几乎无限的权力(信用)。
最佳答案
首先,不要理会这个:
I check if the sender has enough credit to transfer by a SELECT query before running the above-mentioned set of queries.
这会让您面临无论如何都必须处理的竞争条件。相反,在您的数据库中设置约束以使其不可能进入无效状态。
Issue 2: power is
unsigned int
.
也不要那样做,没必要。一个 4 字节的有符号整数应该在正面有足够的空间;如果没有,您可以切换到带符号的 8 字节整数。您需要一个有符号值的原因是它使完整性检查变得相当容易:如果余额低于零,则说明有问题。如果您使用无符号值,则必须保留 4294967295-n
(对于某些 n
)检测下溢和溢出而不是简单的 < 0
.
就约束数据而言,通常您会使用如下 CHECK 约束:
check (power >= 0)
但 MySQL 不支持 CHECK 约束。但是,您可以编写一个 BEFORE INSERT OR UPDATE 触发器来检查 new.power >= 0
和 raise an exception如果不是。
现在你有一个 users
不允许无效的表 power
值,所以我们完成了问题 2。
关于问题 1:交易。是的,您绝对想使用 transaction为转移。您需要这样的序列:
-
start transaction
-
INSERT INTO power (sender, receiver, amount) VALUES ('$sender', '$receiver', '$amount')
-
UPDATE users SET power=power-$amount WHERE user_id='$sender'
-
UPDATE users SET power=power+$amount WHERE user_id='$receiver'
- “检查触发器”是否有任何错误?
- 如果有则发送
rollback
到数据库并告诉用户出了什么问题。 - 如果没有,请发送
commit
到数据库。
- 如果有则发送
交易将确保所有三个操作作为一个整体要么成功要么失败,并且 CHECK 约束(作为触发器实现)确保没有人可以放弃比他们拥有的更多的权力。
关于php - 用户间信用转移的Mysql事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9323764/