簡體   English   中英

Spring JPA 慢查詢 Oracle 使用 like 和 upper

[英]Spring JPA slow query with Oracle using like and upper

我遇到了一個優化問題,我想不通為什么我的查詢這么慢。

在生產中搜索document.name時,如果沒有結果且沒有IDX_DOC_NAME_UPPER ,應用程序大約需要 4 分鍾。 對於IDX_DOC_NAME_UPPER ,它可以在 40s 到 2m 之間變化。 sqlplus中,有索引大約需要 3-4 秒,沒有索引需要 8-9 秒。

DOCUMENTS包含大約 2M 條記錄和ACTIVITIES 7M 條記錄。

數據庫模式

-- DOCUMENTS
CREATE TABLE DOCUMENTS
(
    ID          NUMBER(16) PRIMARY KEY,
    NAME        VARCHAR(1024) NOT NULL,
    DOC_SIZE    NUMBER(16)    NOT NULL,
    CREATED     TIMESTAMP     NOT NULL
);
CREATE INDEX IDX_DOC_NAME_UPPER ON DOCUMENTS (UPPER(NAME));

-- USERS
CREATE TABLE USERS
(
    ID    NUMBER(16) PRIMARY KEY,
    CNAME VARCHAR(256) NOT NULL
    --others, removed for brevity
);

-- ACTIVITIES
CREATE TABLE ACTIVITIES
(
    ID     NUMBER(16) PRIMARY KEY,
    USR_ID NUMBER(16) references USERS (ID) NOT NULL,
    DOC_ID NUMBER(16) references DOCUMENTS (ID),
    PRT_ID NUMBER(16) references USERS (ID) NOT NULL
    --others, removed for brevity
);
CREATE INDEX IDX_ACT_USR_ID ON ACTIVITIES (USR_ID);
CREATE INDEX IDX_ACT_DOC_ID ON ACTIVITIES (DOC_ID);

-- LOGS
CREATE VIEW V_LOGS AS
SELECT CNAME,
       ACTIVITIES.USR_ID,
       PRT_ID,
       DOC_ID
       --others, removed for brevity
FROM ACTIVITIES,
     USERS
WHERE USERS.ID = ACTIVITIES.USR_ID;

實體

@Data
@Entity
@NoArgsConstructor
@Table(name = "USERS")
@SequenceGenerator(allocationSize = 1, name = "USERS_ID", sequenceName = "USERS_ID")
public class User {
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_ID")
    private Long id;

    @NotNull
    @Column(name = "CNAME", nullable = false, length = 256)
    private String cname;
}

@Entity
@Getter
@Immutable
@Table(name = "V_LOGS")
public class LogsView {

    @Id
    @Column(name = "USR_ID", nullable = false)
    private long usrId;

    @Column(name = "PRT_ID", nullable = false)
    private long prtId;

    @Column(name = "DOC_ID")
    private Long docId;

    @ManyToOne
    @JoinColumn(name = "USR_ID",
            nullable = false,
            insertable = false,
            updatable = false)
    private User user;

    @ManyToOne
    @JoinColumn(name = "PRT_ID",
            nullable = false,
            insertable = false,
            updatable = false)
    private User partner;

    @ManyToOne
    @JoinColumn(name = "DOC_ID",
            insertable = false,
            updatable = false)
    private Document document;

    //others, removed for brevity
}

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "DOCUMENTS")
@SequenceGenerator(allocationSize = 1, name = "DOCUMENTS_ID", sequenceName = "DOCUMENTS_ID")
public class Document {
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DOCUMENTS_ID")
    private Long id;

    @Column(name = "NAME", nullable = false, length = 1024)
    private String name;

    @Column(name = "DOC_SIZE", nullable = false)
    private long size;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "CREATED", nullable = false)
    private Date created;

    //others, removed for brevity
}

規格

@RequiredArgsConstructor
public class LogsSpecification implements Specification<LogsView> {
    private final List<SimpleFilter> filters;

