У меня очень простой запрос JOOQ:
jooq.select(TABLE_NAME.fields())
.from(TABLE_NAME)
.where(TABLE_NAME.ID.in(ids))
.fetchInto(tableDTO.class);
где идентификаторы List<BigInteger>
Запрос, который генерирует JOOQ:
select
"schema"."table_name"."id",
"schema"."table_name"."entity_id",
"schema"."table_name"."code",
"schema"."table_name"."created_date",
...
from "schema"."table_name" where "schema"."table_name"."id" in (3623)
таблицаDTO:
@Data
public class tableDTO{
private BigInteger id;
...
таблица DDL:
create table table_name
(
id bigint default nextval('schema.table_name_id_seq'::regclass) not null primary key,
...
Кроме того, для этой таблицы существует принудительный тип для приведения идентификатора поля к BigInteger:
<forcedType>
<name>DECIMAL_INTEGER</name>
<includeExpression>.*\.(TABLE_NAME)\.(ID|PARENT_ID|ENTITY_ID)</includeExpression>
</forcedType>
Когда я запускаю этот запрос через JOOQ, он каким-то образом преобразует список bigint
в numeric
, а postgres использует параллельное последовательное сканирование вместо сканирования индекса.
Я вижу это, распечатав план запроса jooq:
Fetched result: +----------------------------------------------------------------------------------+
|QUERY PLAN |
+----------------------------------------------------------------------------------+
|Gather (cost=1000.00..1561941.21 rows=65315 width=1896) |
| Workers Planned: 2 |
| -> Parallel Seq Scan on table_name (cost=0.00..1554409.71 rows=27215 width=1896)|
| Filter: ((id)::numeric = '3623'::numeric) |
+----------------------------------------------------------------------------------+
Но когда я запускаю тот же запрос через DataGrip, Postgres использует сканирование индекса:
Index Scan using table_name_pkey on table_name (cost=0.43..2.65 rows=1 width=1896)
Index Cond: (id = 3623)
Я уже пробовал пылесосить, анализировать эту таблицу. Кажется, с моей таблицей все в порядке, и jooq каким-то образом генерирует ошибочные приведения
Почему происходит это неявное приведение? Это вина JOOQ или DB?
Вы применили перезапись типа данных , что означает, что генерация кода jOOQ стирает все сведения об исходном типе данных (BIGINT
) и делает вид, что тип данных, сообщаемый представлением PostgreSQL INFORMATION_SCHEMA.COLUMNS
, действительно является NUMERIC
.
Теперь, по многочисленным причинам, jOOQ имеет тенденцию приводить значения привязки в PostgreSQL, например, к CAST(? AS NUMERIC)
поскольку PostgreSQL, как правило, не может определить тип, поэтому, когда PostgreSQL видит сравнение типа:
BIGINT_COLUMN = CAST(? AS NUMERIC)
Тогда есть 4 варианта обработки этого в PostgreSQL:
=
для всех возможных пар типов (непрактично).BIGINT
до NUMERIC
). Это предпочтительное решение, поскольку оно не имеет потерь.NUMERIC
до BIGINT
) (нежелательно, поскольку с потерями)Но когда BIGINT_COLUMN
повышается до NUMERIC
, оптимизатор, похоже, не может использовать индекс, если только индекс не является индексом, основанным на функции CAST(BIGINT_COLUMN AS NUMERIC)
. В блоге jOOQ документирована аналогичная проблема нежелательных рекламных акций Oracle DATE -> TIMESTAMP.
Я не знаю, зачем вам тип BigInteger
в клиентском коде, если база данных может хранить только значения Long
. Самое простое решение — просто не делать этого и вместо этого работать с Long
. Тем не менее, если вы предпочитаете BigInteger
, вы можете:
NUMERIC
(это предотвратит переполнение при хранении слишком больших идентификаторов).Converter<Long, BigInteger>
к столбцу вместо перезаписи типа данных. Таким образом, jOOQ будет создавать значения BigInteger
в клиентском коде без потери информации о базовом типе столбца.Это странно, потому что мы всегда используем перезапись типов данных для преобразования полей идентификаторов в BigInteger
. С таким поведением мы сталкиваемся впервые. Другие таблицы с точно такими же конфигурациями работают хорошо, и приведение типов не применяется. Я попробую ваши предложения по исправлению и вернусь с результатами, спасибо.
@TrueSeeker: Вполне вероятно, что в оптимизаторе есть ограничение, возможно, специфичное для вашего использования IN
вместо =
здесь. Я не исследовал это подробно для PostgreSQL. Но по этим причинам всегда лучше иметь совпадающие типы данных и не полагаться на автоматическое преобразование.
Да, все сработало, как и ожидалось — индекс используется правильно. Я реализовал собственный конвертер для преобразования java.lang.Long
в java.math.BigInteger
. Спасибо за ваш ответ, видимо, мы все время неправильно использовали принудительные типы
Почему вы применили принудительный тип с
BIGINT
/Long
наNUMERIC
/BigInteger
?