文件首頁
MySQL Connector/J 開發人員指南
相關文件 下載本手冊
PDF (美式 Letter) - 1.2Mb
PDF (A4) - 1.2Mb


MySQL Connector/J 開發人員指南  /  疑難排解 Connector/J 應用程式

第 16 章 疑難排解 Connector/J 應用程式

本節說明使用 MySQL Connector/J 的應用程式最常遇到的問題的徵狀和解決方案。

問題

  • 16.1: 當我嘗試使用 MySQL Connector/J 連線至資料庫時,我收到以下例外狀況

    SQLException: Server configuration denies access to data source
    SQLState: 08001
    VendorError: 0

    怎麼回事? 我可以使用 MySQL 命令列用戶端正常連線。

  • 16.2: 我的應用程式擲回 SQLException 'No Suitable Driver'。 為什麼會這樣?

  • 16.3: 我嘗試在 Applet 或應用程式中使用 MySQL Connector/J,但收到類似以下的例外狀況

    SQLException: Cannot connect to MySQL server on host:3306.
    Is there a MySQL server running on the machine/port you
    are trying to connect to?
    
    (java.security.AccessControlException)
    SQLState: 08S01
    VendorError: 0
  • 16.4: 我的 Servlet/應用程式執行一天都沒有問題,然後隔夜就停止運作了

  • 16.5: 我無法使用 Connector/J 連線至 MySQL 伺服器,而且我確定連線參數是正確的。

  • 16.6: 更新包含 主索引鍵(為 FLOAT 或使用 FLOAT 的複合主索引鍵)的資料表會無法更新資料表並引發例外狀況。

  • 16.7: 我收到 ER_NET_PACKET_TOO_LARGE 例外狀況,即使我想要使用 JDBC 插入的二進位 Blob 大小安全地低於 max_allowed_packet 大小。

  • 16.8: 如果我收到類似以下的錯誤訊息,我應該怎麼做:通訊連結失敗 - 上次傳送到伺服器的封包是 X 毫秒前

  • 16.9: 為什麼 Connector/J 在發生通訊失敗後不會重新連線至 MySQL 並重新發出陳述式,而是擲回例外狀況,即使我使用 autoReconnect 連線字串選項?

  • 16.10: 我如何搭配 Connector/J 使用 3 位元組 UTF8?

  • 16.11: 我如何搭配 Connector/J 使用 4 位元組 UTF8 (utf8mb4)?

  • 16.12: 使用 useServerPrepStmts=false 和某些字元編碼可能會導致插入 BLOB 時發生損毀。 如何避免這種情況?

問答

16.1: 當我嘗試使用 MySQL Connector/J 連線至資料庫時,我收到以下例外狀況:

SQLException: Server configuration denies access to data source
SQLState: 08001
VendorError: 0

怎麼回事? 我可以使用 MySQL 命令列用戶端正常連線。

Connector/J 通常使用 TCP/IP 通訊端連線至 MySQL (例外情況請參閱 第 6.10 節,「使用 Unix 網域通訊端連線」第 6.11 節,「使用具名管道連線」)。 MySQL 伺服器上的安全管理員使用其授與表來判斷是否允許 TCP/IP 連線。 因此,您必須透過向 MySQL 伺服器發出 GRANT 陳述式,將必要的安全性認證新增至 MySQL 伺服器。 如需詳細資訊,請參閱 GRANT 陳述式

警告

在 MySQL 上不當變更權限和許可權可能會導致您的伺服器安裝具有非最佳安全性屬性。

注意

除非您新增 --host 旗標,並使用 localhost 以外的主機,否則使用 mysql 命令列用戶端測試連線將無法運作。mysql 命令列用戶端如果使用特殊主機名稱 localhost,則會嘗試使用 Unix 網域通訊端。 如果您要測試至 localhost 的 TCP/IP 連線能力,請改為使用 127.0.0.1 作為主機名稱。

16.2: 我的應用程式擲回 SQLException 'No Suitable Driver'。 為什麼會這樣?

這個錯誤有三種可能原因

  • Connector/J 驅動程式不在您的 CLASSPATH 中,請參閱 第 4 章,《Connector/J 安裝

  • 您的連線 URL 格式不正確,或者您參考了錯誤的 JDBC 驅動程式。

  • 使用 DriverManager 時,系統屬性 jdbc.drivers 未使用 Connector/J 驅動程式的位置填入。

16.3: 我嘗試在 Applet 或應用程式中使用 MySQL Connector/J,但收到類似以下的例外狀況:

SQLException: Cannot connect to MySQL server on host:3306.
Is there a MySQL server running on the machine/port you
are trying to connect to?

(java.security.AccessControlException)
SQLState: 08S01
VendorError: 0

您可能正在執行 Applet、您的 MySQL 伺服器已啟用系統變數 skip_networking 來安裝,或者您的 MySQL 伺服器前方有防火牆。

Applet 只能建立網路連線回到執行 Web 伺服器(其提供 Applet 的 .class 檔案)的電腦。 這表示 MySQL 必須在同一部電腦上執行 (或您必須具有某種連接埠重新導向) 才能運作。 這也表示您將無法從本機檔案系統測試 Applet,而必須一律將其部署到 Web 伺服器。

Connector/J 通常使用 TCP/IP socket 連接到 MySQL (例外情況請參閱第 6.10 節,「使用 Unix 網域 Socket 連接」第 6.11 節,「使用具名管道連接」)。與 MySQL 的 TCP/IP 通訊可能會受到 skip_networking 系統變數或伺服器防火牆的影響。如果 MySQL 已啟用 skip_networking 啟動,您需要註解掉 /etc/mysql/my.cnf/etc/my.cnf 檔案中的此設定,以便 TCP/IP 連線可以運作。(請注意,您的伺服器設定檔也可能存在於 MySQL 伺服器的 data 目錄,或取決於 MySQL 的編譯方式的其他位置;Oracle 建立的二進位檔總是會尋找 /etc/my.cnfdatadir/my.cnf;詳細資訊請參閱 使用選項檔案。)如果您的 MySQL 伺服器已設定防火牆,您需要設定防火牆以允許從執行 Java 程式碼的主機到 MySQL 伺服器正在監聽的埠 (預設為 3306) 的 TCP/IP 連線。

16.4:我的 Servlet/應用程式可以正常運作一天,然後隔夜就停止運作

MySQL 會在閒置 8 小時後關閉連線。您需要使用可以處理過時連線的連線集區,或使用 autoReconnect 參數(請參閱第 6.3 節,「組態屬性」)。

此外,請在您的應用程式中捕獲 SQLExceptions 並處理它們,而不是將它們一路傳播直到您的應用程式退出。這只是良好的程式設計習慣。當 MySQL Connector/J 在處理查詢期間遇到網路連線問題時,它會將 SQLState (請參閱您 API 文件中的 java.sql.SQLException.getSQLState()) 設定為 08S01。請在此時嘗試重新連線到 MySQL。

以下 (簡單) 範例顯示可以處理這些例外狀況的程式碼範例

範例 16.1 Connector/J:具有重試邏輯的交易範例

public void doBusinessOp() throws SQLException {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;

    //
    // How many times do you want to retry the transaction
    // (or at least _getting_ a connection)?
    //
    int retryCount = 5;

    boolean transactionCompleted = false;

    do {
        try {
            conn = getConnection(); // assume getting this from a
                                    // javax.sql.DataSource, or the
                                    // java.sql.DriverManager

            conn.setAutoCommit(false);

            //
            // Okay, at this point, the 'retry-ability' of the
            // transaction really depends on your application logic,
            // whether or not you're using autocommit (in this case
            // not), and whether you're using transactional storage
            // engines
            //
            // For this example, we'll assume that it's _not_ safe
            // to retry the entire transaction, so we set retry
            // count to 0 at this point
            //
            // If you were using exclusively transaction-safe tables,
            // or your application could recover from a connection going
            // bad in the middle of an operation, then you would not
            // touch 'retryCount' here, and just let the loop repeat
            // until retryCount == 0.
            //
            retryCount = 0;

            stmt = conn.createStatement();

            String query = "SELECT foo FROM bar ORDER BY baz";

            rs = stmt.executeQuery(query);

            while (rs.next()) {
            }

            rs.close();
            rs = null;

            stmt.close();
            stmt = null;

            conn.commit();
            conn.close();
            conn = null;

            transactionCompleted = true;
        } catch (SQLException sqlEx) {

            //
            // The two SQL states that are 'retry-able' are 08S01
            // for a communications error, and 40001 for deadlock.
            //
            // Only retry if the error was due to a stale connection,
            // communications problem or deadlock
            //

            String sqlState = sqlEx.getSQLState();

            if ("08S01".equals(sqlState) || "40001".equals(sqlState)) {
                retryCount -= 1;
            } else {
                retryCount = 0;
            }
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException sqlEx) {
                    // You'd probably want to log this...
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException sqlEx) {
                    // You'd probably want to log this as well...
                }
            }

            if (conn != null) {
                try {
                    //
                    // If we got here, and conn is not null, the
                    // transaction should be rolled back, as not
                    // all work has been done

                    try {
                        conn.rollback();
                    } finally {
                        conn.close();
                    }
                } catch (SQLException sqlEx) {
                    //
                    // If we got an exception here, something
                    // pretty serious is going on, so we better
                    // pass it up the stack, rather than just
                    // logging it...

                    throw sqlEx;
                }
            }
        }
    } while (!transactionCompleted && (retryCount > 0));
}


