我有一个插入前钩子,它从特定公司(group by语句)的最新条目中增加数字batch_id
。
但是,如果同时插入记录,即使在单独的事务中,也会得到重复的数字。
是否有任何替代方案或解决方案来避免重复的发生?
这是我的钩子
CREATE OR replace FUNCTION before_orders_insert_trigger
() returns TRIGGER
AS
$$
BEGIN
new.batch_id :=
(
SELECT COALESCE(max(batch_id), 0) + 1
FROM orders
WHERE company_id = new.company_id
GROUP BY company_id limit 1);
RETURN new;
END;
$$ language 'plpgsql';
CREATE TRIGGER before_orders_insert_trigger before
INSERT
ON orders FOR each row EXECUTE PROCEDURE before_orders_insert_trigger();
PostgreSQL的传统解决方案是使用SEQUENCE:
CREATE SEQUENCE s;
INSERT INTO orders (batch_id, ...) VALUES (NEXTVAL(s), ...);
或者使用SERIAL作为主键的列类型,这会创建一个内部序列,并确保在INSERT期间使用它。
CREATE TABLE orders (
batch_id SERIAL PRIMARY KEY,
...
);
在MySQL中,它们没有序列对象。它们通过在列上使用AUTO_INCREMENT选项来实现类似的功能。
CREATE TABLE orders (
batch_id INT AUTO_INCREMENT PRIMARY KEY,
...
);
使用sequence或auto_increment比您的方法更可取,因为它避免了您所识别的竞争条件,当插入并发运行时会导致重复的值。它通过在增加序列时使用全局锁来实现这一点,因此不能为两个并发请求分配相同的值。
阅读我链接到的文档页以获取更多详细信息。
你的评论:
我明白了,你想要一个每组重新开始的序列。
这样做的唯一方法是锁定给定批处理的行集。然后计算先前分配给同一组的最大batch_id,增加该值,并在INSERT语句中使用它。然后释放锁
如果有并发请求,它们必须等待锁。一旦锁被授予给它们,它们就可以查询新的max batch_id,并确保前一个请求已经完成了它们的插入。
MySQL过时的MyISAM存储引擎支持一个特性,允许自动增量列是复合主键的第二列。因此,它为第一列的每个不同的值重新赋值。
CREATE TABLE myisam_orders (
company_id INT NOT NULL,
batch_id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY (company_id, batch_id)
) ENGINE=MyISAM;
唯一可行的方法是MyISAM对写操作进行表级锁定。对表的任何写操作都会阻塞其他请求,因此拥有表锁的人可以自由地计算给定company_id的最大batch_id,而不会导致竞争条件。INSERT操作完成后,表锁被释放。
默认的存储引擎InnoDB不支持相同的行为。MySQL用户希望有多个客户端并发插入。表级锁是吞吐量的瓶颈,因为它使所有客户端排队访问。
如果你有一个高度并发的应用程序,并且不希望客户端因为表锁而相互阻塞,那么你可能不得不妥协,不要为每个公司重新设置batch_id。
在MySQL中,你可以设置你的batch_id和其他字段,你不希望有一个唯一的键重复,然后使用INSERT IGNORE INTO插入数据。