APE
文章标签归档关于

© 2026 APE.PUB

2026-07-06· 31 分钟

JDBC

javajdbc

JDBC 与数据库连接体系

从 JDBC 基础到分布式数据库连接治理的一份完整笔记。


目录

  • 一、JDBC 基础
  • 二、Java 建立 MySQL 连接的本质
  • 三、长连接 vs 短连接
  • 四、DriverManager vs DataSource
  • 五、为什么还要 Hikari/Druid
  • 六、Connection / Statement / ResultSet 的资源占用
  • 七、GC 与资源释放
  • 八、分布式场景下的连接数问题
  • 九、max_connections 与保护上限
  • 十、MySQL 水平扩容
  • 十一、应用规模与容量估算

一、JDBC 基础

1.1 JDBC 是什么

JDBC 本身只是 JDK 定义的一组 interface(在 java.sql 和 javax.sql 包里),不连接任何数据库。真正干活的是各家数据库厂商实现的驱动 jar。

    你的业务代码
         ↓ 调用
    java.sql.Connection / Statement / ResultSet  (interface,JDK 提供)
         ↓ 运行时绑定
    com.mysql.cj.jdbc.ConnectionImpl / ...       (mysql-connector-java 实现)
         ↓
    TCP Socket → MySQL Wire Protocol → 3306 端口

1.2 三层架构

应用层:  DAO / MyBatis / Hibernate
JDBC API 层 (java.sql / javax.sql):  DriverManager, Connection, Statement,
                                     PreparedStatement, ResultSet, DataSource
JDBC Driver 层 (厂商实现):  MySQL Connector/J, PostgreSQL JDBC, Oracle OJDBC
网络层:  TCP + 各家私有 Wire Protocol
数据库服务器

1.3 驱动类型(现代唯一选择:Type 4)

| 类型 | 名称 | 现状 |

|---|---|---|

| Type 1 | JDBC-ODBC 桥 | 淘汰,JDK 8 已移除 |

| Type 2 | Native-API 部分 Java | 淘汰 |

| Type 3 | 网络协议纯 Java | 极少见 |

| Type 4 | 纯 Java 网络协议 | 现代唯一选择 |

1.4 核心 API

DriverManager   → 老式驱动加载器 + 建连工厂
DataSource      → 新式建连工厂(推荐,支持连接池)
Connection      → 一条数据库会话
Statement       → SQL 执行器
    ↓ 继承
PreparedStatement    // 预编译 + 参数占位符,首选
    ↓ 继承
CallableStatement    // 调用存储过程
ResultSet       → 查询结果游标

1.5 完整最小样例

String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
try (Connection conn = DriverManager.getConnection(url, "user", "pwd");
     PreparedStatement ps = conn.prepareStatement(
         "SELECT id, name, age FROM users WHERE age > ? AND city = ?")) {

    ps.setInt(1, 18);
    ps.setString(2, "上海");

    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            long id = rs.getLong("id");
            String name = rs.getString("name");
        }
    }
}

1.6 三种 SQL 执行方法

| 方法 | 用途 | 返回 |

|---|---|---|

| executeQuery() | 只跑 SELECT | ResultSet |

| executeUpdate() | INSERT/UPDATE/DELETE/DDL | int(受影响行数) |

| execute() | 存储过程等多结果集 | boolean |

批量执行:

ps.setInt(1, 1); ps.addBatch();
ps.setInt(1, 2); ps.addBatch();
int[] counts = ps.executeBatch();

配合 MySQL 参数 rewriteBatchedStatements=true 才真正合并为一条 SQL。


二、Java 建立 MySQL 连接的本质

2.1 一句话概括

一个 JDBC Connection = 一条长期存活的 TCP 连接 + MySQL Wire Protocol 会话状态 + Java 对象封装。

┌─────────────────────────────────────────────────┐
│  Java 层:  Connection conn (JDBC 对象)          │
│           ↓ 内部持有                             │
│  MySQL 协议层: 会话状态 (登录身份/当前 schema/  │
│               事务/锁/预处理语句/session vars)   │
│           ↓ 通过                                 │
│  Socket 层:  java.net.Socket 对象                │
│           ↓ 封装                                 │
│  内核 TCP: <本机IP:随机端口, MySQL:3306>          │
│           状态 = ESTABLISHED, 长期不关            │
└─────────────────────────────────────────────────┘