注意

不建議使用 autoReconnect 選項,因為沒有安全的方法可以重新連線到 MySQL 伺服器,而不會冒著連線狀態或資料庫狀態資訊損毀的風險。請改用連線集區,這會讓您的應用程式可以使用集區中可用的連線來連線到 MySQL 伺服器。autoReconnect 功能已棄用,可能會在未來版本中移除。

16.5:我無法使用 Connector/J 連線到 MySQL 伺服器,而且我確定連線參數是正確的。

請確定您的伺服器上是否已啟用 skip_networking 系統變數。Connector/J 必須能夠透過 TCP/IP 與您的伺服器通訊;不支援具名 Socket。另請確保您沒有透過防火牆或其他網路安全性系統篩選連線。如需更多資訊,請參閱 無法連線到 [本機] MySQL 伺服器

16.6:更新包含 主索引鍵 的資料表,該索引鍵是 FLOAT 或使用 FLOAT 的複合主索引鍵,無法更新資料表並引發例外狀況。

Connector/J 會在 UPDATE 期間,將條件新增到 WHERE 子句,以檢查主索引鍵的舊值。如果沒有相符項目,則 Connector/J 會將此視為失敗條件並引發例外狀況。

問題在於,提供的數值與資料庫中儲存的值之間的捨入差異可能表示數值永遠不會相符,因此更新失敗。此問題會影響所有查詢,而不僅僅是來自 Connector/J 的查詢。

若要防止此問題,請使用未使用 FLOAT 的主索引鍵。如果您的主索引鍵中必須使用浮點數欄,請使用 DOUBLEDECIMAL 類型來取代 FLOAT

16.7:我收到 ER_NET_PACKET_TOO_LARGE 例外狀況,即使我想要使用 JDBC 插入的二進位 BLOB 大小安全地低於 max_allowed_packet 大小。

這是因為 com.mysql.cj.AbstractPreparedQuery.streamToBytes() 中的 hexEscapeBlock() 方法可能會使您的資料大小幾乎加倍。

16.8:如果我收到類似下列錯誤訊息,我應該怎麼做:通訊連結失敗 – 最後一個傳送到伺服器的封包是在 X 毫秒之前

一般而言,此錯誤表示網路連線已關閉。可能有數個根本原因

  • 防火牆或路由器可能會抑制閒置連線 (MySQL 用戶端/伺服器協定不會 ping)。

  • MySQL 伺服器可能會關閉超過 wait_timeoutinteractive_timeout 臨界值的閒置連線。

雖然網路連線可能會不穩定,但下列事項有助於避免問題

  • 確保從連線集區使用時,連線有效。使用以 /* ping */ 開頭的查詢來執行輕量型 ping,而不是完整查詢。請注意,ping 的語法必須與此處指定的完全相同。

  • 盡量減少在執行其他應用程式邏輯時,連線物件保持閒置的時間長度。

  • 如果連線已閒置一段時間,請在使用之前明確驗證連線。

  • 確保 wait_timeoutinteractive_timeout 設定得夠高。

  • 確保已啟用 tcpKeepalive

  • 確保任何可設定的防火牆或路由器逾時設定允許最大的預期連線閒置時間。

注意

如果連線已閒置一段時間,請不要期望能夠重複使用連線而不會發生問題。如果要重複使用閒置一段時間的連線,請確保在重複使用之前明確測試它。

