JDBC查询数据库——普通、流式、游标

news2024/9/20 9:29:11

问题

通过JDBC对MySQL进行数据查询时,有个很容易踩的坑,以下面代码为例:

    public static void selectNormal() throws SQLException{
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        PreparedStatement statement = connection.prepareStatement("select * from test",ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
        //statement.setFetchSize(100);
        ResultSet resultSet = statement.executeQuery();
        
        while(resultSet.next()){
            System.out.println(resultSet.getString(1));
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

 但是这段代码在查询结果数据条数较大时则会出现内存溢出OOM问题:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
   at com.mysql.jdbc.MysqlIO.nextRowFast(MysqlIO.java:2213)
   at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:1992)
   at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:3413)
   at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:471)
   at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:3115)
   at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:2344)
   at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2739)
   at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2486)
   at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858)
   at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1966)
   at com.cmbc.dap.dao.test.MysqlBatchTest.selectNormal(MysqlBatchTest.java:46)
   at com.cmbc.dap.dao.test.MysqlBatchTest.main(MysqlBatchTest.java:13)

 你可能会说设置fetchSize即可,但不幸的是,将上述代码设置fetchSize代码注释打开,依然会报出同样错误,fetchSize并没有生效,MySQL仍然一股脑将所有数据加载到内存,直到撑爆。
对于大数据量下查询,如果才能保证应用程序正确运行呢?寻根溯源,我们还是通过查看MySQL驱动源码来找答案。
 

MySQL驱动 查询实现原理

com.mysql.jdbc.PreparedStatement

public java.sql.ResultSet executeQuery() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {

            MySQLConnection locallyScopedConn = this.connection;

            checkForDml(this.originalSql, this.firstCharOfStmt);

            this.batchedGeneratedKeys = null;

            resetCancelledState();

            implicitlyCloseAllOpenResults();

            clearWarnings();

            if (this.doPingInstead) {
                doPingInstead();

                return this.results;
            }

            setupStreamingTimeout(locallyScopedConn);

            Buffer sendPacket = fillSendPacket();

            String oldCatalog = null;

            if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
                oldCatalog = locallyScopedConn.getCatalog();
                locallyScopedConn.setCatalog(this.currentCatalog);
            }

            //
            // Check if we have cached metadata for this query...
            //
            CachedResultSetMetaData cachedMetadata = null;
            if (locallyScopedConn.getCacheResultSetMetadata()) {
                cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql);
            }

            Field[] metadataFromCache = null;

            if (cachedMetadata != null) {
                metadataFromCache = cachedMetadata.fields;
            }

            locallyScopedConn.setSessionMaxRows(this.maxRows);

            this.results = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), true, metadataFromCache, false);

            if (oldCatalog != null) {
                locallyScopedConn.setCatalog(oldCatalog);
            }

            if (cachedMetadata != null) {
                locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, cachedMetadata, this.results);
            } else {
                if (locallyScopedConn.getCacheResultSetMetadata()) {
                    locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, null /* will be created */, this.results);
                }
            }
            this.lastInsertId = this.results.getUpdateID();

            return this.results;
        }
    }

上面代码中我们特别注意createStreamingResultSet方法,此方法返回是否创建流式结果集,即采用流式查询。流式查询与普通查询不同之处在于并不是一次性将所有数据加载到内存,在调用next()方法时,MySQL驱动只从网络数据流获取到1条数据,然后返回应用,这样就避免了内存溢出问题。我们看下该方法的实现:
 

 /**
     * We only stream result sets when they are forward-only, read-only, and the
     * fetch size has been set to Integer.MIN_VALUE
     * 
     * @return true if this result set should be streamed row at-a-time, rather
     *         than read all at once.
     */
    protected boolean createStreamingResultSet() {
        return ((this.resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
                && (this.fetchSize == Integer.MIN_VALUE));
    }

可以看到满足这三个条件即会采用流式查询,前面两个其实就是MySQL创建Statement的默认的游标类型,在PreparedStatement类我们可以看到

    private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;
    private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
    public java.sql.PreparedStatement prepareStatement(String sql)
            throws SQLException {
        return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE,
                DEFAULT_RESULT_SET_CONCURRENCY);
    }

因此创建statement,不指定后面两个参数默认也是满足流式查询的条件的。

PreparedStatement statement = connection.prepareStatement("select * from test");

而第三个条件却很奇怪,fetchSize必须为Integer.MIN_VALUE即-2147483648,而这样一个负数是MySQL自定义的的特殊含义值,在JDBC接口规范并无此说明。至此我们就知道了如何使用流式查询了,修改代码如下:

public static void selectStream() throws SQLException{
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        PreparedStatement statement = connection.prepareStatement("select * from test",ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
        statement.setFetchSize(Integer.MIN_VALUE);
        
        long begin = System.currentTimeMillis();
        ResultSet resultSet = statement.executeQuery();
        
        while(resultSet.next()){
            //System.out.println(resultSet.getString(1));
        }
        long end = System.currentTimeMillis();
        System.out.println("selectStream span time="+(end-begin) + "ms");
        
        resultSet.close();
        statement.close();
        connection.close();
    }

运行,果然解决了OOM问题,无论数据量多大,都可以正常查询了。

在StatementImpl中有enableStreamingResults()方法,该方法其实就是设置这三个条件的,网上很多文章介绍此种方式开启流式查询,但笔者不太推荐这种方式,因为需要强制转换为MySQL驱动中的StatementImpl类,这其实已经并非JDBC的标准接口。

     public void enableStreamingResults() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            this.originalResultSetType = this.resultSetType;
            this.originalFetchSize = this.fetchSize;

            setFetchSize(Integer.MIN_VALUE);
            setResultSetType(ResultSet.TYPE_FORWARD_ONLY);
        }
    }

 至此,我们已经知道如何使用流式查询解决大数据查询时的OOM问题,但流式查询的实现原理我们还不清楚,因此我们继续看源代码一探究竟,为了更方便展示方法调用层次,我画了一个调用序列图:

 

 

 我们直接看com.mysql.jdbc.MysqlIO中的getResultSet方法:

/**
     * Build a result set. Delegates to buildResultSetWithRows() to build a
     * JDBC-version-specific ResultSet, given rows as byte data, and field
     * information.
     *
     * @param callingStatement DOCUMENT ME!
     * @param columnCount the number of columns in the result set
     * @param maxRows the maximum number of rows to read (-1 means all rows)
     * @param resultSetType (TYPE_FORWARD_ONLY, TYPE_SCROLL_????)
     * @param resultSetConcurrency the type of result set (CONCUR_UPDATABLE or
     *        READ_ONLY)
     * @param streamResults should the result set be read all at once, or
     *        streamed?
     * @param catalog the database name in use when the result set was created
     * @param isBinaryEncoded is this result set in native encoding?
     * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
     *
     * @return a result set
     *
     * @throws SQLException if a database access error occurs
     */
    protected ResultSetImpl getResultSet(StatementImpl callingStatement,
        long columnCount, int maxRows, int resultSetType,
        int resultSetConcurrency, boolean streamResults, String catalog,
        boolean isBinaryEncoded, Field[] metadataFromCache)
        throws SQLException {
        Buffer packet; // The packet from the server
        Field[] fields = null;

        // Read in the column information

        if (metadataFromCache == null /* we want the metadata from the server */) {
            fields = new Field[(int) columnCount];

            for (int i = 0; i < columnCount; i++) {
                Buffer fieldPacket = null;

                fieldPacket = readPacket();
                fields[i] = unpackField(fieldPacket, false);
            }
        } else {
            for (int i = 0; i < columnCount; i++) {
                skipPacket();
            }
        }

        packet = reuseAndReadPacket(this.reusablePacket);
        
        readServerStatusForResultSets(packet);

        //
        // Handle cursor-based fetch first
        //

        if (this.connection.versionMeetsMinimum(5, 0, 2)
                && this.connection.getUseCursorFetch()
                && isBinaryEncoded
                && callingStatement != null
                && callingStatement.getFetchSize() != 0
                && callingStatement.getResultSetType() == ResultSet.TYPE_FORWARD_ONLY) {
            ServerPreparedStatement prepStmt = (com.mysql.jdbc.ServerPreparedStatement) callingStatement;

            boolean usingCursor = true;

            //
            // Server versions 5.0.5 or newer will only open
            // a cursor and set this flag if they can, otherwise
            // they punt and go back to mysql_store_results() behavior
            //

            if (this.connection.versionMeetsMinimum(5, 0, 5)) {
                usingCursor = (this.serverStatus &
                        SERVER_STATUS_CURSOR_EXISTS) != 0;
            }

            if (usingCursor) {
                RowData rows = new RowDataCursor(
                    this,
                    prepStmt,
                    fields);

                ResultSetImpl rs = buildResultSetWithRows(
                    callingStatement,
                    catalog,
                    fields,
                    rows, resultSetType, resultSetConcurrency, isBinaryEncoded);

                if (usingCursor) {
                    rs.setFetchSize(callingStatement.getFetchSize());
                }

                return rs;
            }
        }

        RowData rowData = null;

        if (!streamResults) {
            rowData = readSingleRowSet(columnCount, maxRows,
                    resultSetConcurrency, isBinaryEncoded,
                    (metadataFromCache == null) ? fields : metadataFromCache);
        } else {
            rowData = new RowDataDynamic(this, (int) columnCount,
                    (metadataFromCache == null) ? fields : metadataFromCache,
                    isBinaryEncoded);
            this.streamingData = rowData;
        }

        ResultSetImpl rs = buildResultSetWithRows(callingStatement, catalog,
                (metadataFromCache == null) ? fields : metadataFromCache,
            rowData, resultSetType, resultSetConcurrency, isBinaryEncoded);



        return rs;
    }