2.2 建连时发生什么

DriverManager.getConnection(url, user, pwd) 一行,背后 6 个步骤:

| 步骤 | 干了什么 | 耗时量级 |

|---|---|---|

| 1. DNS 解析 | host → IP | 0~几十 ms |

| 2. TCP 三次握手 | SYN / SYN-ACK / ACK | 1 RTT (~1-10ms 内网) |

| 3. (可选) TLS 握手 | 加密协商 | 1-2 RTT |

| 4. MySQL 握手包 | Server Greeting | 1 RTT |

| 5. 认证 | HandshakeResponse | 1 RTT |

| 6. 会话初始化 | USE db / 字符集 / autocommit | 若干 round-trip |

合计:内网 ~10ms,跨机房 ~50-200ms。这就是"每次 SQL 都新建连接会被打死"的根本原因 —— SQL 本身 1ms,建连比查询本身还贵 10-100 倍。

2.3 建完之后是长连接

  • 内核 TCP 进入 ESTABLISHED 状态,只要没主动关就一直活
  • 上面反复跑 COM_QUERY / COM_STMT_EXECUTE,一条连接跑无数条 SQL
  • MySQL 端在 THD 里保存会话状态
  • 客户端和服务端都不主动关,直到 wait_timeout(默认 8h)或网络中断

2.4 必须搭配连接池

单条长连接同一时刻只能跑一条 SQL(MySQL 协议是半双工的)。HikariCP / Druid / tomcat-jdbc 就是干这个的:

  • 池启动时预建 N 条连接
  • getConnection() → 借,close() → 归还不真关
  • maxLifetime(HikariCP 默认 30 分钟)强制轮换
  • keepaliveTime 心跳保活,防 NAT 或防火墙静默丢弃

三、长连接 vs 短连接

3.1 现代主流:长连接

| 场景 | 组件 | 类型 |

|---|---|---|

| MySQL / Postgres | JDBC + 池 | 长连接 |

| Redis | Lettuce/Jedis | 长连接 |

| Kafka / RocketMQ | Client | 长连接 |

| Dubbo / gRPC | Netty | 长连接 |

| HTTP/1.1 | 默认 Keep-Alive | 长连接 |

| HTTP/2, HTTP/3 | 协议层强制 | 长连接 |

3.2 仍存在的短连接场景

  • DNS 查询(UDP,天然无连接)
  • Serverless / Lambda 冷启动
  • Webhook / 回调通知
  • K8s 健康检查探针
  • 一次性脚本 / CLI 工具
  • 传统 CGI 模式

3.3 长连接的代价

  • 需要连接池管理
  • 需要保活机制(TCP Keepalive / 应用心跳 / maxLifetime)
  • 需要处理僵尸连接(NAT 超时、防火墙静默丢弃)
  • 服务端资源占用(每连接一个 THD)
  • 优雅关闭复杂(drain 现有连接)

四、DriverManager vs DataSource

4.1 DriverManager 的 5 个硬伤

1. 静态方法,不面向对象 — 不能注入、不能 mock、不能装饰

2. 连接信息硬编码 — 环境切换要改代码,密码进 Git 历史

3. 没法用 JNDI — 企业级容器无法统一管理数据源

4. 没有扩展点 — 无法插入连接池、监控、路由、XA

5. 并发瓶颈 — 内部 synchronized

4.2 DataSource 的优势

javax.sql.DataSource                    // 基础接口 —— 应用直接用
    ├─ javax.sql.ConnectionPoolDataSource   // 物理连接工厂
    └─ javax.sql.XADataSource               // XA 分布式事务
  • 接口化,可实现、可包装、可代理
  • 可注入,Spring / 单元测试友好
  • JNDI 友好,应用服务器统一管理
  • 配置外置,不改代码切换环境
  • 装饰器模式的钩子 —— 连接池、监控、路由都基于这个

4.3 时间线

1997  JDBC 1.0   只有 DriverManager
1999  JDBC 2.0   引入 DataSource + ConnectionPoolDataSource + XADataSource
2000s            Apache DBCP、C3P0 出现
2012  HikariCP 发布
2013  Spring Boot 默认集成 HikariCP,事实标准

