成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

1. 概述

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

本文主要分成兩部分:

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

😈 Let's Go。

2. JDBC Client 實現(xiàn)

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

  1. public class PreparedStatementDemo { 
  2.  
  3.     public static void main(String[] args) throws ClassNotFoundException, SQLException { 
  4.         // 1. 獲得數(shù)據(jù)庫連接 
  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 連接時,useServerPrepStmts=true 是非常非常非常重要的參數(shù)。如果不配置,PreparedStatement 實際是個假的 PreparedStatement(新版本默認為 FALSE,據(jù)說部分老版本默認為 TRUE),未開啟服務端級別的 SQL 預編譯。

WHY ?來看下 JDBC 里面是怎么實現(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 預編譯。 
  27.                    } 
  28.                } 
  29.            } else { 
  30.                try { 
  31.                    // 向 Server 提交 SQL 預編譯。 
  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.  
  • 【前者】當 Client 開啟 useServerPreparedStmts 并且 Server 支持 ServerPrepare,Client 會向 Server 提交 SQL 預編譯請求。
  1. if (this.useServerPreparedStmts && canServerPrepare) { 
  2.     pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); 
  3. } 
  • 【后者】當 Client 未開啟 useServerPreparedStmts 或者 Server 不支持 ServerPrepare,Client 創(chuàng)建 PreparedStatement,不會向 Server 提交 SQL 預編譯請求。
  1. pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); 

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

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

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

3. MyCAT Server 實現(xiàn)

3.1 創(chuàng)建 PreparedStatement

該操作對應 Client conn.prepareStatement(....)。 

 

 

 

MyCAT 接收到請求后,創(chuàng)建 PreparedStatement,并返回 statementId 等信息。Client 發(fā)起 SQL 執(zhí)行時,需要將 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.        // 解析獲取字段個數(shù)和參數(shù)個數(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.  

每個連接之間,PreparedStatement 不共享,即不同連接,即使 SQL相同,對應的 PreparedStatement 不同。

3.2 執(zhí)行 SQL

該操作對應 Client conn.execute(....)。 

 

 

 

MyCAT 接收到請求后,將 PreparedStatement 使用請求的參數(shù)格式化成可執(zhí)行的 SQL 進行執(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中的動態(tài)參數(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. 彩蛋

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

給老鐵們額外加個🍗。

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

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

  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.  

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

責任編輯:龐桂玉 來源: 芋艿V的博客
相關推薦

2017-07-26 09:41:28

MyCATSQLMongoDB

2017-07-18 17:07:40

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

2017-12-01 05:04:32

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

2017-11-27 05:36:16

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

2017-11-27 05:06:42

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

2018-02-24 19:37:33

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

2009-01-20 10:45:55

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

2011-08-10 13:03:58

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

2017-05-23 18:55:05

mysql-proxy數(shù)據(jù)庫架構

2017-11-27 06:01:37

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

2017-12-01 05:40:56

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

2017-12-11 13:30:49

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

2020-04-10 17:00:33

Mycat分庫分表SpringBoot

2017-11-03 11:02:08

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

2017-11-30 08:56:14

數(shù)據(jù)庫中間件架構師

2024-12-06 08:29:29

2019-05-13 15:00:14

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

2020-10-15 08:34:32

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

2021-07-27 05:49:59

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

2018-11-07 15:30:19

數(shù)據(jù)庫NewSQLNoSQL
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产一极毛片 | 成年人视频在线免费观看 | 2018中文字幕第一页 | 午夜成人免费视频 | 成人在线精品视频 | 国产精品欧美日韩 | 激情91| 97精品一区二区 | 亚洲免费视频在线观看 | www.887色视频免费 | 久久久精品影院 | 精品一区国产 | 中文字幕在线一区 | 欧美视频一区二区三区 | 精品久久久久久亚洲综合网 | 中文字幕高清av | 午夜在线视频一区二区三区 | 天堂素人约啪 | 免费黄色片在线观看 | 国产精品一区二区三区四区五区 | 国产精品综合视频 | 亚洲国产精品99久久久久久久久 | 国产精品久久国产精品久久 | av在线天堂网 | 成人精品视频 | 91在线精品一区二区 | 亚洲在线电影 | 国产日韩欧美精品一区二区三区 | 久久久91精品国产一区二区三区 | 伊色综合久久之综合久久 | 亚洲精品欧美精品 | 中文字幕视频在线看 | 欧美午夜精品久久久久久浪潮 | 999re5这里只有精品 | 国产欧美精品一区二区色综合 | 成人在线精品视频 | 超碰人人91 | 99热精品在线观看 | 免费av电影网站 | 一区二区av在线 | 日本成人在线观看网站 |