2009-03-16 24 views
21

Tôi thấy các sự cố về hiệu suất với việc truy xuất nhiều phiên bản của các đối tượng có nhiều mối quan hệ với các đối tượng khác. Tôi đang sử dụng triển khai JPA của Spring và Hibernate với MySQL. Vấn đề là khi thực hiện một truy vấn JPA, Hibernate không tự động tham gia vào các bảng khác. Điều này dẫn đến các truy vấn SQL * n + 1, trong đó n là số đối tượng được lấy ra và r là số lượng các mối quan hệ.Có thể sử dụng Hibernate trong các ứng dụng nhạy cảm với hiệu năng không?

Ví dụ, một người sống tại một địa chỉ, có nhiều Sở thích, và đã đi nhiều nước:

@Entity 
public class Person { 
    @Id public Integer personId;  
    public String name;  
    @ManyToOne public Address address;  
    @ManyToMany public Set<Hobby> hobbies;  
    @ManyToMany public Set<Country> countriesVisited; 
} 

Khi tôi thực hiện một truy vấn JPA để có được tất cả những người có tên là Bob, và có 100 Bobs trong cơ sở dữ liệu:

SELECT p FROM Person p WHERE p.name='Bob' 

Hibernate dịch này để 301 truy vấn SQL:

SELECT ... FROM Person WHERE name='Bob' 
SELECT ... FROM Address WHERE personId=1 
SELECT ... FROM Address WHERE personId=2 
... 
SELECT ... FROM Hobby WHERE personId=1 
SELECT ... FROM Hobby WHERE personId=2 
... 
SELECT ... FROM Country WHERE personId=1 
SELECT ... FROM Country WHERE personId=2 
... 

Theo Hibernate FAQ (herehere), giải pháp là chỉ định LEFT JOIN hoặc LEFT OUTER JOIN (for many-to-many) trong truy vấn. Bây giờ câu hỏi của tôi trông giống như:

SELECT p, a, h, c FROM Person p 
LEFT JOIN p.address a LEFT OUTER JOIN p.hobbies h LEFT OUTER JOIN p.countriesVisited c 
WHERE p.name = 'Bob' 

này hoạt động, nhưng dường như có một lỗi nếu có nhiều hơn một LEFT OUTER JOIN trong trường hợp Hibernate không chính xác tìm kiếm một cột không tồn tại:

could not read column value from result set: personId69_2_; Column 'personId69_2_' not found. 

Hành vi lỗi dường như có thể được giải quyết bằng Hibernate Core bug HHH-3636. Thật không may sửa chữa không phải là một phần của bất kỳ phát hành Hibernate JAR. Tôi đã chạy ứng dụng của tôi chống lại việc xây dựng bản chụp nhưng hành vi lỗi vẫn còn hiện diện. Tôi cũng đã xây dựng Hibernate Core JAR của riêng mình từ mã mới nhất trong kho lưu trữ và hành vi lỗi vẫn còn hiện diện. Vì vậy, có thể HHH-3636 không giải quyết vấn đề này.

Giới hạn hiệu suất Hibernate này rất bực bội. Nếu tôi truy vấn 1000 đối tượng thì 1000 * r + 1 truy vấn SQL được thực hiện cho cơ sở dữ liệu. Trong trường hợp của tôi, tôi có 8 mối quan hệ vì vậy tôi nhận được 8001 truy vấn SQL, dẫn đến hiệu suất kinh khủng. Các giải pháp Hibernate chính thức cho việc này là để lại tham gia tất cả các mối quan hệ. Nhưng điều này là không thể với nhiều hơn một nhiều-nhiều mối quan hệ do hành vi lỗi. Vì vậy, tôi bị mắc kẹt với các phép nối trái cho các mối quan hệ nhiều-một và các truy vấn n * r + 1 do các mối quan hệ nhiều-nhiều. Tôi dự định gửi vấn đề LEFT OUTER JOIN dưới dạng lỗi Hibernate, nhưng trong thời gian chờ đợi, khách hàng của tôi cần một ứng dụng có hiệu suất hợp lý. Tôi hiện đang sử dụng kết hợp lấy hàng loạt (BatchSize), ehcache và bộ nhớ đệm trong bộ nhớ tùy chỉnh nhưng hiệu suất vẫn khá kém (nó cải thiện việc lấy 5000 đối tượng từ 30 đến 8 giây). Điểm mấu chốt là quá nhiều truy vấn SQL đang nhấn vào cơ sở dữ liệu.