    @Override
    public Predicate toPredicate(Root<LogsView> root,
                                 CriteriaQuery<?> criteriaQuery,
                                 CriteriaBuilder criteriaBuilder) {
        if (CollectionUtils.isEmpty(filters)) return null;

        Predicate[] predicates = filters.stream()
                .filter(Objects::nonNull)
                .map(filter -> getPredicate(filter, root, criteriaBuilder))
                .toArray(Predicate[]::new);

        return criteriaBuilder.and(predicates);
    }

    private Predicate getPredicate(SimpleFilter filter,
                                   Root<LogsView> root,
                                   CriteriaBuilder builder) {
        Objects.requireNonNull(filter.getName());
        Predicate predicate;
        String[] keys = filter.getName().split("\\.");
        if (filter.getPredicate() == LIKE) {
            predicate = builder.like(
                    builder.upper(PredicateUtils.getChildPath(root, keys)),
                    ("%" + filter.getValue() + "%").toUpperCase());
        } else {
            predicate = builder.equal(
                    PredicateUtils.getChildPath(root, keys),
                    filter.getValue());
        }
        //others, removed for brevity

        return predicate;
    }
}

資料庫

@Repository
public interface LogsRepository extends ReadOnlyRepository<LogsView, Long>, JpaSpecificationExecutor<Logs> {}

服務

@Service
@RequiredArgsConstructor
public class LogsService {
    private final LogsRepository logsRepository;
    private final ObjectMapper mapper;
    private final Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");

    public Page<LogsDto> paginated(String name, String value) {
        var type = isNumeric(value) ? SimpleFilter.PredicateType.EQUAL : SimpleFilter.PredicateType.LIKE;
        var filter = new SimpleFilter(name, value, type);
        return logsRepository
                .findAll(new LogsSpecification(List.of(filter)), Pageable.ofSize(10))
                .map(en -> mapper.convertValue(en, LogsDto.class));
    }


    private boolean isNumeric(String strNum) {
        return strNum != null && pattern.matcher(strNum).matches();
    }
}

查詢生成

-- Hibernate: 
-- executing 5 JDBC statements;
select *
from (select * -- removed for brevity
      from v_logs v_logs0_
               cross join documents document1_
      where v_logs0_.doc_id = document1_.id
        and (
          upper(document1_.name) like ?
          )
        and (
              v_logs0_.usr_id in (4, 72, 76, 123, 147, 199, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)
          )
        and v_logs0_.usr_id <> 1
      order by v_logs0_.usr_id asc)
where rownum <= ?
沒有document.name過濾器,第 10 頁
1.228738106 seconds
使用document.name過濾器,第 10 頁和結果
2.900642325 seconds
使用document.name過濾器,第 10 頁,沒有結果
Without IDX_DOC_NAME_UPPER: 240.123813697 seconds, sqlpus: 8-9 seconds
With IDX_DOC_NAME_UPPER: 110.123813697(not stable), seconds sqlpus: 3-4 seconds

SQL 小提琴

查看執行計划

配置

  • 數據庫:Oracle 19c
  • JDK : 11
  • Ojdbc8 :21.3.0
  • 彈簧引導:2.6.4
  • Hibernate :5.6.5.決賽

我用代碼結構重新創建了一個簡單的應用程序,您可以在此鏈接中找到它: demo-oracle-slow

謝謝

1. LIKE 很慢
在您的實現中,您使用LIKE '%string%' 索引搜索失敗。
LIKE 搜索示例:
在此處輸入圖像描述
使用LIKE 'string%'而不是LIKE '%string%' 如果您指定前導字符,則 Oracle 更有可能使用上層索引執行查詢 - 這將提高性能。


另一種選擇是嘗試創建一個反向文本索引,以防您的姓名字段包含多個單詞。 請參閱CTXCAT 索引文檔 請注意表 memory 的空間將顯着增加。

2. Hibernate正在生成cross join
Hibernate 正在生成cross join的第二個問題。 它需要避免。
在您的條件中使用JoinType.INNER
另一種解決方案是定義 Entity Graph。 例子

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM