ORA-01000, lỗi con trỏ mở tối đa, là một lỗi cực kỳ phổ biến trong phát triển cơ sở dữ liệu của Oracle. Trong ngữ cảnh của Java, nó xảy ra khi ứng dụng cố gắng mở nhiều Kết quả hơn là các con trỏ được cấu hình trên một cá thể cơ sở dữ liệu.
Nguyên nhân phổ biến là:
Lỗi cấu hình
- Bạn có nhiều luồng trong ứng dụng của mình truy vấn cơ sở dữ liệu hơn các con trỏ trên DB. Một trường hợp là nơi bạn có một kết nối và nhóm luồng lớn hơn số lượng con trỏ trên cơ sở dữ liệu.
- Bạn có nhiều nhà phát triển hoặc ứng dụng được kết nối với cùng một thể hiện DB (có thể sẽ bao gồm nhiều lược đồ) và cùng nhau bạn đang sử dụng quá nhiều kết nối.
Giải pháp:
- Tăng số lượng con trỏ trên cơ sở dữ liệu (nếu tài nguyên cho phép) hoặc
- Giảm số lượng chủ đề trong ứng dụng.
Rò rỉ con trỏ
- Các ứng dụng không đóng resultSets (trong JDBC) hoặc con trỏ (trong các thủ tục được lưu trữ trên cơ sở dữ liệu)
- Giải pháp : Rò rỉ con trỏ là lỗi; tăng số lượng con trỏ trên DB chỉ đơn giản là trì hoãn sự thất bại không thể tránh khỏi. Rò rỉ có thể được tìm thấy bằng cách sử dụng phân tích mã tĩnh , JDBC hoặc ghi nhật ký cấp ứng dụng và giám sát cơ sở dữ liệu .
Lý lịch
Phần này mô tả một số lý thuyết đằng sau các con trỏ và cách sử dụng JDBC. Nếu bạn không cần biết nền, bạn có thể bỏ qua phần này và đi thẳng đến 'Loại bỏ rò rỉ'.
Một con trỏ là gì?
Con trỏ là một tài nguyên trên cơ sở dữ liệu chứa trạng thái của truy vấn, cụ thể là vị trí mà trình đọc nằm trong Bộ kết quả. Mỗi câu lệnh SELECT có một con trỏ và các thủ tục được lưu trữ PL / SQL có thể mở và sử dụng nhiều con trỏ theo yêu cầu. Bạn có thể tìm hiểu thêm về các con trỏ trên Orafaq .
Một phiên bản cơ sở dữ liệu thường phục vụ một số lược đồ khác nhau , nhiều người dùng khác nhau, mỗi phiên có nhiều phiên . Để làm điều này, nó có một số con trỏ cố định có sẵn cho tất cả các lược đồ, người dùng và phiên. Khi tất cả các con trỏ đang mở (đang sử dụng) và yêu cầu xuất hiện yêu cầu một con trỏ mới, yêu cầu không thành công với lỗi ORA-010000.
Tìm và thiết lập số lượng con trỏ
Số thường được cấu hình bởi DBA khi cài đặt. Số lượng con trỏ hiện đang sử dụng, số lượng tối đa và cấu hình có thể được truy cập trong các chức năng Quản trị viên trong Oracle SQL Developer . Từ SQL, nó có thể được đặt bằng:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Liên kết JDBC trong JVM với các con trỏ trên DB
Các đối tượng JDBC bên dưới được liên kết chặt chẽ với các khái niệm cơ sở dữ liệu sau:
- Kết nối JDBC là đại diện khách hàng của phiên cơ sở dữ liệu và cung cấp các giao dịch cơ sở dữ liệu . Một kết nối chỉ có thể mở một giao dịch duy nhất tại một thời điểm (nhưng các giao dịch có thể được lồng nhau)
- Một JDBC ResultSet được hỗ trợ bởi một đơn trỏ trên cơ sở dữ liệu. Khi close () được gọi trên Kết quả, con trỏ sẽ được giải phóng.
- Một CallableStatement của JDBC gọi một thủ tục được lưu trữ trên cơ sở dữ liệu, thường được viết bằng PL / SQL. Quy trình được lưu trữ có thể tạo ra 0 hoặc nhiều con trỏ và có thể trả về một con trỏ dưới dạng Bộ kết quả JDBC.
JDBC là luồng an toàn: Việc truyền các đối tượng JDBC khác nhau giữa các luồng là khá ổn.
Ví dụ: bạn có thể tạo kết nối trong một luồng; một luồng khác có thể sử dụng kết nối này để tạo PreparedStatement và luồng thứ ba có thể xử lý tập kết quả. Hạn chế chính duy nhất là bạn không thể mở nhiều Kết quả trên một PreparedStatement duy nhất bất cứ lúc nào. Xem Oracle DB có hỗ trợ nhiều hoạt động (song song) cho mỗi kết nối không?
Lưu ý rằng một cam kết cơ sở dữ liệu xảy ra trên một Kết nối và do đó, tất cả các DML (CHERTN, CẬP NHẬT và XÓA) trên kết nối đó sẽ cùng nhau cam kết. Do đó, nếu bạn muốn hỗ trợ nhiều giao dịch cùng một lúc, bạn phải có ít nhất một Kết nối cho mỗi Giao dịch đồng thời.
Đóng các đối tượng JDBC
Một ví dụ điển hình của việc thực hiện một Kết quả là:
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
try {
while ( rs.next() ) {
System.out.println( "Name: " + rs.getString("FULL_NAME") );
}
} finally {
try { rs.close(); } catch (Exception ignore) { }
}
} finally {
try { stmt.close(); } catch (Exception ignore) { }
}
Lưu ý cách mệnh đề cuối cùng bỏ qua mọi ngoại lệ được đưa ra bởi close ():
- Nếu bạn chỉ đơn giản đóng Kết quả mà không thử {} bắt {}, nó có thể thất bại và ngăn Tuyên bố bị đóng
- Chúng tôi muốn cho phép bất kỳ ngoại lệ nào được nêu ra trong cơ thể của cố gắng tuyên truyền cho người gọi. Nếu bạn có một vòng lặp, ví dụ, tạo và thực thi các Báo cáo, hãy nhớ đóng từng Tuyên bố trong vòng lặp.
Trong Java 7, Oracle đã giới thiệu giao diện AutoClosizable thay thế hầu hết các bản tóm tắt Java 6 bằng một số đường cú pháp hay.
Giữ các đối tượng JDBC
Các đối tượng JDBC có thể được giữ an toàn trong các biến cục bộ, thể hiện đối tượng và các thành viên lớp. Nói chung là thực hành tốt hơn để:
- Sử dụng cá thể đối tượng hoặc thành viên lớp để giữ các đối tượng JDBC được sử dụng lại nhiều lần trong một khoảng thời gian dài hơn, chẳng hạn như Kết nối và PreparedStatements
- Sử dụng các biến cục bộ cho resultSets vì chúng được lấy, lặp đi lặp lại và sau đó đóng thường trong phạm vi của một hàm duy nhất.
Tuy nhiên, có một ngoại lệ: Nếu bạn đang sử dụng EJB hoặc thùng chứa Servlet / JSP, bạn phải tuân theo một mô hình luồng nghiêm ngặt:
- Chỉ Máy chủ ứng dụng tạo các luồng (mà nó xử lý các yêu cầu đến)
- Chỉ Máy chủ ứng dụng tạo kết nối (mà bạn có được từ nhóm kết nối)
- Khi lưu các giá trị (trạng thái) giữa các cuộc gọi, bạn phải rất cẩn thận. Không bao giờ lưu trữ giá trị trong bộ nhớ cache hoặc thành viên tĩnh của bạn - điều này không an toàn trên các cụm và các điều kiện kỳ lạ khác và Máy chủ ứng dụng có thể làm những điều khủng khiếp đối với dữ liệu của bạn. Thay vào đó sử dụng đậu trạng thái hoặc cơ sở dữ liệu.
- Cụ thể, không bao giờ giữ các đối tượng JDBC (Kết nối, Kết quả, PreparedStatements, v.v.) trên các yêu cầu từ xa khác nhau - hãy để Máy chủ ứng dụng quản lý việc này. Máy chủ ứng dụng không chỉ cung cấp một nhóm kết nối, nó còn lưu trữ PreparedStatements của bạn.
Loại bỏ rò rỉ
Có một số quy trình và công cụ có sẵn để giúp phát hiện và loại bỏ rò rỉ JDBC:
Trong quá trình phát triển - bắt lỗi sớm là cách tiếp cận tốt nhất:
Thực tiễn phát triển: Thực tiễn phát triển tốt sẽ làm giảm số lượng lỗi trong phần mềm của bạn trước khi nó rời khỏi bàn của nhà phát triển. Thực hành cụ thể bao gồm:
- Lập trình cặp , để giáo dục những người không có đủ kinh nghiệm
- Mã đánh giá vì nhiều mắt tốt hơn một
- Kiểm thử đơn vị có nghĩa là bạn có thể thực hiện bất kỳ và tất cả các cơ sở mã của mình từ một công cụ kiểm tra khiến việc sao chép rò rỉ trở nên tầm thường
- Sử dụng các thư viện hiện có để tổng hợp kết nối thay vì xây dựng thư viện của riêng bạn
Phân tích mã tĩnh: Sử dụng một công cụ như Findbugs xuất sắc để thực hiện phân tích mã tĩnh. Điều này chọn nhiều nơi mà đóng () chưa được xử lý chính xác. Findbugs có một plugin cho Eclipse, nhưng nó cũng chạy độc lập cho một lần, có tích hợp vào Jenkins CI và các công cụ xây dựng khác
Trong thời gian chạy:
Khả năng giữ và cam kết
- Nếu khả năng giữ kết quả của Bộ kết quả là Kết quả Bộ. Điều này có thể được đặt bằng cách sử dụng Connection.setHoldability () hoặc bằng cách sử dụng phương thức Connection.createStatement () bị quá tải.
Đăng nhập vào thời gian chạy.
- Đặt báo cáo nhật ký tốt trong mã của bạn. Những điều này nên rõ ràng và dễ hiểu để khách hàng, nhân viên hỗ trợ và đồng đội có thể hiểu mà không cần đào tạo. Chúng nên ngắn gọn và bao gồm in các giá trị trạng thái / nội bộ của các biến và thuộc tính chính để bạn có thể theo dõi logic xử lý. Ghi nhật ký tốt là cơ bản để gỡ lỗi các ứng dụng, đặc biệt là những ứng dụng đã được triển khai.
Bạn có thể thêm trình điều khiển JDBC gỡ lỗi vào dự án của mình (để gỡ lỗi - không thực sự triển khai nó). Một ví dụ (tôi chưa sử dụng nó) là log4jdbc . Sau đó, bạn cần thực hiện một số phân tích đơn giản trên tệp này để xem những người thực thi nào không có lần đóng tương ứng. Đếm mở và đóng sẽ làm nổi bật nếu có vấn đề tiềm ẩn
- Giám sát cơ sở dữ liệu. Giám sát ứng dụng đang chạy của bạn bằng các công cụ, chẳng hạn như chức năng 'Giám sát SQL' của Nhà phát triển SQL hoặc TOAD của Quest . Giám sát được mô tả trong bài viết này . Trong quá trình theo dõi, bạn truy vấn các con trỏ mở (ví dụ từ bảng v $ sesstat) và xem lại SQL của chúng. Nếu số lượng con trỏ ngày càng tăng và (quan trọng nhất) bị chi phối bởi một câu lệnh SQL giống hệt nhau, bạn biết rằng bạn có một rò rỉ với SQL đó. Tìm kiếm mã của bạn và xem xét.
Những suy nghĩ khác
Bạn có thể sử dụng WeakReferences để xử lý các kết nối đóng không?
Tham chiếu yếu và mềm là các cách cho phép bạn tham chiếu một đối tượng theo cách cho phép JVM thu gom rác tham chiếu bất cứ lúc nào nó thấy phù hợp (giả sử không có chuỗi tham chiếu mạnh đến đối tượng đó).
Nếu bạn chuyển ReferenceQueue trong hàm tạo cho Reference hoặc mềm, đối tượng được đặt trong ReferenceQueue khi đối tượng được GC'ed khi nó xảy ra (nếu nó xảy ra). Với phương pháp này, bạn có thể tương tác với quyết toán của đối tượng và bạn có thể đóng hoặc hoàn thiện đối tượng tại thời điểm đó.
Tài liệu tham khảo Phantom là một chút kỳ lạ; Mục đích của chúng chỉ là để kiểm soát quyết toán, nhưng bạn không bao giờ có thể có được một tham chiếu đến đối tượng ban đầu, vì vậy sẽ rất khó để gọi phương thức close () trên nó.
Tuy nhiên, hiếm khi nên cố gắng kiểm soát khi GC chạy (Weak, Soft và PhantomReferences cho bạn biết sau khi thực tế là đối tượng bị mê hoặc bởi GC). Trong thực tế, nếu dung lượng bộ nhớ trong JVM lớn (ví dụ -Xmx2000m), bạn có thể không bao giờ sử dụng đối tượng và bạn vẫn sẽ trải nghiệm ORA-01000. Nếu bộ nhớ JVM nhỏ so với yêu cầu của chương trình của bạn, bạn có thể thấy rằng các đối tượng result Set và PreparedStatement được xử lý ngay sau khi tạo (trước khi bạn có thể đọc từ chúng), điều đó có thể sẽ làm hỏng chương trình của bạn.
TL; DR: Cơ chế tham chiếu yếu không phải là cách tốt để quản lý và đóng các đối tượng Statement và result Set.
for (String language : additionalLangs) {