五、为什么还要 Hikari/Druid

5.1 厂商 DataSource 是什么

com.mysql.cj.jdbc.MysqlDataSource 只是把 DriverManager.getConnection() 换了个 OO 写法:

public Connection getConnection() throws SQLException {
    // 每次都打开新 TCP + MySQL 握手 → 返回新连接
    return new ConnectionImpl(...);
}

没有池,没有复用,没有监控。

5.2 Hikari/Druid 提供了什么

| 能力 | 厂商 DS | Hikari/Druid |

|---|:---:|:---:|

| 连接复用 | ❌ | ✅ |

| 并发控制 (maxPoolSize) | ❌ | ✅ |

| 健康检查 / 保活 | ❌ | ✅ |

| 超时治理 | ❌ | ✅ |

| 监控埋点 | ❌ | ✅ |

| SQL 拦截 / 防注入 | ❌ | ✅ (Druid) |

| 连接泄漏检测 | ❌ | ✅ (Hikari leakDetectionThreshold) |

| 支持 Spring 事务 | 事实上不能 | ✅ |

5.3 两种取物理连接的方式

Hikari 内部拿物理连接有两条路径:

路径 A (jdbcUrl):走 DriverManager → 找到 Driver → driver.connect(url, props)

路径 B (dataSourceClassName,官方推荐):反射 new MysqlDataSource() → setter → getConnection()

两条路最终都到 com.mysql.cj.jdbc.ConnectionImpl。

5.4 类比

  • 厂商 DataSource = 一家能烧砖的窑(要一块烧一块,不要就砸)
  • Hikari/Druid = 建材市场 + 仓库(预订、复用、监控、报警)
  • 分工不同,谁都替代不了谁

六、Connection / Statement / ResultSet 的资源占用

6.1 三者不是三个连接,是三层资源

Connection    = 一条数据库会话(TCP 连接 + 会话状态)
   ↓ 派生
Statement     = 会话上的一次"SQL 执行上下文"
   ↓ 派生
ResultSet     = SQL 执行返回的"结果集游标"

6.2 三者各占的独立资源(不重复)

| 资源 | Connection | Statement | ResultSet |

|---|:---:|:---:|:---:|

| 客户端 TCP fd | ✅ 独占 1 个 | ❌ | ❌ |

| 客户端 Socket 缓冲区 | ✅ | ❌ | ❌ |

| MySQL THD 线程/会话 | ✅ | ❌ | ❌ |

| MySQL Threads_connected 槽位 | ✅ | ❌ | ❌ |

| MySQL prepared statement 缓存 | ❌ | ✅ 每 PS 一份 | ❌ |

| MySQL statement_id 编号 | ❌ | ✅ 独占 | ❌ |

| MySQL 游标 / 临时表 / 排序缓冲 | ❌ | ❌ | ✅ 独占 |

| MySQL 待发送行内存 | ❌ | ❌ | ✅ |

6.3 数量独立累加

Connection (1 个)
   ↓ 占:1 fd + 1 THD + 1 连接槽位
   │
   ├── Statement #1  → MySQL:1 份 prepared_stmt + statement_id
   │      └── ResultSet A  → MySQL:1 份游标/临时表
   │      └── ResultSet B  → MySQL:1 份游标/临时表
   ├── Statement #2  → MySQL:1 份 prepared_stmt
   │      └── ResultSet C  → MySQL:1 份游标/临时表
   └── Statement #3  → MySQL:1 份 prepared_stmt
          └── ResultSet D  → MySQL:1 份游标/临时表

MySQL 端占用 = 1 Connection + N Statement + N×M ResultSet 份不同资源。

6.4 泄漏后果

| 资源 | 泄漏后果 |

|---|---|

| Connection | 打满 max_connections,全站崩;fd 耗尽 |

| Statement | 打满 max_prepared_stmt_count,新 prepare 失败 |

| ResultSet | 服务端临时资源堆积;同 Statement 无法继续执行 |

6.5 连接池下,conn.close() 只是归还池,不能级联释放服务端资源

这是最典型的"连接池 + 资源泄漏"事故根源。Statement 和 ResultSet 必须各自显式 close,不能指望 Connection 帮忙兜底。

6.6 正确姿势:try-with-resources

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    while (rs.next()) { ... }
}   // JVM 反向 close(): rs → ps → conn

6.7 MySQL 侧观察命令

SHOW GLOBAL STATUS LIKE 'Threads_connected';         -- 连接数
SHOW GLOBAL STATUS LIKE 'Prepared_stmt_count';       -- prepared 数
SELECT * FROM performance_schema.prepared_statements_instances;

七、GC 与资源释放

7.1 关键认知:Java 对象 ≠ 它占用的外部资源

Java 堆里 (GC 管):
  PreparedStatement 对象 (~500 字节)
     ↓ 持有引用
  Connection 对象 → Socket 对象 → 缓冲区

JVM 堆外 (GC 不管):
  ├─ OS file descriptor
  ├─ 内核 TCP 缓冲区
  └─ MySQL 服务端 THD / prepared cache / 游标 / 临时表

GC 只回收左边,不知道右边存在。

7.2 为什么不能靠 GC 清理

1. GC 触发条件是堆内存不够 — 而 PS 对象很小,堆压力小,GC 触发不了;但堆外资源已经泄漏成灾

2. GC 时机太晚 — 就算触发,服务早就崩了

3. finalize() 不可靠 — 时机不定,JDK 9 起废弃

4. 引用挂着 GC 收不了 — 池化 Connection 长期存活,挂在下面的 Statement 永远不 GC

7.3 生产事故时间线

依赖 GC 的写法:

  • T+15s fd 达到 1024,报 Too many open files
  • T+30s MySQL max_prepared_stmt_count 打满
  • T+10min GC 才动一下

5 秒挂,10 分钟才 GC。指望它救命?没戏。

7.4 正确的心智模型

| | 显式 close() | GC |

|---|---|---|

| 谁负责 | 应用代码 | JVM 后台线程 |

| 何时触发 | 立即 | 堆水位到阈值 |

| 清理什么 | 外部资源(fd/socket/服务端会话) | Java 对象内存 |

| 不做的后果 | 几秒到几分钟服务挂 | 堆 OOM,自动触发 |

两条清理管道,各司其职,谁都替代不了谁。

7.5 反直觉:大量短命对象 JVM 处理得很好

for (100 万次) { try (PS) { ... } } 完全没问题。JVM 最擅长处理短命对象,Young GC 秒回收几百万个。真正伤性能的不是"多创建对象",而是"少量长生命周期对象拉着大量外部资源不放"。


八、分布式场景下的连接数问题

8.1 应用集群 × 池 × 后端实例 → 连接数爆炸

假设:100 台 App × 每机每池 10 条 × 10 个 MySQL 实例。

| 架构 | 每 MySQL 实例连接数 |

|---|---|

| 单池随机路由(罕见) | ~100 |

| 每 MySQL 独立池 × 10(主流) | ~1000 |

| 分片架构,每机只连相关分片 | ~100-200 |

行业规律:App 机器数 × 每机池大小 = MySQL 连接负担,线性放大。

8.2 读写分离救不了 Master 连接数

读写分离拆的是SQL 请求分布,不是连接数。

改造前: 100 App × 30 → Master 3000 条
改造后: 100 App × (20 写 + 10 读×3) → Master 2000 条 + 每 Slave 1000 条

Master 从 3000 降到 2000,但仍数千级别,依然会撞 max_connections。总连接数反而涨了。

因为连接池是按并发峰值预留,不是按 QPS 预留 —— 就算 90% SQL 是读、走了 Slave,写池也不能设太小。

8.3 真正的解法(四招组合)

招 1:缩小应用池 size — HikariCP 官方主张 10-20 就够,连接数越少数据库越快

招 2:引入连接代理层(核心) — ProxySQL / MaxScale / RDS Proxy

App × 1000 条 → ProxySQL → MySQL 只看到 50-100 条(多路复用)

这是分布式系统里唯一真正解决"应用扩张 → DB 连接爆炸"的架构。

招 3:MySQL 8 Thread Pool 插件 — 让连接数 ≠ OS 线程数,缓解上下文切换

招 4:分库分表 — 终极解法,把连接数分散到 N 个 Master

8.4 各扩容手段对应的瓶颈

| 瓶颈 | 主从复制 | 读写分离 | ProxySQL | 分库分表 |

|---|:---:|:---:|:---:|:---:|

