我正在设计一个提供优惠券(序列号)的网站。所有凭证代码都生成并保存在这样的表中,而不是在线生成:
v_id voucher status (used?)
-------------------------------------------------------
1 abcdefgabcdefg1 0
2 abcdefgabcdefg2 0
3 abcdefgabcdefg3 0
4 abcdefgabcdefg4 0
我应该在成功付款后向最终用户显示未获取的(状态:0)凭证,但我不知道如何执行此操作,因为此网站可能具有高流量,因此不止一个人可能在相同的时间内访问网站,所以例如我不能只使用:
SELECT * FROM table WHERE status=0 LIMIT 0,1;
然后更新它:
UPDATE table SET status=1 WHERE v_id=....
因为两个人可能同时访问,所以我们为它们提供了v_id = 1序列码(相同的串行代码)。
什么是确保我显示唯一序列码的最佳方法?
答案 0 :(得分:2)
此答案中的代码尚未经过测试。应调整每个列名和表名以适合您的代码。发布此答案是为了说明如何处理并发性和唯一性。
确保一张优惠券只能使用一次
我们将创建另一个表格,我们将填写已使用过的优惠券。
提到的表格将包含foreign key
到voucher
表格,通过其主键引用特定凭证,并且它将具有相同的列unique
。这使得优惠券100%安全,不会被多次使用
CREATE TABLE vouchers_uised (
id int unsigned not null auto_increment,
voucher_id int unsigned not null,
unique(voucher_id),
foreign key (voucher_id) references `vouchers` (`id`) on delete cascade
) ENGINE = InnoDB;
使用优惠券时,您必须插入vouchers_used
表。它是一个简单的表,所以你需要做的就是插入一个列。
INSERT INTO vouchers_used SET voucher_id = 1;
如果成功,PHP会将查询/预准备语句的结果解释为true
失败将向SQLSTATE
发出代码23000
的信号DUPLICATE KEY
,代码为PDO
。如果在异常模式下使用$e->getCode() == '23000
,则捕获异常并检查JOIN
;`将允许您检查查询是否因为已使用凭证而失败
有两种方法可以处理可用的优惠券。
首先是尝试使用已使用过的凭证vouchers_used
使用is_available
表,并过滤掉vouchers
表中不存在的凭证
第二个是使用vouchers
表上vouchers_used
的标记来帮助自己。每次将记录添加到DELIMITER $$
CREATE
TRIGGER `vouchers_used_after_insert` AFTER INSERT
ON `vouchers_used`
FOR EACH ROW BEGIN
UPDATE vouchers SET is_used = 1 WHERE id = NEW.voucher_id;
END$$
DELIMITER ;
表时,您都可以使用一个更新SELECT * FROM vouchers WHERE is_used = 0;
表的触发器来帮助自己。这样你就不必过多担心PHP代码中的逻辑了
触发码:
unique
要显示前端可用的优惠券,只需一个简单的查询即可:
int delete(const char *data)
{
struct node *temp = h;
if (h == NULL) return;
while (temp->next != NULL)
{
if (strcmp(temp->word, data) == 0)
{
if (temp->prev != NULL)
{
temp->prev->next = temp->next;
}
if (temp->next != NULL)
{
temp->next->prev = temp->prev;
}
if (h == temp)
{
h = temp->next;
}
free(temp);
return;
}
temp = temp->next;
}
}
如果凭证在平均时间内被使用, Call to a member function setBody() on null
约束将阻止用户对数据完整性做任何不好的事情。您的工作是相应地解释来自数据库的消息,并指示用户同时使用凭证。
答案 1 :(得分:0)
如果您的问题是"我如何解决并发问题,因为在我检查(检索当前写入的数据)的那一刻,我做出决定的那一刻(不允许/允许使用优惠券)以及您实际使用优惠券的时刻(如果您允许的话)"。
使用交易。 检查以下内容以确保正确设置数据库(连接)以使用事务: http://dev.mysql.com/doc/refman/5.7/en/commit.html
同样重要的是,正确的隔离级别可以起作用: http://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
SQL代码:
START TRANSACTION;
SELECT v_id, status
FROM table
WHERE voucher = 'voucher code';
UPDATE table
SET Status = -1 #reserve
WHERE voucher = 'voucher code'
AND Status = 0;
#handle logic to either allow or decline claim
UPDATE table
SET Status = 1 #claim
WHERE voucher = 'voucher code'
AND Status = -1;
if (l_allow) then
commit;
else
rollback;
end if;
或者,您可以执行更新选择。 这会在您选择的同一查询中更新数据库中的值。
要检索结果,您可以使用局部变量,这样您就不会再次请求同一个表。
UPDATE (table a, (
SELECT i.ID, i.Col1, @Col2 := i.Col2
FROM other_table) i
) TableSel
)
SET a.Col1 = TableSel.Col1,
a.Col2 = 1
FROM (
WHERE
i.ID = table.ID;
SELECT @Col2;
或者使用临时状态进行更新并选择相应的记录以查看是否成功。
UPDATE table
SET Status = -1 #reserve
WHERE voucher = 'voucher code'
AND Status = 0;
SELECT v_id, status
FROM table
WHERE voucher = 'voucher code';
您可以处理代码中的最后一位 OR
SELECT if(ifnull(status, 0) = -1, 1, 0) as IsClaimed
FROM table
WHERE voucher = 'voucher code';
但是,现在你必须单独声明(SET status = 1) 如果您想要撤消此操作,请将其取消设置。
更新(10-02-2017)
要清楚,你想要在这里实现的是防止所述表上的任何其他交互,因此你可以防止错误地读出当前状态并防止改变,直到你想出你是否想做某事然后做实际的更新/插入/删除。
使用隔离级别/事务,您可以实现锁定表,这将使其他事务等待,直到契约完成。
您也可以直接硬锁表: http://www.mysqltutorial.org/mysql-table-locking/
当您提交/回滚事务,关闭连接或连接丢失(如果您的数据库服务器已相应设置)时,事务过期会更加棘手。
当您手动锁定表并且没有实现某些方法来定期检查锁定是否保持开启时,您将在忘记取消锁定时停止数据库操作(在您的脚本/函数/存储过程)或脚本/函数中途失败并且实际上没有到达解锁表子句。
除此之外的其他任何操作都不建议使用此解决方案:数据暂存,大/完全数据库/表传输,大型聚合/计算(例如公司报告)和绝对必须的数据集(1个或多个表)完整+连贯使用(从2017年1月2日开始填充感官数据表时没有任何意义,因为它仍然需要完成以便进行适当的增量)。
所以当我能够使用正确的隔离级别时,我坚持使用函数/存储过程和事务处理。
如果您的问题是"我如何确保优惠券代码是唯一的"那么你应该在你的凭证栏上放一个唯一的索引。
除非您打算重复使用凭证代码,否则您应该这样做,您需要使用以下代码进行分区:启用/禁用状态,开始/结束日期或类似广告系列。
在最后一种情况下,您可以通过将两个字段添加到唯一索引来确保唯一性。