PHP SilverStripe ORM:重复键值违反了数据对象写入的唯一约束

PHP SilverStripe ORM:重复键值违反了数据对象写入的唯一约束,php,postgresql,silverstripe,Php,Postgresql,Silverstripe,我的网站上有一个功能,可以将一组值快速保存到相同的DataObject类型。大多数时候都没问题,但偶尔我会出错 错误:重复的键值违反唯一约束 阅读我看到的文档: SilverStripe不使用数据库的内置自动编号系统。相反,它将通过向当前最大ID添加1来生成新ID 在前面查看代码时,它看起来像是从主键中检索max number,插入具有该ID的记录,然后设置DataObject的值并再次写入。在我的负载平衡环境中,当发送这些多个条目时,我相信插入是使用相同的主键进行的,因此会出现错误 就我所知,

我的网站上有一个功能,可以将一组值快速保存到相同的
DataObject
类型。大多数时候都没问题,但偶尔我会出错

错误:重复的键值违反唯一约束

阅读我看到的文档:

SilverStripe不使用数据库的内置自动编号系统。相反,它将通过向当前最大ID添加1来生成新ID

在前面查看代码时,它看起来像是从主键中检索max number,插入具有该ID的记录,然后设置DataObject的值并再次写入。在我的负载平衡环境中,当发送这些多个条目时,我相信插入是使用相同的主键进行的,因此会出现错误

就我所知,这是一个我无法回避的问题。从other和doco中,我无法设置复合主键。我能想到的唯一一件事就是为create运行一个自定义sql,它使用DB内置的自动编号系统

有没有更好的方法来处理这个错误,或者我可以设置一个复合主键

编辑

完全错误是

Query failed: ERROR: duplicate key value violates unique constraint 'TABLE_pkey'
DETAIL: Key ('ID')=(136) already exists.
声明如下:

INSERT INTO "TABLE" ("ClassName", "Name", "MemberID", "OtherTabeID", "Value", "LastEdited", "Created", "ID") VALUES ($1, $2, $3, $4, $5, $6, $7, $8),Array) 
我读这篇文章是因为它是从先前确定的值插入ID,而不是依赖DB自动增量。对吗

编辑2

通过查看日志,似乎先使用创建的
字段执行
插入
,然后执行select语句以获取
ID

SELECT last_value FROM "TABLENAME_ID_seq"
然后执行
更新
,并保存其他详细信息

我觉得这可能是一种竞争条件,会导致保存到不正确的行,但不会导致我目前所经历的情况。理想情况下,任何
INSERT
都会有一个
返回的“ID”
,用于update命令

编辑3

上面的过程与我的堆栈跟踪相反,堆栈跟踪显示插入包含的不仅仅是创建的

pg_query_params(Resource id #154,INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES ($1, $2, $3, $4, $5, $6, $7, $8),Array) 
PostgreSQLConnector.php:200
PostgreSQLConnector->preparedQuery(INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES (?, ?, ?, ?, ?, ?, ?, ?),Array,256) 
Database.php:143
SS_Database->{closure}(INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)) 
Database.php:193
SS_Database->benchmarkQuery(INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES (?, ?, ?, ?, ?, ?, ?, ?),Closure,Array) 
Database.php:146
SS_Database->preparedQuery(INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES (?, ?, ?, ?, ?, ?, ?, ?),Array,256) 
DB.php:365
DB::prepared_query(INSERT INTO "TABLENAME" ("ClassName", "Name", "MemberID", "OTHERTABLEID", "Value", "LastEdited", "Created", "ID") VALUES (?, ?, ?, ?, ?, ?, ?, ?),Array) 
SQLExpression.php:121

编辑:文档化的SilverStripe生成的密钥支持已中断,并且使用了一种无法正常工作的标识符生成方法。然而,一位开发人员已经确认这是一个文档错误,框架的真正行为不再是使用max()查询。所以问题不在这里

对于任何想知道为什么使用
max(…)
生成密钥是错误的人来说:它完全不安全。甚至在子查询中。如果您这样做:

INSERT INTO my_table(id, ...)
VALUES
(
  (SELECT max(id) + 1 FROM my_table),
  ...
);
然后两个
SELECT
s可以同时运行。它们将得到相同的结果,然后两个
insert
s将尝试插入相同的值。即使一个
insert
在另一个
select
运行之前完成,如果尚未提交,另一个
select
也不会看到新值

只有在
先锁定表
选择时,这样做才安全。。。用于子查询中的更新
。A
选择。。。在这种情况下,更新的速度要慢得多

因此,如果您可以修改SilverStripe,请将其更改为至少在独占模式下发送一个
锁表mytable
选择max(…)
之前,这样它既慢又笨拙,但又不会损坏

或者,更好的方法是,修复它,只使用数据库序列

如果真正的业务需要无间隙编号,请使用由
UPDATE维护的计数器表。。。而是返回…
。(如果需要便携性,则必须在同一事务中使用
选择…进行更新
,然后使用
更新

更新:该框架在最新版本中不再使用这种方法


(删除了关于该方法不具建设性的牢骚)

文档中的注释已经非常过时(即使是2007年的SilverStripe 2.1也有正确的行为),文档中描述的方法将导致竞争条件

复杂性在于SilverStripe使用多表继承,而SilverStripe在这种情况下所做的是:

  • 插入到SiteTree表中
  • 获取生成的ID
  • 使用相同的ID插入页面表(和其他表)
它还可以对具有相同ID的SiteTree表执行后续更新写入


不幸的是,这不一定能帮助您解决问题,但至少可以消除问题的一个可能来源。

正如@CraigRinger所指出的,访问插入ID的方式是跨所有会话,而不是每个会话。这是基于会话的


到目前为止,我还没有重复这个问题,但我不完全相信这是问题的核心。如果它再次出现故障,我会更新它。

你能使用序列吗?@JorgeCampos你能通过ORM做到这一点吗?我可以直接在其中编写SQL,但它避免了ORM带来的所有好处,所以我希望避免它。不幸的是,我不知道silverstripe能回答这个问题。我建议使用序列,因为它是数据库的内置函数。可以使用该方法进行搜索,但不确定。如果重新生成ID,则无法确定框架如何从查询中选择作为参数传递的ID
$8
。这就是为什么我建议设置
log\u statement=all
和一个更好的
log\u line\u前缀
,这样您就可以看到在整个会话中所做的事情。它可以手动调用序列上的
nextval(..)
,然后将结果传递给
insert
。这很好。
从“TABLENAME\u ID\u seq”中选择最后一个值。
是。。。深深地被感动了。你不应该这样访问序列。您唯一应该使用的是
nextval
,如果您只插入一条记录,
lastval
。直接
选择
序列关系中的ing将在较新的Postg中停止工作