| 读 QPS | ✅ | ✅ | 部分 | ✅ |

| 写 QPS | ❌ | ❌ | ❌ | ✅ |

| 数据总量 | ❌ | ❌ | ❌ | ✅ |

| Master 连接数 | ❌ | ❌ | ✅ | ✅ |

| 应对 App 集群扩张 | ❌ | ❌ | ✅ | ✅ |


九、max_connections 与保护上限

9.1 什么是 max_connections

MySQL 允许的最大并发连接数。超过后新连接返回:

ERROR 1040 (HY000): Too many connections

默认 151(MySQL 5.7 / 8.0 都是),生产必然要大调。

9.2 修改方式

SET GLOBAL max_connections = 5000;   -- 运行时,重启失效
# /etc/my.cnf
[mysqld]
max_connections = 5000

9.3 大调的代价

  • 内存暴涨:每连接 1-2MB × 5000 = 5-10 GB,挤占 innodb_buffer_pool
  • OS 线程/上下文切换爆炸(MySQL 5.6- 每连接一线程)
  • 文件描述符跟着涨(要同步调 open_files_limit 和 ulimit)
  • 雪崩风险不减反增:一个慢 SQL 卡住,上千连接排队等锁

HikariCP 作者名言:"连接数越少,数据库越快"。

9.4 合理范围

| 场景 | 典型值 |

|---|---|

| 单机小应用 | 151 (默认足够) |

| 中等规模 App 集群 | 500 - 2000 |

| 大规模互联网 | 3000 - 8000 |

| 接入 Proxy | MySQL 端可以调小 |

9.5 本质:优雅拒绝 vs 雪崩崩溃

没有 max_connections:

  • 内存耗尽 → swap → CPU 100% 上下文切换 → OOM Killer → 全站崩
  • 渐变式死机,难恢复

有 max_connections:

  • 到达上限直接返回错误
  • MySQL 本身不崩,已有连接照常服务
  • 应用能明确感知,走降级
  • DBA 有时间介入

这是通用的"背压 / 限流 / 舱壁隔离" 模式:

过载时拒绝新请求 > 接受了但处理不完

同款设计到处都是:Nginx worker_connections、Tomcat max-threads、Redis maxclients、K8s memory limit、HikariCP maximumPoolSize。


十、MySQL 水平扩容

10.1 主从复制:只能扩读

Master (写)
   ├─ binlog → Slave 1 (读)
   ├─ binlog → Slave 2 (读)
   └─ binlog → Slave N (读)

能扩:读 QPS、报表 / OLAP、异地容灾

不能扩:写 QPS、数据总量(每从库都存全量)

上限:从库回放 binlog 有限并行 → 主从延迟 → 读到旧数据 → 只能回读主库

10.2 为什么写不能扩:数据一致性的根本矛盾

多主同写会产生冲突;强一致 + 高性能 + 水平扩展写不可兼得。MySQL 选择"强一致 + 单点写",牺牲写扩展性。

要水平扩写,只能分库分表。

10.3 分库分表:唯一真正的水平扩写

                业务读写
                   ↓
         分片路由 (按 sharding key)
      ↓      ↓      ↓      ↓
   Shard-0  Shard-1 ...   Shard-N
   (Master) (Master)     (Master)

每个分片独立 Master,只存全量数据的一部分。同时扩了写 QPS、数据量、连接数。

10.4 分库分表的代价

  • 分片键选择极难,选错要重构
  • 跨分片事务几乎不可能(要 XA / TCC / Saga)
  • JOIN 变得困难
  • 迁移和扩容痛苦(rehash)
  • 全局唯一 ID / 分页 / COUNT(*) / ORDER BY 都要重新设计
  • 运维成本几何级增长

能不分就不分,是撑不住之后的选择,不是一上来的选择。

10.5 渐进演进路径

① 单机 MySQL
     ↓ 读扛不住
② 主从复制 + 读写分离
     ↓ 主写扛不住 / 数据太大
③ 垂直分库(按业务)
     ↓ 单业务还扛不住
④ 水平分表
     ↓
⑤ 水平分库 + 水平分表  ← 本项目在这一阶段
     ↓ 或
⑥ 分布式数据库(TiDB / OceanBase / PolarDB-X)

10.6 本项目(Ctrip DAL)

App
   ↓