三种查询方式


上代码可以看到,MySQL驱动会根据不同的参数设置选择对应的ResultSet实现类,分别对应三种查询方式:

1.RowDataStatic 静态结果集,默认的查询方式,普通查询
2.RowDataDynamic 动态结果集,流式查询
3.RowDataCursor 游标结果集,服务器端基于游标查询
简单看下这几个类的实现代码:

方式1 普通查询
 

 private RowData readSingleRowSet(long columnCount, int maxRows, int resultSetConcurrency, boolean isBinaryEncoded, Field[] fields) throws SQLException {
        RowData rowData;
        ArrayList<ResultSetRow> rows = new ArrayList<ResultSetRow>();

        boolean useBufferRowExplicit = useBufferRowExplicit(fields);

        // Now read the data
        ResultSetRow row = nextRow(fields, (int) columnCount, isBinaryEncoded, resultSetConcurrency, false, useBufferRowExplicit, false, null);

        int rowCount = 0;

        if (row != null) {
            rows.add(row);
            rowCount = 1;
        }

        while (row != null) {
            row = nextRow(fields, (int) columnCount, isBinaryEncoded, resultSetConcurrency, false, useBufferRowExplicit, false, null);

            if (row != null) {
                if ((maxRows == -1) || (rowCount < maxRows)) {
                    rows.add(row);
                    rowCount++;
                }
            }
        }

        rowData = new RowDataStatic(rows);

        return rowData;
    }

可以看出,此种方式其实就是一次性把查询的所有结果集都保存在本地数组中,所以如果数据量太大,超过jvm内存,则会报文中篇头所示的OOM错误。

方式2 流式查询

每次只获取一条结果集,待应用处理完再次调用next()时,继续获取下一条数据,由代码可以看出流式查询获取数据的方法与普通查询其实是一样的( this.io.nextRow),不同之处在与普通查询时先获取所有数据,然后交给应用处理(next方法其实都是从内存数组遍历),而流式查询时逐条获取,待应用处理完再去拿下一条数据。
com.mysql.jdbc.RowDataDynamic
 

