自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

數(shù)據(jù)庫(kù)中間件 MyCAT源碼分析 —— PreparedStatement 重新入門

數(shù)據(jù)庫(kù)
相信很多同學(xué)在學(xué)習(xí) JDBC 時(shí),都碰到 PreparedStatement 和 Statement。究竟該使用哪個(gè)呢?最終很可能是懵里懵懂的看了各種總結(jié),使用 PreparedStatement。那么本文,通過(guò) MyCAT 對(duì) PreparedStatement 的實(shí)現(xiàn)對(duì)大家能夠重新理解下。

1. 概述

相信很多同學(xué)在學(xué)習(xí) JDBC 時(shí),都碰到 PreparedStatement 和 Statement。究竟該使用哪個(gè)呢?最終很可能是懵里懵懂的看了各種總結(jié),使用 PreparedStatement。那么本文,通過(guò) MyCAT 對(duì) PreparedStatement 的實(shí)現(xiàn)對(duì)大家能夠重新理解下。

本文主要分成兩部分:

  1. JDBC Client 如何實(shí)現(xiàn) PreparedStatement。
  2. MyCAT Server 如何處理 PreparedStatement。

😈 Let's Go。

2. JDBC Client 實(shí)現(xiàn)

首先,我們來(lái)看一段大家最喜歡復(fù)制粘貼之一的代碼,JDBC PreparedStatement 查詢 MySQL 數(shù)據(jù)庫(kù):

  1. public class PreparedStatementDemo { 
  2.  
  3.     public static void main(String[] args) throws ClassNotFoundException, SQLException { 
  4.         // 1. 獲得數(shù)據(jù)庫(kù)連接 
  5.         Class.forName("com.mysql.jdbc.Driver"); 
  6.         Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:8066/dbtest?useServerPrepStmts=true""root""123456"); 
  7.  
  8.         // PreparedStatement 
  9.         PreparedStatement ps = conn.prepareStatement("SELECT id, username, password FROM t_user WHERE id = ?"); 
  10.         ps.setLong(1, Math.abs(new Random().nextLong())); 
  11.  
  12.         // execute 
  13.         ps.executeQuery(); 
  14.     } 
  15.  
  16.  

獲取 MySQL 連接時(shí),useServerPrepStmts=true 是非常非常非常重要的參數(shù)。如果不配置,PreparedStatement 實(shí)際是個(gè)假的 PreparedStatement(新版本默認(rèn)為 FALSE,據(jù)說(shuō)部分老版本默認(rèn)為 TRUE),未開啟服務(wù)端級(jí)別的 SQL 預(yù)編譯。

WHY ?來(lái)看下 JDBC 里面是怎么實(shí)現(xiàn)的。

  1. // com.mysql.jdbc.ConnectionImpl.java 
  2. public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 
  3.    synchronized (getConnectionMutex()) { 
  4.        checkClosed(); 
  5.  
  6.        PreparedStatement pStmt = null
  7.        boolean canServerPrepare = true
  8.        String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql) : sql; 
  9.  
  10.        if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) { 
  11.            canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); 
  12.        } 
  13.  
  14.        if (this.useServerPreparedStmts && canServerPrepare) { 
  15.            if (this.getCachePreparedStatements()) { // 從緩存中獲取 pStmt 
  16.                synchronized (this.serverSideStatementCache) { 
  17.                    pStmt = (com.mysql.jdbc.ServerPreparedStatement) this.serverSideStatementCache 
  18.                            .remove(makePreparedStatementCacheKey(this.database, sql)); 
  19.  
  20.                    if (pStmt != null) { 
  21.                        ((com.mysql.jdbc.ServerPreparedStatement) pStmt).setClosed(false); 
  22.                        pStmt.clearParameters(); // 清理上次留下的參數(shù) 
  23.                    } 
  24.  
  25.                    if (pStmt == null) { 
  26.                         // .... 省略代碼 :向 Server 提交 SQL 預(yù)編譯。 
  27.                    } 
  28.                } 
  29.            } else { 
  30.                try { 
  31.                    // 向 Server 提交 SQL 預(yù)編譯。 
  32.                    pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); 
  33.  
  34.                    pStmt.setResultSetType(resultSetType); 
  35.                    pStmt.setResultSetConcurrency(resultSetConcurrency); 
  36.                } catch (SQLException sqlEx) { 
  37.                    // Punt, if necessary 
  38.                    if (getEmulateUnsupportedPstmts()) { 
  39.                        pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); 
  40.                    } else { 
  41.                        throw sqlEx; 
  42.                    } 
  43.                } 
  44.            } 
  45.        } else { 
  46.            pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); 
  47.        } 
  48.  
  49.        return pStmt; 
  50.    } 
  51.  
  • 【前者】當(dāng) Client 開啟 useServerPreparedStmts 并且 Server 支持 ServerPrepare,Client 會(huì)向 Server 提交 SQL 預(yù)編譯請(qǐng)求。
  1. if (this.useServerPreparedStmts && canServerPrepare) { 
  2.     pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); 
  3. } 
  • 【后者】當(dāng) Client 未開啟 useServerPreparedStmts 或者 Server 不支持 ServerPrepare,Client 創(chuàng)建 PreparedStatement,不會(huì)向 Server 提交 SQL 預(yù)編譯請(qǐng)求。
  1. pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); 

即使這樣,究竟為什么性能會(huì)更好呢?

  • 【前者】返回的 PreparedStatement 對(duì)象類是 JDBC42ServerPreparedStatement.java,后續(xù)每次執(zhí)行 SQL 只需將對(duì)應(yīng)占位符?對(duì)應(yīng)的值提交給 Server即可,減少網(wǎng)絡(luò)傳輸和 SQL 解析開銷。
  • 【后者】返回的 PreparedStatement 對(duì)象類是 JDBC42PreparedStatement.java,后續(xù)每次執(zhí)行 SQL 需要將完整的 SQL 提交給 Server,增加了網(wǎng)絡(luò)傳輸和 SQL 解析開銷。

🌚:【前者】性能一定比【后者】好嗎?相信你已經(jīng)有了正確的答案。

3. MyCAT Server 實(shí)現(xiàn)

3.1 創(chuàng)建 PreparedStatement

該操作對(duì)應(yīng) Client conn.prepareStatement(....)。 

 

 

 

MyCAT 接收到請(qǐng)求后,創(chuàng)建 PreparedStatement,并返回 statementId 等信息。Client 發(fā)起 SQL 執(zhí)行時(shí),需要將 statementId 帶給 MyCAT。核心代碼如下:

  1. // ServerPrepareHandler.java 
  2. @Override 
  3. public void prepare(String sql) { 
  4. LOGGER.debug("use server prepare, sql: " + sql); 
  5.  
  6.    PreparedStatement pstmt = pstmtForSql.get(sql); 
  7.    if (pstmt == null) { // 緩存中獲取 
  8.        // 解析獲取字段個(gè)數(shù)和參數(shù)個(gè)數(shù) 
  9.        int columnCount = getColumnCount(sql); 
  10.        int paramCount = getParamCount(sql); 
  11.        pstmt = new PreparedStatement(++pstmtId, sql, columnCount, paramCount); 
  12.        pstmtForSql.put(pstmt.getStatement(), pstmt); 
  13.        pstmtForId.put(pstmt.getId(), pstmt); 
  14.    } 
  15.    PreparedStmtResponse.response(pstmt, source); 
  16. // PreparedStmtResponse.java 
  17. public static void response(PreparedStatement pstmt, FrontendConnection c) { 
  18.    byte packetId = 0; 
  19.  
  20.    // write preparedOk packet 
  21.    PreparedOkPacket preparedOk = new PreparedOkPacket(); 
  22.    preparedOk.packetId = ++packetId; 
  23.    preparedOk.statementId = pstmt.getId(); 
  24.    preparedOk.columnsNumber = pstmt.getColumnsNumber(); 
  25.    preparedOk.parametersNumber = pstmt.getParametersNumber(); 
  26.    ByteBuffer buffer = preparedOk.write(c.allocate(), c,true); 
  27.  
  28.    // write parameter field packet 
  29.    int parametersNumber = preparedOk.parametersNumber; 
  30.    if (parametersNumber > 0) { 
  31.        for (int i = 0; i < parametersNumber; i++) { 
  32.            FieldPacket field = new FieldPacket(); 
  33.            field.packetId = ++packetId; 
  34.            buffer = field.write(buffer, c,true); 
  35.        } 
  36.        EOFPacket eof = new EOFPacket(); 
  37.        eof.packetId = ++packetId; 
  38.        buffer = eof.write(buffer, c,true); 
  39.    } 
  40.  
  41.    // write column field packet 
  42.    int columnsNumber = preparedOk.columnsNumber; 
  43.    if (columnsNumber > 0) { 
  44.        for (int i = 0; i < columnsNumber; i++) { 
  45.            FieldPacket field = new FieldPacket(); 
  46.            field.packetId = ++packetId; 
  47.            buffer = field.write(buffer, c,true); 
  48.        } 
  49.        EOFPacket eof = new EOFPacket(); 
  50.        eof.packetId = ++packetId; 
  51.        buffer = eof.write(buffer, c,true); 
  52.    } 
  53.  
  54.    // send buffer 
  55.    c.write(buffer); 
  56.  

每個(gè)連接之間,PreparedStatement 不共享,即不同連接,即使 SQL相同,對(duì)應(yīng)的 PreparedStatement 不同。

3.2 執(zhí)行 SQL

該操作對(duì)應(yīng) Client conn.execute(....)。 

 

 

 

MyCAT 接收到請(qǐng)求后,將 PreparedStatement 使用請(qǐng)求的參數(shù)格式化成可執(zhí)行的 SQL 進(jìn)行執(zhí)行。偽代碼如下:

  1. String sql = pstmt.sql.format(request.params); 
  2.  
  3. execute(sql);  

核心代碼如下:

  1. // ServerPrepareHandler.java 
  2. @Override 
  3. public void execute(byte[] data) { 
  4.    long pstmtId = ByteUtil.readUB4(data, 5); 
  5.    PreparedStatement pstmt = null
  6.    if ((pstmt = pstmtForId.get(pstmtId)) == null) { 
  7.        source.writeErrMessage(ErrorCode.ER_ERROR_WHEN_EXECUTING_COMMAND, "Unknown pstmtId when executing."); 
  8.    } else { 
  9.        // 參數(shù)讀取 
  10.        ExecutePacket packet = new ExecutePacket(pstmt); 
  11.        try { 
  12.            packet.read(data, source.getCharset()); 
  13.        } catch (UnsupportedEncodingException e) { 
  14.            source.writeErrMessage(ErrorCode.ER_ERROR_WHEN_EXECUTING_COMMAND, e.getMessage()); 
  15.            return
  16.        } 
  17.        BindValue[] bindValues = packet.values
  18.        // 還原sql中的動(dòng)態(tài)參數(shù)為實(shí)際參數(shù)值 
  19.        String sql = prepareStmtBindValue(pstmt, bindValues); 
  20.        // 執(zhí)行sql 
  21.        source.getSession2().setPrepared(true); 
  22.        source.query(sql); 
  23.    } 
  24.  
  25. private String prepareStmtBindValue(PreparedStatement pstmt, BindValue[] bindValues) { 
  26.    String sql = pstmt.getStatement(); 
  27.    int[] paramTypes = pstmt.getParametersType(); 
  28.  
  29.    StringBuilder sb = new StringBuilder(); 
  30.    int idx = 0; 
  31.    for (int i = 0, len = sql.length(); i < len; i++) { 
  32.        char c = sql.charAt(i); 
  33.        if (c != '?') { 
  34.            sb.append(c); 
  35.            continue
  36.        } 
  37.        // 處理占位符? 
  38.        int paramType = paramTypes[idx]; 
  39.        BindValue bindValue = bindValues[idx]; 
  40.        idx++; 
  41.        // 處理字段為空的情況 
  42.        if (bindValue.isNull) { 
  43.            sb.append("NULL"); 
  44.            continue
  45.        } 
  46.        // 非空情況, 根據(jù)字段類型獲取值 
  47.        switch (paramType & 0xff) { 
  48.            case Fields.FIELD_TYPE_TINY: 
  49.                sb.append(String.valueOf(bindValue.byteBinding)); 
  50.                break; 
  51.            case Fields.FIELD_TYPE_SHORT: 
  52.                sb.append(String.valueOf(bindValue.shortBinding)); 
  53.                break; 
  54.            case Fields.FIELD_TYPE_LONG: 
  55.                sb.append(String.valueOf(bindValue.intBinding)); 
  56.                break; 
  57.            // .... 省略非核心代碼 
  58.         } 
  59.    } 
  60.  
  61.    return sb.toString(); 
  62.  

4. 彩蛋

💯 看到此處是不是真愛?!反正我信了。

給老鐵們額外加個(gè)🍗。

細(xì)心的同學(xué)們可能已經(jīng)注意到 JDBC Client 是支持緩存 PreparedStatement,無(wú)需每次都讓 Server 進(jìn)行創(chuàng)建。

當(dāng)配置 MySQL 數(shù)據(jù)連接 cachePrepStmts=true 時(shí)開啟 Client 級(jí)別的緩存。But,此處的緩存又和一般的緩存不一樣,是使用 remove 的方式獲得的,并且創(chuàng)建好 PreparedStatement 時(shí)也不添加到緩存。那什么時(shí)候添加緩存呢?在 pstmt.close() 時(shí),并且pstmt 是通過(guò)緩存獲取時(shí),添加到緩存。核心代碼如下:

  1. // ServerPreparedStatement.java 
  2. public void close() throws SQLException { 
  3.    MySQLConnection locallyScopedConn = this.connection
  4.  
  5.    if (locallyScopedConn == null) { 
  6.        return; // already closed 
  7.    } 
  8.  
  9.    synchronized (locallyScopedConn.getConnectionMutex()) { 
  10.        if (this.isCached && isPoolable() && !this.isClosed) { 
  11.            clearParameters(); 
  12.            this.isClosed = true
  13.            this.connection.recachePreparedStatement(this); 
  14.            return
  15.        } 
  16.  
  17.        realClose(truetrue); 
  18.    } 
  19. // ConnectionImpl.java 
  20. public void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException { 
  21.    synchronized (getConnectionMutex()) { 
  22.        if (getCachePreparedStatements() && pstmt.isPoolable()) { 
  23.            synchronized (this.serverSideStatementCache) { 
  24.                this.serverSideStatementCache.put(makePreparedStatementCacheKey(pstmt.currentCatalog, pstmt.originalSql), pstmt); 
  25.            } 
  26.        } 
  27.    } 
  28.  

為什么要這么實(shí)現(xiàn)?PreparedStatement 是有狀態(tài)的變量,我們會(huì)去 setXXX(pos, value),一旦多線程共享,會(huì)導(dǎo)致錯(cuò)亂。 

責(zé)任編輯:龐桂玉 來(lái)源: 芋艿V的博客
相關(guān)推薦

2017-07-26 09:41:28

MyCATSQLMongoDB

2017-07-18 17:07:40

數(shù)據(jù)庫(kù) MyCATJoin

2017-12-01 05:04:32

數(shù)據(jù)庫(kù)中間件Atlas

2017-11-27 05:36:16

數(shù)據(jù)庫(kù)中間件TDDL

2017-11-27 05:06:42

數(shù)據(jù)庫(kù)中間件cobar

2018-02-24 19:37:33

Java8數(shù)據(jù)庫(kù)中間件

2009-01-20 10:45:55

Oracle數(shù)據(jù)庫(kù)中間件

2011-08-10 13:03:58

CJDBC數(shù)據(jù)庫(kù)集群

2017-05-23 18:55:05

mysql-proxy數(shù)據(jù)庫(kù)架構(gòu)

2020-04-10 17:00:33

Mycat分庫(kù)分表SpringBoot

2017-12-01 05:40:56

數(shù)據(jù)庫(kù)中間件join

2017-11-27 06:01:37

數(shù)據(jù)庫(kù)中間件中間層

2017-12-11 13:30:49

Go語(yǔ)言數(shù)據(jù)庫(kù)中間件

2017-11-03 11:02:08

數(shù)據(jù)庫(kù)中間件

2017-11-30 08:56:14

數(shù)據(jù)庫(kù)中間件架構(gòu)師

2024-12-06 08:29:29

2021-07-27 05:49:59

MySQL數(shù)據(jù)庫(kù)中間件

2020-10-15 08:34:32

數(shù)據(jù)庫(kù)中間件漫談

2019-05-13 15:00:14

MySQLMyCat數(shù)據(jù)庫(kù)

2018-11-07 15:30:19

數(shù)據(jù)庫(kù)NewSQLNoSQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)