首页 > 解决方案 > 如何为 postgres 配置休眠隔离级别

问题描述

ErrorCase在 postgres 数据库中有一个表。此表有一个case_id数据类型为文本的字段。它的值由格式生成:yymmdd_xxxx. yymmdd 是记录插入数据库的日期,xxxx 是该日期的记录数。

例如,2019/08/01 的第 3 个错误案例将具有case_id= 190801_0003。在 08/04,如果还有一个案例,case_id它将是 190804_0001,然后继续。

我已经在数据库中使用触发器为该字段生成值:

DECLARE
    total integer;
BEGIN
    SELECT (COUNT(*) + 1) INTO total  FROM public.ErrorCase WHERE create_at = current_date;
    IF (NEW.case_id is null) THEN 
        NEW.case_id = to_char(current_timestamp, 'YYMMDD_') || trim(to_char(total, '0000'));
    END IF;
    RETURN NEW;
END

在 Spring Project 中,我为 jpa/hibernates 配置应用程序属性:

datasource:
        type: com.zaxxer.hikari.HikariDataSource
        url: jdbc:postgresql://localhost:5432/table_name
        username: postgres
        password: postgres
        hikari:
            poolName: Hikari
            auto-commit: false
    jpa:
        database-platform: io.github.jhipster.domain.util.FixedPostgreSQL82Dialect
        database: POSTGRESQL
        show-sql: true
        properties:
            hibernate.id.new_generator_mappings: true
            hibernate.connection.provider_disables_autocommit: true
            hibernate.cache.use_second_level_cache: true
            hibernate.cache.use_query_cache: false
            hibernate.generate_statistics: true

目前,它会正确生成 case_id。

但是,当几乎同时插入许多记录时,它会case_id为两条记录生成相同的记录。我想原因是因为隔离级别。当第一个事务尚未提交时,第二个事务执行 SELECT 查询以构建 case_id。因此,SELECT 查询的结果不包括第一次查询的记录(因为它还没有提交)。因此,第二个case_id与第一个具有相同的结果。

请建议我解决此问题的任何解决方案,哪种隔离级别适合这种情况???

标签: postgresqlhibernatejpa

解决方案


yymmdd 是记录插入数据库的日期,xxxx 是该日期的记录数” - 没有冒犯,但这是一个可怕的设计。

您应该有两个单独的列,一date列和一integer列。如果要在插入期间增加计数器,请将该日期列设为主键并使用insert on conflict. 您可以摆脱这种效率极低的触发器,更重要的是,即使已提交读取,并发修改也将是安全的。

就像是:

create table error_case
(
  error_date date not null primary key, 
  counter integer not null default 1
);

然后使用以下内容插入行:

insert into error_case (error_date)
values (date '2019-08-01')
on conflict (error_date) do update 
   set counter = counter + 1;

并发插入不需要触发器并且是安全的。

如果您确实需要一个文本列作为“案例 ID”,请创建一个返回该格式的视图:

create view v_error_case
as
select concat(to_char(error_date, 'yymmdd'), '_', to_char(counter, '0000')) as case_id,
       ... other columns
from error_case;

推荐阅读