16.9:即使我使用 autoReconnect 連線字串選項,為什麼 Connector/J 不重新連線到 MySQL 並在通訊失敗後重新發出陳述式,而是擲回例外狀況?

這有數個原因。第一個是交易完整性。MySQL 參考手冊指出沒有安全的方法可以重新連線到 MySQL 伺服器,而不會冒著連線狀態或資料庫狀態資訊損毀的風險。例如,請考慮下列一連串陳述式

conn.createStatement().execute(
  "UPDATE checking_account SET balance = balance - 1000.00 WHERE customer='Smith'");
conn.createStatement().execute(
  "UPDATE savings_account SET balance = balance + 1000.00 WHERE customer='Smith'");
conn.commit();

考慮在 UPDATEchecking_account 之後,與伺服器的連線失敗的情況。如果未擲回例外狀況,且應用程式永遠不會知道此問題,它將繼續執行。但是,伺服器在這種情況下沒有提交第一個交易,因此將會回復。但是,執行會繼續進行下一個交易,並將 savings_account 餘額增加 1000。應用程式沒有收到例外狀況,因此它會繼續進行,最終提交第二個交易,因為提交只適用於新連線中所做的變更。在此範例中,並非發生轉帳,而是發生存款。

請注意,啟用 autocommit 執行無法解決此問題。當 Connector/J 遇到通訊問題時,無法判斷伺服器是否已處理目前正在執行的陳述式。下列理論狀態同樣有可能

  • 伺服器從未收到陳述式,因此伺服器上沒有發生相關處理。

  • 伺服器收到陳述式,完整執行它,但用戶端未收到回應。

如果您在啟用 autocommit 的情況下執行,則當遇到通訊例外狀況時,無法保證伺服器上的資料狀態。陳述式可能已傳送到伺服器,也可能沒有。您只知道通訊在某個時間點失敗,在用戶端收到來自伺服器的確認 (或資料) 之前。這不僅會影響 autocommit 陳述式。如果通訊問題發生在 Connection.commit() 期間,就會出現一個問題,那就是交易是在通訊失敗之前提交到伺服器上,還是伺服器根本沒有收到提交請求。

產生例外狀況的第二個原因是交易範圍的關聯資料可能會受到損害,例如

  • 暫存表格。

  • 使用者定義的變數。

  • 伺服器端準備好的陳述式。

當連線失敗時,這些項目會遺失,如果連線在沒有產生例外狀況的情況下靜默地重新連線,這可能會不利於您應用程式的正確執行。

總而言之,通訊錯誤會產生 Connector/J 無法單純透過靜默重新連線而忽略的條件。必須通知應用程式。然後由應用程式開發人員決定在發生連線錯誤和失敗時如何處理。

16.10:我如何使用 Connector/J 的 3 位元組 UTF8?

由於沒有 Java 風格的字元集名稱可以與連線選項 charaterEncoding 一起使用,因此使用 utf8mb3 作為連線字元集的唯一方法是將 utf8mb3 定序 (例如,utf8_general_ci) 用於連線選項 connectionCollation,這會強制使用 utf8mb3 字元集。詳細資訊請參閱第 6.7 節,「使用字元集和 Unicode」

16.11:我如何使用 Connector/J 的 4 位元組 UTF8 (utf8mb4)?

若要使用 Connector/J 的 4 位元組 UTF8,請使用 character_set_server=utf8mb4 設定 MySQL 伺服器。如果未在連線字串中設定 characterEncodingconnectionCollation,則 Connector/J 會使用該設定。這相當於自動偵測字元集。詳細資訊請參閱第 6.7 節,「使用字元集和 Unicode」。您可以使用 characterEncoding=UTF-8 來使用 utf8mb4,即使伺服器上的 character_set_server 已設定為其他值也是如此。

16.12:使用 useServerPrepStmts=false 和某些字元編碼可能會導致插入 BLOB 時資料損毀。如何避免這種情況?

當使用某些字元編碼時,例如 SJIS、CP932 和 BIG5,BLOB 資料中可能包含可以被解讀為控制字元的字元,例如反斜線 '\'。這可能會導致將 BLOB 插入資料庫時資料損毀。要避免這種情況,需要執行兩件事:

  1. 將連線字串選項 useServerPrepStmts 設定為 true

  2. SQL_MODE 設定為 NO_BACKSLASH_ESCAPES