两个相关问题:
我应该对SQL做出其他改进吗?有问题的数据是一张包含发票的表格,每张发票都绑定到客户端ID和客户特定的订单ID。这意味着发票ID和元组(客户ID,订单ID)必须是唯一的。
我的第一个版本是:
DROP TABLE IF EXISTS invoice;
CREATE TABLE invoice (
id int(11) AUTO_INCREMENT,
client_id int(11) NOT NULL,
order_id int(11) NOT NULL,
INDEX(id, client_id, order_id)
);
DELIMITER //
CREATE TRIGGER invoice_insert
BEFORE insert
ON invoice
FOR EACH ROW
BEGIN
SET @max_order_id = (SELECT IFNULL(MAX(order_id), 0) FROM invoice WHERE client_id = NEW.client_id);
SET NEW.order_id = @max_order_id + 1;
END//
DELIMITER ;
-- example use
INSERT INTO invoice (client_id) VALUES (1), (1), (2), (1), (2), (2), (2);
INSERT INTO invoice (client_id) VALUES (1), (1), (2), (1), (2), (2), (2);
SELECT * from invoice;
第二个版本是:
DROP TABLE IF EXISTS invoice, client;
CREATE TABLE invoice (
id int(11) AUTO_INCREMENT,
client_id int(11) NOT NULL,
order_id int(11) NOT NULL,
INDEX(id, client_id, order_id)
);
CREATE TABLE client (
id int(11) AUTO_INCREMENT,
max_order_id int(11) NOT NULL DEFAULT '0',
INDEX(id)
);
DELIMITER //
CREATE TRIGGER invoice_insert
BEFORE INSERT
ON invoice
FOR EACH ROW
BEGIN
SET @max_order_id = (SELECT max_order_id FROM client where id = NEW.client_id);
SET NEW.order_id = @max_order_id + 1;
UPDATE client SET max_order_id = NEW.order_id WHERE id = NEW.client_id;
END//
DELIMITER ;
-- example use
INSERT INTO client () VALUES (), (), ();
INSERT INTO invoice (client_id) VALUES (1), (1), (2), (1), (2), (2), (2);
INSERT INTO invoice (client_id) VALUES (1), (1), (2), (1), (2), (2), (2);
SELECT * from invoice;
SELECT * from client;
第二个版本的优点是它为每个客户维护一个单独的订单ID计数器,这使我可以删除条目而不会面临创建重复订单ID的危险。
答案 0 :(得分:0)
首先,我会质疑您从OrderId获得的价值。由于每个OrderId显然都是唯一分配给一张发票的,为什么不简单地使用发票的ID?
其次,即使触发器在事务中运行,这两种方法都不安全。例如,假设一个客户端具有最大OrderId = 27,当两个发票“同时”进入时。自动增量给它们ID 53和54说,然后启动触发器。
53的事务启动触发器并运行SELECT,因此得到OrderId 27.在它前进到触发器中的下一个语句之前,54的事务启动触发器并因此运行相同的触发器 - 因此也获得27。然后每个事务都完成其触发器,因此最终会出现重复的OrderIds。
该行为的一个缓解是触发器在发出select以读取更新值之前执行更新语句FIRST(因此获取独占锁)。还是那太可怕了。
最好是(a)使用上面提到的发票ID - 或者如果由于任何原因这是不可能的话,那么(b)有一个自动递增的ORDER表。