在insert hook之前同时插入记录时避免重复



我有一个插入前钩子,它从特定公司(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插入数据。

最新更新