Vì vậy, câu hỏi của tôi, có thể sử dụng Hibernate trong các ứng dụng nhạy cảm về hiệu năng trong đó các bảng có nhiều mối quan hệ với nhau không? Tôi rất thích nghe cách Hibernate thành công sử dụng hiệu suất địa chỉ. Tôi có nên viết tay SQL (mà phần nào đánh bại mục đích sử dụng Hibernate)? Tôi có nên khử chuẩn hóa lược đồ cơ sở dữ liệu của mình để giảm số lượng các bảng đã tham gia không? Tôi có nên không sử dụng Hibernate nếu tôi cần hiệu năng truy vấn nhanh không? Có gì nhanh hơn không?

+0

Chúng tôi đã phát hành Batoo JPA đó là ~ nhanh hơn 15 lần sau đó Hibernate và triển khai JPA Spec% 100. Động lực chính của dự án là tất cả ba triển khai JPA chỉ đơn giản là làm chậm. Có JPA nhận ra có thể nhanh hơn nhiều, chúng tôi đã phát triển Batoo JPA. Hãy dùng thử http://batoo.jp –

Trả lời

11

Xem câu trả lời của tôi để other question của bạn, nếu bạn đọc toàn bộ câu hỏi thường gặp bạn liên quan đến:

Thực hiện theo các hướng dẫn phương pháp tốt nhất! Đảm bảo rằng tất cả và ánh xạ chỉ định lazy = "true" trong Hibernate2 (đây là mặc định mới trong Hibernate3). Sử dụng HQL LEFT JOIN FETCH để chỉ định các liên kết nào bạn cần được truy lục trong lệnh SELECT ban đầu.

Cách thứ hai để tránh sự cố chọn n + 1 là sử dụng fetch = "subselect" trong Hibernate3.

Nếu bạn vẫn không chắc chắn, hãy tham khảo tài liệu Hibernate và Hibernate in Action.

Xem các mẹo trên improving performance. Nếu bạn không cẩn thận với việc tham gia, bạn sẽ gặp phải sự cố với số Cartesian Product.

+0

Cảm ơn, tôi đang điều tra điều này ngay bây giờ –

+0

Lựa chọn công việc tuyệt vời, cảm ơn. –

+0

Chỉ cần làm rõ về các giá trị mặc định để tìm nạp. Nếu bạn sử dụng chú thích, thì Hibernate theo mặc định tìm nạp được đặt ra bởi thông số JPA cho biết rằng các kết hợp nhiều-một và một-một-một được mặc định theo mặc định và các bộ sưu tập được mặc định là lười. –

3

Bạn đã thử "tham gia" fetch strategy cho các bộ sưu tập chưa?

0

Nếu bạn cần một tính năng của Hibernate và tính năng này là lỗi bạn có hai lựa chọn: a) Gửi một bugrequest và sử dụng một workaround (hiệu suất chậm hoặc sql viết tay) cho đến khi lỗi được cố định mà sẽ mất một thời gian b) Gửi báo cáo lỗi cùng với bản sửa lỗi và kiểm tra. (Tất nhiên bạn chỉ có thể sử dụng bugfix và bỏ qua phần kiểm tra lỗi và kiểm tra).

7

Ngoài chiến lược "tìm nạp", bạn cũng có thể thử đặt kích thước tìm nạp theo lô trong thuộc tính ngủ đông, vì vậy nó sẽ chạy các truy vấn tham gia không phải từng lần một mà theo từng đợt.

Trong appContext.xml của bạn:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
    ...  
    <property name="hibernateProperties"> 
     <props>   
      ... 
      <prop key="hibernate.default_batch_fetch_size">32</prop> 
     </props> 
    </property> 
</bean> 

Vì vậy, thay vì:

SELECT ... FROM Hobby WHERE personId=1 
SELECT ... FROM Hobby WHERE personId=2 

Bạn sẽ nhận được:

SELECT ... FROM Hobby WHERE personId in (1,2,...,32); 
SELECT ... FROM Hobby WHERE personId in (33,34,...,64); 
Các vấn đề liên quan