private void nextRecord() throws SQLException {

        try {
            if (!this.noMoreRows) {
                this.nextRow = this.io.nextRow(this.metadata, this.columnCount, this.isBinaryEncoded, java.sql.ResultSet.CONCUR_READ_ONLY, true,
                        this.useBufferRowExplicit, true, null);

                if (this.nextRow == null) {
                    this.noMoreRows = true;
                    this.isAfterEnd = true;
                    this.moreResultsExisted = this.io.tackOnMoreStreamingResults(this.owner);

                    if (this.index == -1) {
                        this.wasEmpty = true;
                    }
                }
            } else {
                this.nextRow = null;
                this.isAfterEnd = true;
            }
        } catch (SQLException sqlEx) {
            if (sqlEx instanceof StreamingNotifiable) {
                ((StreamingNotifiable) sqlEx).setWasStreamingResults();
            }

            // There won't be any more rows
            this.noMoreRows = true;

            // don't wrap SQLExceptions
            throw sqlEx;
        } catch (Exception ex) {
            String exceptionType = ex.getClass().getName();
            String exceptionMessage = ex.getMessage();

            exceptionMessage += Messages.getString("RowDataDynamic.7");
            exceptionMessage += Util.stackTraceToString(ex);

            SQLException sqlEx = SQLError.createSQLException(
                    Messages.getString("RowDataDynamic.8") + exceptionType + Messages.getString("RowDataDynamic.9") + exceptionMessage,
                    SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
            sqlEx.initCause(ex);

            throw sqlEx;
        }
    }

方式3 RowDataCursor 基于游标

从代码我们惊喜的发现,MySQL其实是支持游标查询的,这种方式下MySQL服务器端一次只发送fetchSize条数据,MySQL驱动会获取完fetchSize条数据后返回给应用,应用处理完继续调用next()时,继续发送fetch命令,继续获取下一批次fetchSize条数据。

 protected List<ResultSetRow> fetchRowsViaCursor(List<ResultSetRow> fetchedRows, long statementId, Field[] columnTypes, int fetchSize,
            boolean useBufferRowExplicit) throws SQLException {

        if (fetchedRows == null) {
            fetchedRows = new ArrayList<ResultSetRow>(fetchSize);
        } else {
            fetchedRows.clear();
        }

        this.sharedSendPacket.clear();

        this.sharedSendPacket.writeByte((byte) MysqlDefs.COM_FETCH);
        this.sharedSendPacket.writeLong(statementId);
        this.sharedSendPacket.writeLong(fetchSize);

        sendCommand(MysqlDefs.COM_FETCH, null, this.sharedSendPacket, true, null, 0);

        ResultSetRow row = null;

        while ((row = nextRow(columnTypes, columnTypes.length, true, ResultSet.CONCUR_READ_ONLY, false, useBufferRowExplicit, false, null)) != null) {
            fetchedRows.add(row);
        }

        return fetchedRows;
    }

我们看下基于游标的查询测试代码:(设置useCursorFetch=true,指定fetchSize)

public static void selectStreamWithUseCursorFetch() throws SQLException{
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useCursorFetch=true", "root", "123456");
        PreparedStatement statement = connection.prepareStatement("select * from test");
        statement.setFetchSize(10);
        
        long begin = System.currentTimeMillis();
        ResultSet resultSet = statement.executeQuery();
        
        
        while(resultSet.next()){
            //System.out.println(resultSet.getString(1));
        }
        
        long end = System.currentTimeMillis();
        System.out.println("selectStreamWithUseCursorFetch span time="+(end-begin) + "ms");
        resultSet.close();
        statement.close();
        connection.close();
    }

运行发现大数据量时这种方式也可正常运行。应用指定每次查询获取的条数fetchSize,MySQL服务器每次只查询指定条数的数据,因此单次查询相比与前面两种方式占用MySQL时间较短。但由于MySQL方不知道客户端什么时候将数据消费完,MySQL需要建立一个临时空间来存放每次查询出的数据,大数据量时MySQL服务器IOPS、磁盘占用都会飙升,而且需要与服务器进行更多次的网络通讯,因此最终查询效率是不如流式查询的。
 

本地测试查询100w数据,方式2与方式3执行时间对比:

selectStreamWithUseCursorFetch span time=507ms
selectStream span time=155ms

从结果上看,由于基于游标方式,服务器端需要更多额外处理,查询性能更低些,对于大数据量一般情况下推荐基于动态结果集的流式查询。

总结:
本文通过对MySQL驱动中查询模块的源码进行剖析,可知MySQL支持三种不同的查询方式,分别适用不同的场景,了解其各自优缺点后,才能在实际项目中正确使用。


一、普通查询

  • 优点:应用代码简单,数据量较小时操作速度快。
  • 缺点:数据量大时会出现OOM问题。

二、流式查询

  • 优点:大数据量时不会有OOM问题。
  • 缺点:占用数据库时间更长,导致网络拥塞的可能性较大。

三、游标查询

  • 优点:大数据量时不会有OOM问题,相比流式查询对数据库单次占用时间较短。
  • 缺点:相比流式查询,对服务端资源消耗更大,响应时间更长。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/583312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【一次调频】考虑储能电池参与一次调频技术经济模型的容量配置方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【IEEE/TOP录用】中科院2区TOP仅3个月录用~

一、【录用案例】 喜提一篇IEEE旗下中科院2区TOP录用&#xff1b; 2023/1/31提交&#xff0c;2023/5/26录用&#xff0c;仅3个月零26天录用&#xff1b; 传感器类SCIE&EI 【IF及分区】IF:4&#xff0c;JCR1/2区&#xff0c;中科院2区&#xff1b; 【检索情况】SCIE&E…

c语言编程练习题:7-42 整除光棍

#include <stdio.h>int main() {int x,s1,n1;scanf("%d",&x);while(s<x){ss*101;n;}while(1) {printf("%d",s/x); if(s%x0) break;s(s%x)*101;n;}printf(" %d",n);return 0; }代码来自&#xff1a;https://yunjinqi.top/article/16…

pdf如何转换成jpg图片?

pdf如何转换成jpg图片&#xff1f;PDF&#xff08;Portable Document Format&#xff09;是一种常见的文件格式&#xff0c;由Adobe Systems创建。与其他文档格式相比&#xff0c;它具有固定页面布局和跨平台兼容性等优点&#xff0c;因此广泛应用于电子书、论文、技术手册、合…

从外包到阿里,这 2 年 5 个月 13 天到底发生了什么?

个人背景&#xff1a; 本人毕业于浙江某二本院校&#xff0c;算是科班出身&#xff0c;毕业后就进了一家外包公司做测试&#xff0c;当然不是阿里的外包&#xff0c;具体什么公司就不透露了&#xff0c;在外包一呆就呆了整整 2 年多&#xff0c;直到现在才从外包离开&#xff…

Linux上jdk无法执行二进制文件及​gzip: stdin: invalid compressed data–format violated​报错

首先输入命令 java -version 直接给我报错&#xff1a;无法执行二进制文件 网上找了一堆&#xff0c;后面你得看看自己的Linux架构是什么&#xff0c;输入以下命令查看linux的操作系统版本 cat /proc/version 我当时华为云选择的服务器是openEuler。看到aarch64才反应过来我…

ATECLOUD芯片自动化测试平台,打破传统自动化测试5大问题

芯片测试通常包括以下几个步骤&#xff1a;设计验证测试&#xff08;Design Verification Testing&#xff09;&#xff1a;在设计阶段&#xff0c;通过一系列的仿真和验证测试来确保设计的正确性和可行性。这些测试可能包括电路板布局、逻辑分析、时序分析等。原型验证测试&am…

U盘超级加密3000忘记密码该怎么办?

作为一款专业的U盘加密软件&#xff0c;U盘超级加密3000是目前最受欢迎的U盘加密软件之一&#xff0c;它可以简单有效地加密U盘数据&#xff0c;最大程度的降低U盘数据泄露的风险。那么U盘超级加密3000忘记密码该怎么办呢&#xff1f; U盘超级加密3000一共有4处可以设置密码的场…

【Python】一个房贷计算器功能的小案例

题目要求&#xff1a; 房贷计算公式如下: 〉每月月供参考贷款金额[月利率(1&#xff0b;月利率)^还款月数]{[(1月利率)^还款月数]–1}>还款总额每月月供参考期限 12 ≥支付利息还款总额–贷款金额10000 以上计算方式中月利率(月利率利率12&#xff09;)指以月为计息周期计算…

Flutter的RawMaterialButton按钮

RawMaterialButton介绍 简介 Flutter的RawMaterialButton是一个具有原始材料样式的可点击的按钮控件。它可以用于创建自定义的按钮和交互元素&#xff0c;具有许多可自定义的属性。自定义更灵活。 重要属性 以下是RawMaterialButton的一些常用属性&#xff1a; onPressed&…

为什么要进行业务连续性管理?

为什么要进行业务连续性管理&#xff1f;在现代社会中&#xff0c;企业日常经营所面临的风险越来越多样化、复杂化。突发事件如自然灾害、供应链中断、网络攻击等都有可能对企业生产经营带来巨大影响&#xff0c;甚至导致公司破产倒闭。因此&#xff0c;保证业务连续性显得尤为…

GPT Demo 分享|日不落直播间接入虚拟人AI互动

摘要 虚拟人和数字人是人工智能技术在现实生活中的具体应用&#xff0c;它们可以为人们的生活和工作带来便利和创新。在直播间场景里&#xff0c;虚拟人和数字人可用于直播主播、智能客服、营销推广等。接入GPT的虚拟人像是加了超强buff&#xff0c;具备更强大的自然语言处理能…

面试:JS运行机制

浏览器端和node端的js运行机制执行的过程&#xff0c;进行两者的运行机制比较&#xff0c;以及同步任务和异步任务的说明&#xff0c;两种异步任务的必要性&#xff0c;以及各自有哪些回调&#xff0c;部分回调的优先级。 JS运行机制复述 首先js执行&#xff0c;会有一个函数执…

深度学习--优化器篇(超详细付代码测试流程包含:SGD,SGDM,NAG,Adagrad,RMSProp,Adam,Adadelta,Nadam等常用优化器)

引言 在深度学习过程中总会在代码中遇到优化器.Adam()&#xff0c;在上一篇实现卷积神经网络CNN的代码分析中也提到了优化器的概念,那么优化器如何通俗的理解呢&#xff1f;个人通俗理解(仅供参考)&#xff1a;为梯度下降法配置的一个的"领航员"&#xff0c;寻找模型…

realman——控制真实的机械臂

概述 MoveIt!完成运动规划后的输出接口是一个命名为“FollowJointTrajectory”的 action,其中包含了一系列规划好的路径点轨迹,与使用 MoveIt!控制 Gazebo中的机械臂不同的是,虚拟机械臂有 gazebo 的 ros_control 插件自动帮我们获取了follow_joint_trajectory 的动作 acti…

MVI56-GSC 串行增强通信模块

通用ASCII串行增强通信模块 MVI56E-GSC/GSCXT 通用ASCII串行增强通信模块允许Rockwell Automation ControlLogix可编程自动化控制器(PACs)使用非特定ASCII字符文本串或字节值串行通信协议轻松与串行设备连接。 MVI56E-GSC增强功能包括通过模块的以太网端口进行本地和远程诊断…

2023年高性能计算就业前景如何?IT人的机遇与挑战

在当今数字化时代&#xff0c;高性能计算&#xff08;HPC&#xff09;作为一项关键技术&#xff0c;正迅速成为各行各业的核心需求。不论是在职程序员还是在校大学生&#xff0c;懂高性能计算都将大大提升工作及科研、做课题的效率。而且加之2023年大模型的风靡&#xff0c;人工…

MySQL 连接的使用

MySQL 连接的使用 在前几章节中&#xff0c;我们已经学会了如何在一张表中读取数据&#xff0c;这是相对简单的&#xff0c;但是在真正的应用中经常需要从多个数据表中读取数据。 ​ 本章节我们将向大家介绍如何使用 MySQL 的 JOIN 在两个或多个表中查询数据。 你可以在 SEL…

炸裂开源:你肯定需要的6 个中文版 ChatGPT 提示教程。

提示工程&#xff08;Prompt Engineering&#xff09;是一门相对较新的学科&#xff0c;教你你如何开发、优化提示来更好的使用 GPT 等大模型、更好的将大模型的能力接入到你的系统中。 本文整理了 GitHub 上质量最高的 6 个【中文版】提示工程教程。请收藏、转发&#xff0c;…

vue3-lazy图片懒加载

vue3-lazy&#xff1a;https://github.com/ustbhuangyi/vue3-lazy 1、npm安装 npm install vue3-lazy -S2、main.js注册 import { createApp } from vue import App from ./app import lazyPlugin from vue3-lazyconst app createApp(App) app.use(lazyPlugin, {loading: l…