Ctrip DAL Client(应用层分片路由,不走网络代理)
   ↓
tomcat-jdbc 池(每分片独立池)
   ↓
每分片的 MySQL Master (db_cluster)

优势:无额外网络跳、性能最好。

劣势:无跨 App 连接复用,连接数仍为 App 数 × 池大小 × 分片数。


十一、应用规模与容量估算

11.1 App 机器规模参照(2026)

| 规模 | 单服务机器数 | 典型场景 |

|---|---|---|

| 玩具 / 个人项目 | 1-2 台 | 博客、demo |

| 小型创业公司 | 3-20 台 | 早期 SaaS |

| 中型互联网 | 20-200 台 | 垂直行业头部 |

| 中大型 | 200-1000 台 | 用户几千万级 |

| 大厂核心业务 | 1000-10000 台 | 淘宝下单、微信、抖音 |

| 超大规模 | 10000+ 台 | 春晚级 |

100 台:中等规模,不算大型。

11.2 单机 App 处理能力(参照)

| 类型 | 单机 QPS |

|---|---|

| Java 计算密集 | 500 - 2000 |

| Java IO 密集 | 2000 - 5000 |

| Java 纯缓存 | 5000 - 20000 |

| Golang / Rust | 同硬件下 2-3 倍 |

| PHP / Python 同步 | 同硬件下 1/3 - 1/2 |

11.3 容量计算三步

步骤 1:估单机能力(比如 Java Spring Boot ≈ 1500 QPS)

步骤 2:总 QPS / 单机能力

步骤 3:× 1.5-2 倍冗余(单机故障、突发流量、发布滚动、GC 抖动)

11.4 例:读 2w + 写几百 QPS

  • 净算:20500 / 1500 ≈ 14 台
  • 加冗余:20-30 台
  • 缓存做得好 + K8s 弹性伸缩:平峰 5-10 台,高峰 15-20 台

11.5 QPS 量级对应的瓶颈

| QPS | 主要瓶颈 |

|---|---|

| < 1000 | 无 |

| 1k-1w | App 单机吞吐、DB 连接池 |

| 1w-10w | DB 读、缓存命中率、GC 停顿 |

| 10w-100w | DB 写、消息队列、机房带宽 |

| 100w+ | 全组件需要专门优化 |

读 2w + 写几百:处在"App 单机吞吐 + DB 连接"层,不需要分库分表,MySQL 主从 + Redis 缓存足矣。


附录:常见误区速查

| 误区 | 真相 |

|---|---|

| "用了 DataSource 就不用驱动 jar" | ❌ 驱动 jar 必须在 classpath |

| "DriverManager 过时了没人用" | ❌ 很多连接池内部还在用 |

| "HikariDataSource 就是空壳" | ❌ 它内部还嵌了另一个 DataSource 或 Driver |

| "conn.close() 会自动关掉所有 Statement/ResultSet" | ⚠️ 池化场景失效,必须各自 close |

| "对象 GC 时 finalize 会兜底 close" | ❌ 不可靠,已废弃,别指望 |

| "多 new 对象 = 内存爆 = 慢" | ❌ 短命对象是 JVM 最擅长的场景 |

| "读写分离能解决 Master 连接数" | ❌ 只解决 QPS,连接数几乎没变 |

| "调大 max_connections 就能扛更多" | ⚠️ 有代价,内存和线程切换会崩 |

| "只要主从复制就能水平扩容" | ❌ 只扩读,写永远压在单 Master |

| "100 台 App 是大厂规模" | ❌ 大厂核心业务动辄几千上万台 |


附录:关键命令速查

-- 查连接数
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';

-- 查 prepared statement
SHOW GLOBAL STATUS LIKE 'Prepared_stmt_count';
SELECT * FROM performance_schema.prepared_statements_instances;

-- 查临时表
SHOW STATUS LIKE 'Created_tmp_tables';
SHOW STATUS LIKE 'Created_tmp_disk_tables';

-- 动态调 max_connections
SET GLOBAL max_connections = 5000;
# 查进程 fd 数
ls /proc/<pid>/fd | wc -l

# 查 fd 上限
cat /proc/<pid>/limits | grep "open files"

# 查 socket 数
ls -l /proc/<pid>/fd | grep socket | wc -l
← 返回首页