Streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming@3e72fd68 is still active.

背景

今天写一个并发查询的功能,但是报了如下的错误

java.sql.SQLException: Streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming@3e72fd68 is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.StatementImpl.executeSimpleNonQuery(StatementImpl.java:1241)
	at com.mysql.cj.jdbc.StatementImpl.setupStreamingTimeout(StatementImpl.java:632)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:947)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)

探索

奇怪的是,虽然是并发,但是我都使用了独立的 hikari 连接,并且每次查询完都主动 close 了连接和 statments,当然这里要注意下,随时使用了全新的 hikari 连接,但是因为它有连接池,所以其它线程依然可以重复使用空闲的数据库连接。

Spring 核心知识

注解源码解析

  • @Configuration 如何扫描注解,并且构建 bean
  • @ComponentScan 定义 filter, 源码查看如何扫描注释,使用过滤规则
  • @Bean 源码查看如何 parser bean 注解信息,如何 实例化,并调用自定义的 init 和 destroy。
  • 从 IOC 容器中获取 Bean 的过程
  • @Import 注解与 bean 注解类似,不过使用起来更灵活些
  • @PropertySource 如何解析自定义文件,并加入到 spring 环境中

Mysql基础知识学习

Innodb 索引结构

B+tree 相比红黑树可以用更少的磁盘 IO B+tree 相比 Hash 结构和 B-tree,支持范围查询

索引分为聚簇索引和二级索引。 聚簇索引就是包含所有的内容,表数据存于叶子节点上。

通过二级索引一般需要回表查询,但是如果是二级索引是覆盖索引,也就是包含了所有需要的字段,那么就可以不回表了。

联合索引并不是多个索引,而是一个索引结构,按书写顺序来的。

MVCC 原理

MVCC 依赖 undolog + readView + 数据库隐藏字段

readView 是开始读时候的快照,维护了开始时刻活跃事务 id

可见性就是依赖这个活跃事务 idList,如果当前事务(readView 中的相关事务 id) id 比最小的还小,那么可见,比最大的还大,那么不可见,如果在之间,那么就看在不在列表里,如果在,那么就不可见,不在就可见。

如果不可见,那么就会去找 undolog 中的版本链,找到合适的事务 id。比较方法应该还是如上所述。

Explain/ DESC 优化查询

extra 中 filesort 并不是文件排序,加了 order by 之后一般都有。

java.lang.OutOfMemoryError: unable to create native thread

背景

用户反馈系统运行一段时间后就出现了如下错误。

java.lang.RuntimeException: java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached

用户执行了几次查询后,就发现线程疯狂飙升,而且jvm 内存释放后,线程却没释放,内存还是不够。

这说明,jvm 虽然内存不高,但是由于线程太多,java 线程和操作系统线程有对应关系,相当于操作系统线程占了pod 内的内存,造成内存不够。

开始我以为是监控不准,后来进入到 pod 后,发现 jvm 内存确实不高。那么就是线程泄漏。

这是用户的 stack 信息,发现 ss-0,ss-1,ss-2 线程个有 400 +

"ss-0" #721 daemon prio=5 os_prio=0 cpu=57.90ms elapsed=155563.07s tid=0x00007fc79a22dbd0 nid=0x316 waiting on condition  [0x00007fc5329c8000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

"ss-1" #722 daemon prio=5 os_prio=0 cpu=42.84ms elapsed=155563.07s tid=0x00007fc79a22ff30 nid=0x317 waiting on condition  [0x00007fc5328c7000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

"ss-2" #723 daemon prio=5 os_prio=0 cpu=47.49ms elapsed=155563.07s tid=0x00007fc79a230ea0 nid=0x318 waiting on condition  [0x00007fc5327c6000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
	- parking to wait for  <0x000000040492a820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.2/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.2/ForkJoinPool.java:3463)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.2/ForkJoinPool.java:3434)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.2/AbstractQueuedSynchronizer.java:1623)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.2/ThreadPoolExecutor.java:1062)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.2/ThreadPoolExecutor.java:1122)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.2/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.2/Thread.java:833)

   Locked ownable synchronizers:
	- None

其实这里就可以推测出来是创建了 400 + 个线程池,但是没有回收。

远程编译

今天同事用局域网内闲置电脑搞了个远程编译环境。本地脚本如下,效果很好,编译再也不用卡了

#!/usr/bin/env zsh
usage() { echo "Usage: ./rb.sh [-t] [-rf <model>]" 1>&2; exit 1; }
arg="-Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -Dspotbugs.skip=true -Drat.skip=true -Djacoco.skip=true -DskipITs -DskipTests -Prelease"
while getopts "tr:" o; do
    case "${o}" in
        t) arg="-Drat.skip=true -Dcheckstyle.skip=false -T1" ;;
        r) rf="-rf ${OPTARG}" ;;
        *) usage ;;
    esac
done
shift $((OPTIND-1))
rsync -azh --progress --delete --exclude={'**/target','.git','.idea'} "$(pwd)" chuxin@llt-mbp.local:~/dev
rsync -azh --progress ~/.m2/ chuxin@llt-mbp.local:~/.m2/
# shellcheck disable=SC2087
ssh -tt -i /Users/chenchuxin/.ssh/id_rsa chuxin@llt-mbp.local << EOF
cd ~/dev/${PWD##*/}
./mvnw clean install $arg $rf
exit 0
EOF
rsync -azh --progress --include='**/target' chuxin@llt-mbp.local:~/dev/"${PWD##*/}"/ "$(pwd)"
rsync -azh --progress chuxin@llt-mbp.local:~/.m2/ ~/.m2

原理其实也很简单,就是使用 rsync 来同步,先将依赖同步过去,编译完后再将文件同步回来