SQLAlchemy Postrgresql XID 类型比较

问题描述 投票:0回答:1

我有一些代码可以使用

xmin
值来防止并发更新。在模型中,它映射到整数,如下所示

class MyTable(Base):
   version = mapped_column(Integer, name="xmin", system=True)

然后我将其与:

update(MyTable).where(MyTable.version == int(row.version))

但我最近发现它确实失败并出现错误

(psycopg.errors.NumericValueOutOfRange) integer out of range [SQL: ...xmin = %(xmin_1)s::INTEGER]

我按照文档中的建议检查了字符串,但它也失败了,因为

operator does not exist: xid = character varying
BigInteger
类型也是如此。

那么

xmin
的正确类型是什么?

以防万一,我正在使用 psycopg 3.1.18 和 sqlalchemy 2.0.29

postgresql sqlalchemy psycopg3
1个回答
0
投票

类似

xmin = value::XID
但不知道该怎么做

为什么不走另一条路,做

xmin::text = value

class MyTable(Base):
   version = mapped_column(String, name="xmin", system=True)
update(MyTable).where(String(MyTable.version) == String(row.version))

应该可以了。不过,我同意 @Frank Heikens' remark,即使用显式或咨询锁可能会更容易,而不是尝试利用内部 MVCC 机制。

FOR UPDATE 并没有真正起作用。

确实显式锁被广泛采用,非常易于使用和维护。如果这是您第一次尝试,典型的初学者错误是

  • 忘记它们只保留到交易结束,
    commit
    /
    rollback
  • 如果您不关闭自动提交,它们将被您的客户端悄悄释放
  • 如果您使用的池中后续查询最终位于不同的会话中,则您的下一个查询可能最终位于与之前获取锁的会话不同的会话中
  • 锁定了错误的东西并期望锁定其他东西

咨询锁不锁定任何东西——它们的工作原理就像信号量。您可以在应用程序的某些部分添加对它们的请求,这些请求可能会相互干扰,并在尝试进一步操作之前请求特定的咨询锁。如果当前有一个并行工作线程在执行该操作,则它一定在您之前获取了锁,因此您将等待(或被拒绝,或跳到最近的未锁定操作,具体取决于您如何配置它)。


如果您查看

pg_cast
目录,您会发现
xid
只有一个从
xid8
xid
定义的显式转换,没有其他内容:
db<>fiddle 的演示

select castsource::regtype
      ,casttarget::regtype
      ,castfunc::regproc
      ,case castcontext when 'e' then 'explicit'
                        when 'a' then 'assignment'
                        when 'i' then 'implicit' end as castcontext
      ,case castmethod when 'f' then 'cast function'
                       when 'i' then 'input/output function'
                       when 'b' then 'binary coercion' end as castmethod
from pg_cast 
where casttarget::regtype='xid'::regtype 
   or castsource::regtype='xid'::regtype;
castsource 施放目标 castfunc castcontext 铸造方法
xid8 xid xid 明确 投射功能

=
运算符后面定义了一堆函数,用于处理每一侧的不同类型:

SELECT n.nspname as "Schema",
  o.oprname AS "Name",
  CASE WHEN o.oprkind='l' THEN NULL 
       ELSE pg_catalog.format_type(o.oprleft, NULL) END AS "Left arg type",
  CASE WHEN o.oprkind='r' THEN NULL 
       ELSE pg_catalog.format_type(o.oprright, NULL) END AS "Right arg type",
  pg_catalog.format_type(o.oprresult, NULL) AS "Result type",
  o.oprcode AS "Function",
  coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),
           pg_catalog.obj_description(o.oprcode, 'pg_proc')) AS "Description"
FROM pg_catalog.pg_operator o
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace
WHERE o.oprname OPERATOR(pg_catalog.~) '^(=)$' COLLATE pg_catalog.default
  AND pg_catalog.pg_operator_is_visible(o.oid)
ORDER BY 1, 2, 3, 4;

如果 Postgres 没有看到与您的参数对匹配的组合,它会尝试查看是否定义了隐式转换来转换其中一个参数,形成它知道如何比较的一对。有趣的是,那里有 is

xid=int4
比较:

架构 姓名 左arg类型 右参数类型 结果类型 功能 描述
pg_目录 = xid 整数 布尔值 xideqint4 平等
pg_目录 = xid xid 布尔值 xideq 平等
pg_目录 = xid8 xid8 布尔值 xid8eq 平等

这就是为什么该错误消息没有抱怨运算符而是整数格式。问题是,它主要是为了方便,这样你就可以type类似

where xmin=12345
之类的东西 - 正如前面指出的,没有强制转换,因为这些类型不兼容。它们都是 32 位,但
xid
是无符号的,这意味着它的一半值高于
int4
的上限。您获得并尝试施放的
xid
一定在该范围内。

您可以某种程度上始终依赖

text

“自动 I/O 转换”,即使用数据类型自己的 I/O 函数来执行与

text
或其他字符串类型之间的转换,未在
pg_cast
中明确表示。

但这有点误导。如果你尝试其中任何一个,它都会失败:

select xmin = xmin::text from test;
select xmin = 260498::text from test;
select xmin = '260498'::text from test;
ERROR:  operator does not exist: xid = text
LINE 1: select xmin=xmin::text from test;

这是因为显式强制转换将禁用 Postgres 方面的进一步猜测 - 它不会尝试进一步转换右侧的

text
,以匹配左侧的
xid
。 但这会起作用:

select xmin = '260498' from test;

因为那还不是真正的

text
。这是一个
unknown
,Postgres 会猜测需要转换或者更确切地说分配以匹配左操作数,如下所示:

select xmin = xid('260498') from test;

如果您可以让 sqlalchemy 将您的请求转换为其中之一,那么它就会起作用。或者,您可以回到上面的想法:

select xmin::text = '260498'::text from test;

pg_cast
目录没有提到从最初的
unknown
输入到每种类型都有一个转换(转换、赋值)方法,但它也没有提到每种类型都可以转换为
text
。因此,您可以将您的值保留在
text
中,并将其他值也转换为
text
,然后进行比较。

© www.soinside.com 2019 - 2024. All rights reserved.