Jooq неявно преобразует bigint в числовое значение, и индекс не работает

У меня очень простой запрос 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?

Почему вы применили принудительный тип с BIGINT/Long на NUMERIC/BigInteger?

Lukas Eder 24.04.2024 14:54
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
1
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Почему это происходит

Вы применили перезапись типа данных , что означает, что генерация кода 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. С таким поведением мы сталкиваемся впервые. Другие таблицы с точно такими же конфигурациями работают хорошо, и приведение типов не применяется. Я попробую ваши предложения по исправлению и вернусь с результатами, спасибо.

True Seeker 25.04.2024 08:52

@TrueSeeker: Вполне вероятно, что в оптимизаторе есть ограничение, возможно, специфичное для вашего использования IN вместо = здесь. Я не исследовал это подробно для PostgreSQL. Но по этим причинам всегда лучше иметь совпадающие типы данных и не полагаться на автоматическое преобразование.

Lukas Eder 25.04.2024 09:06

Да, все сработало, как и ожидалось — индекс используется правильно. Я реализовал собственный конвертер для преобразования java.lang.Long в java.math.BigInteger. Спасибо за ваш ответ, видимо, мы все время неправильно использовали принудительные типы

True Seeker 25.04.2024 09:45

Другие вопросы по теме