后端面试题 - 网络与性能优化篇

4 阅读22分钟

一、网络协议基础

1.1 HTTP协议

Q1: HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3有什么区别?

答案:

HTTP/1.0 (1996年)

特点:

1. 短连接
   - 每次请求都要建立新的TCP连接
   - 请求完成后立即关闭连接

流程:
请求1: 建立连接 → 发送请求 → 接收响应 → 关闭连接
请求2: 建立连接 → 发送请求 → 接收响应 → 关闭连接
请求3: 建立连接 → 发送请求 → 接收响应 → 关闭连接

问题:
- 每次建立连接需要TCP三次握手 (耗时100ms+)
- 关闭连接需要四次挥手
- 性能极差!

示例:
加载一个网页 (1个HTML + 10张图片 + 5个CSS + 5个JS)
- 需要建立21次TCP连接
- 总耗时: 21 × 100ms = 2100ms (光是建立连接就要2秒!)

HTTP/1.1 (1997年,至今仍广泛使用)

改进点:

1. 长连接 (Keep-Alive)

默认开启持久连接:
Connection: keep-alive

流程:
建立连接 → 请求1 → 响应1 → 请求2 → 响应2 → 请求3 → 响应3 → 关闭连接

优势:
- 一次TCP连接可以发送多个请求
- 减少连接建立和关闭的开销
- 性能提升5-10倍!

配置:
nginx:
  keepalive_timeout 65;  # 连接空闲65秒后关闭
  keepalive_requests 100;  # 单个连接最多100个请求

2. 管道化 (Pipelining)

定义: 不等待响应,连续发送多个请求

HTTP/1.0:
请求1 → 等待响应1 → 请求2 → 等待响应2 → 请求3 → 等待响应3

HTTP/1.1 (管道化):
请求1 → 请求2 → 请求3 → 响应1 → 响应2 → 响应3

问题:
- 队头阻塞 (Head-of-Line Blocking)
- 响应必须按请求顺序返回
- 如果响应1慢,响应2和响应3也被阻塞

实际:
- 浏览器默认不开启管道化 (bug太多)
- 实际作用有限

3. 分块传输 (Chunked Transfer Encoding)

定义: 响应数据分块发送,不需要预先知道总长度

示例: 动态生成的内容
HTTP/1.1 200 OK
Transfer-Encoding: chunked

5\r\n
Hello\r\n
6\r\n
 World\r\n
0\r\n
\r\n

应用场景:
- 流式响应 (Server-Sent Events)
- 大文件下载
- 动态内容生成

4. 更多缓存策略

HTTP/1.0: Expires
HTTP/1.1: Cache-Control, ETag, If-None-Match, If-Modified-Since

示例:
# 强缓存
Cache-Control: max-age=3600  # 缓存1小时

# 协商缓存
ETag: "abc123"
If-None-Match: "abc123"  # 服务器返回304 Not Modified

HTTP/2 (2015年)

核心改进:

1. 二进制分帧 (Binary Framing)

HTTP/1.1: 文本协议
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n

HTTP/2: 二进制协议
+-------+-------+-------+-------+
| Frame Type | Flags | Stream ID |
+-------+-------+-------+-------+
| Payload                       |
+-------------------------------+

优势:
- 解析更高效 (机器友好)
- 压缩率更高
- 更少的错误

2. 多路复用 (Multiplexing)

HTTP/1.1:
连接1: 请求1 → 响应1 (阻塞)
连接2: 请求2 → 响应2 (阻塞)
连接3: 请求3 → 响应3 (阻塞)

HTTP/2:
单连接: 请求1、请求2、请求3 → 响应1、响应2、响应3 (并发,不阻塞)

实现:
- 每个请求分配一个Stream ID
- 帧(Frame)带Stream ID
- 接收方根据Stream ID重组响应

示例:
浏览器加载网页 (100个资源):
- HTTP/1.1: 6个并发连接,需要17轮 (100/6)
- HTTP/2: 1个连接,1轮即可完成!

性能提升: 50-80%!

3. 头部压缩 (HPACK)

HTTP/1.1:
每次请求都发送完整的头部:
GET /api/user HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
Accept: application/json
Cookie: session=abc123...
(约1-2KB)

HTTP/2:
使用HPACK压缩算法:
- 静态表: 常见头部(如:method, :path)预定义
- 动态表: 之前出现过的头部,用索引代替
- 霍夫曼编码: 进一步压缩

示例:
请求1:
:method: GET
:path: /api/user
cookie: session=abc123

请求2 (只发送差异):
:path: /api/product  (只有path变化,其他用索引)

压缩率: 80-90%,头部从2KB → 200B!

4. 服务器推送 (Server Push)

定义: 服务器主动推送资源,不等客户端请求

传统流程:
客户端请求 index.html → 服务器返回 →
客户端解析,发现需要 style.css → 请求 style.css → 服务器返回

HTTP/2流程:
客户端请求 index.html →
服务器返回 index.html + 主动推送 style.css, script.js

代码示例 (Node.js):
const http2 = require('http2');
const server = http2.createSecureServer(options);

server.on('stream', (stream, headers) => {
  if (headers[':path'] === '/index.html') {
    // 推送CSS
    stream.pushStream({ ':path': '/style.css' }, (err, pushStream) => {
      pushStream.respondWithFile('/path/to/style.css');
    });

    // 推送JS
    stream.pushStream({ ':path': '/script.js' }, (err, pushStream) => {
      pushStream.respondWithFile('/path/to/script.js');
    });

    // 返回HTML
    stream.respondWithFile('/path/to/index.html');
  }
});

优势:
- 减少往返时间 (RTT)
- 提升首屏加载速度

问题:
- 可能推送用户已缓存的资源 (浪费带宽)
- 浏览器支持有限
- 实际使用较少

5. 流优先级 (Stream Priority)

定义: 为不同请求设置优先级,优先处理重要资源

示例:
优先级高: HTML, CSS (阻塞渲染)
优先级中: JavaScript
优先级低: 图片

浏览器请求:
HTML (权重256)
CSS (权重220)
JS (权重147)
Image (权重110)

服务器优先返回HTML和CSS,再返回JS和Image

提升首屏渲染速度!

HTTP/2的局限性:

1. TCP队头阻塞 (TCP Head-of-Line Blocking)
   - HTTP/2解决了HTTP层的队头阻塞
   - 但TCP层仍有队头阻塞

原理:
TCP是字节流,必须按顺序交付
- 丢包1: 包1丢失,包2和包3已到达
- TCP必须等包1重传后,才能交付包2和包3
- 即使包2和包3属于不同的HTTP/2 Stream

结果:
- 弱网环境(丢包率5%),HTTP/2性能不如HTTP/1.1
- HTTP/1.1多连接,一个连接丢包不影响其他连接
- HTTP/2单连接,一个包丢失阻塞所有Stream

2. 连接建立慢
   - TCP三次握手
   - TLS握手 (4次)
   - 总共: 2-3个RTT (200-300ms)

HTTP/3 (2022年,基于QUIC协议)

核心改进:

1. 基于UDP

HTTP/1.1, HTTP/2: 基于TCP
HTTP/3: 基于UDP + QUIC协议

为什么用UDP?
- TCP协议固化在操作系统内核,难以修改
- UDP灵活,可以在用户空间实现QUIC协议
- QUIC = Quick UDP Internet Connections

2. 解决TCP队头阻塞

QUIC的多路复用:
- 每个Stream独立,互不影响
- Stream1丢包,只阻塞Stream1
- Stream2和Stream3继续传输

TCP vs QUIC:
TCP:
  丢包 → 重传 → 等待 → 阻塞所有数据

QUIC:
  Stream1丢包 → 重传Stream1
  Stream2正常传输 (不阻塞)
  Stream3正常传输 (不阻塞)

性能提升:
- 弱网环境 (丢包率5%): HTTP/3比HTTP/2快30-50%

3. 0-RTT连接建立

传统HTTPS (HTTP/2):
RTT1: TCP SYN → SYN-ACK → ACK
RTT2: TLS Client Hello → Server Hello
RTT3: TLS Key Exchange → Change Cipher Spec
RTT4: 发送HTTP请求 → 接收响应

总耗时: 3-4个RTT (300-400ms)

QUIC (HTTP/3):
首次连接:
RTT1: Client Hello (QUIC + TLS) → Server Hello + 数据
RTT2: 接收数据

后续连接 (0-RTT):
RTT0: 直接发送加密数据 (使用之前的密钥)

总耗时: 0-1个RTT (0-100ms)

性能提升: 连接建立速度提升3-4倍!

4. 连接迁移 (Connection Migration)

TCP连接标识:
(源IP, 源端口, 目标IP, 目标端口)

问题:
- 手机从WiFi切换到4G
- IP地址变化
- TCP连接断开,需要重新建立

QUIC连接标识:
Connection ID (64位随机数)

优势:
- 手机从WiFi切换到4G
- Connection ID不变
- 连接继续保持,无需重建!

应用场景:
- 移动网络切换
- NAT重绑定
- 负载均衡IP变化

5. 改进的拥塞控制

TCP拥塞控制:
- 慢启动, 拥塞避免, 快速重传, 快速恢复
- 在内核实现,难以优化

QUIC拥塞控制:
- 在用户空间实现
- 可以快速迭代和优化
- 更精确的RTT测量
- 更好的丢包恢复

算法:
- BBR (Bottleneck Bandwidth and RTT)
- Cubic
- Reno

性能:
- 吞吐量提升10-30%
- 延迟降低20-40%

HTTP版本对比总结:

特性HTTP/1.0HTTP/1.1HTTP/2HTTP/3
连接短连接长连接长连接长连接 (基于UDP)
并发串行串行 (可管道)多路复用多路复用
队头阻塞HTTP层+TCP层HTTP层+TCP层仅TCP层
头部压缩HPACKQPACK
协议格式文本文本二进制二进制
传输层TCPTCPTCPQUIC (UDP)
连接建立3个RTT3个RTT3个RTT0-1个RTT
性能提升基准5-10倍50-80倍100-150倍

实际应用建议:

1. HTTP/1.1 → HTTP/2迁移 (强烈推荐)
   - 性能提升明显
   - 无需修改应用代码
   - 只需升级Web服务器 (Nginx 1.9.5+)
   - 浏览器兼容性好 (95%+)

配置示例 (Nginx):
server {
    listen 443 ssl http2;  # 开启HTTP/2
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
}

2. HTTP/2 → HTTP/3迁移 (谨慎)
   - 需要浏览器支持 (Chrome 87+, Firefox 88+)
   - 需要Web服务器支持 (Nginx 1.25.0+, Caddy 2.0+)
   - 弱网环境提升明显
   - 强网环境提升有限

配置示例 (Nginx):
server {
    listen 443 quic reuseport;  # HTTP/3 (QUIC)
    listen 443 ssl http2;        # HTTP/2回退
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    add_header Alt-Svc 'h3=":443"; ma=86400';  # 告知浏览器支持HTTP/3
}

3. 优化建议
   - 强网环境: HTTP/2足够
   - 弱网环境: HTTP/3提升明显
   - 移动端: HTTP/3优先 (连接迁移)
   - CDN: 优先支持HTTP/3 (Cloudflare, Fastly等已支持)

常见问题:

Q: HTTP/2一定比HTTP/1.1快吗?
A: 不一定
   - 强网环境: HTTP/2快50-80%
   - 弱网环境 (丢包率>5%): 可能更慢 (TCP队头阻塞)
   - 建议: 同时支持HTTP/2和HTTP/1.1,让浏览器选择

Q: 需要修改代码吗?
A: 不需要
   - HTTP协议对应用透明
   - 只需升级Web服务器
   - 应用代码无需修改

Q: 是否需要HTTPS?
A: HTTP/2理论上支持HTTP,但主流浏览器只支持HTTPS
   - Chrome, Firefox, Safari只支持HTTP/2 over TLS
   - 事实上,HTTP/2 = HTTPS

Q: HTTP/3什么时候能普及?
A: 已经在普及中
   - Google, Facebook, Cloudflare已大规模使用
   - 浏览器支持率: 90%+ (2024)
   - 预计2-3年内成为主流

1.2 HTTPS与安全

Q2: HTTPS的加密流程是怎样的?如何防止中间人攻击?

答案:

HTTPS = HTTP + SSL/TLS

加密流程 (TLS 1.2):

阶段1: TCP三次握手

客户端 → SYN → 服务器
服务器 → SYN-ACK → 客户端
客户端 → ACK → 服务器

耗时: 1个RTT (50-100ms)

阶段2: TLS握手

步骤1: Client Hello

客户端 → 服务器

内容:
- TLS版本: TLS 1.2
- 支持的加密套件: [TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, ...]
- 客户端随机数: Random_C (用于后续生成密钥)
- 支持的压缩算法
- SNI (Server Name Indication): example.com (支持虚拟主机)

作用:
- 告知服务器客户端能力
- 协商加密算法

步骤2: Server Hello + Certificate + Server Hello Done

服务器 → 客户端

内容:
- TLS版本: TLS 1.2
- 选择的加密套件: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- 服务器随机数: Random_S
- 服务器证书 (包含公钥)
- Server Hello Done

作用:
- 确定使用的TLS版本和加密套件
- 发送证书给客户端验证

步骤3: 客户端验证证书

客户端验证:
1. 证书是否在有效期内
2. 证书域名是否匹配
3. 证书是否被吊销 (CRL或OCSP)
4. 证书是否由受信任的CA签发

证书链验证:
example.com证书 (服务器证书)
    ↓ 签发者
中间CA证书
    ↓ 签发者
根CA证书 (浏览器内置)

验证过程:
1. 用中间CA的公钥验证服务器证书的签名
2. 用根CA的公钥验证中间CA证书的签名
3. 根CA证书是浏览器内置的受信任证书

如果验证失败:
- 浏览器显示"您的连接不是私密连接"
- 用户可以选择继续或退出

步骤4: Client Key Exchange

客户端 → 服务器

客户端生成:
- Pre-Master Secret (随机数)

客户端用服务器公钥加密:
- Encrypted Pre-Master Secret

发送给服务器:
- 服务器用私钥解密,得到Pre-Master Secret

双方都有:
- Random_C (客户端随机数)
- Random_S (服务器随机数)
- Pre-Master Secret (预主密钥)

双方各自计算:
Master Secret = PRF(Pre-Master Secret, "master secret", Random_C + Random_S)

再从Master Secret派生:
- 客户端写密钥 (Client Write Key)
- 服务器写密钥 (Server Write Key)
- 客户端写MAC密钥
- 服务器写MAC密钥
- 客户端写IV (初始化向量)
- 服务器写IV

作用:
- 双方协商出对称密钥
- 后续使用对称加密通信 (AES)

步骤5: Change Cipher Spec + Finished

客户端 → 服务器

内容:
- Change Cipher Spec: 后续消息使用协商的密钥加密
- Finished: 握手完成,发送加密的握手摘要

服务器 → 客户端

内容:
- Change Cipher Spec
- Finished

作用:
- 确认握手完成
- 开始加密通信

阶段3: 加密通信

客户端 → 服务器: GET /index.html HTTP/1.1 (AES加密)
服务器 → 客户端: HTTP/1.1 200 OK (AES加密)

使用对称加密 (AES):
- 速度快 (比RSA快1000倍)
- 安全性高 (AES-128足够安全)

完整流程时间消耗:

TCP握手: 1 RTT (50ms)
TLS握手: 2 RTT (100ms)
HTTP请求: 1 RTT (50ms)

总耗时: 4 RTT (200ms)

优化:
- TLS 1.3: 1 RTT握手 (节省50ms)
- 0-RTT: 重用会话密钥 (节省100ms)
- HTTP/3: 0-1 RTT连接 (节省150-200ms)

为什么需要非对称加密 + 对称加密?

只用对称加密 (AES):
问题: 密钥如何安全传输?
- 客户端和服务器如何协商密钥?
- 明文传输密钥 → 被窃听,不安全

只用非对称加密 (RSA):
问题: 性能太差
- RSA加密速度是AES的1/1000
- 大量数据加密,CPU负载极高

混合加密 (推荐):
1. 非对称加密 (RSA): 传输对称密钥 (只传一次,数据量小)
2. 对称加密 (AES): 传输应用数据 (速度快)

兼顾安全性和性能!

如何防止中间人攻击?

攻击场景:

正常流程:
客户端 ←→ 服务器

中间人攻击:
客户端 ←→ 攻击者 ←→ 服务器

攻击者操作:
1. 拦截客户端请求
2. 转发给服务器
3. 拦截服务器响应
4. 转发给客户端
5. 窃听和篡改所有数据!

防御措施:

1. 证书验证 (核心防御)

攻击者伪造证书:
客户端: 请求 example.com
攻击者: 返回伪造的证书 (攻击者自己的公钥)

客户端验证:
1. 检查证书签名
2. 发现证书不是受信任的CA签发
3. 浏览器警告: "您的连接不是私密连接"
4. 攻击被阻止!

关键:
- 根CA证书是浏览器内置的,攻击者无法伪造
- 证书签名无法伪造 (需要CA的私钥)

2. 证书绑定 (Certificate Pinning)

定义: 客户端预先知道服务器证书或CA

实现:
# iOS
let session = URLSession(
    configuration: .default,
    delegate: CertificatePinner(),
    delegateQueue: nil
)

class CertificatePinner: NSObject, URLSessionDelegate {
    let expectedCertHash = "sha256/AAAAAAAAAA..."

    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge) {
        // 验证证书哈希
        if getCertHash(challenge.protectionSpace.serverTrust) == expectedCertHash {
            // 通过
        } else {
            // 拒绝连接
        }
    }
}

优势:
- 即使CA被攻破,也能防御
- 金融App常用

劣势:
- 证书更新需要发布App
- 证书过期导致App无法使用

3. HSTS (HTTP Strict Transport Security)

定义: 强制使用HTTPS,防止降级攻击

响应头:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

含义:
- max-age: 1年内强制HTTPS
- includeSubDomains: 包括所有子域名
- preload: 加入浏览器预加载列表

攻击场景:
用户访问: http://example.com (HTTP)
攻击者拦截,不转发到HTTPS
攻击者返回伪造的HTTP页面

有HSTS:
用户第一次访问: https://example.com
服务器返回: Strict-Transport-Security: max-age=31536000
浏览器记住: example.com只能用HTTPS
后续访问: 用户输入http://example.com
浏览器自动转换: https://example.com
攻击者无法拦截!

HSTS Preload:
- 浏览器内置HSTS域名列表
- 首次访问就强制HTTPS
- 提交到: https://hstspreload.org/

4. 检查证书吊销状态

CRL (Certificate Revocation List):
- 证书吊销列表
- 浏览器定期下载
- 检查证书是否被吊销

问题:
- CRL文件可能很大 (MB级)
- 更新不及时

OCSP (Online Certificate Status Protocol):
- 在线证书状态协议
- 浏览器实时查询证书状态
- 请求: 证书序列号
- 响应: Good / Revoked / Unknown

问题:
- 隐私泄露 (CA知道用户访问哪些网站)
- 性能开销 (额外的网络请求)

OCSP Stapling:
- 服务器定期查询OCSP
- 服务器在TLS握手时附带OCSP响应
- 客户端直接验证,无需额外请求

优势:
- 性能好 (无额外请求)
- 隐私保护 (CA不知道用户访问)

配置 (Nginx):
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/ca-cert.pem;

5. 防止SSLStrip攻击

攻击原理:
用户访问: http://bank.com
攻击者拦截,返回: HTTP页面 (不转HTTPS)
用户输入密码,攻击者窃听

防御:
1. HSTS: 强制HTTPS
2. HSTS Preload: 首次访问就HTTPS
3. 用户警觉: 检查地址栏是否有锁图标

常见问题:

Q: 为什么HTTPS慢?
A: 相比HTTP,HTTPS慢在:
   1. TLS握手: 额外2个RTT (100ms)
   2. 加密解密: CPU开销 (1-5%,可忽略)

   优化:
   1. TLS 1.3: 减少到1个RTT
   2. 会话复用: 0-RTT
   3. HTTP/2: 复用连接
   4. CDN: 减少网络延迟

Q: 证书为什么要钱?
A: 证书本身免费 (Let's Encrypt)
   - 付费证书提供额外服务:
     - EV证书: 绿色地址栏,企业认证
     - 通配符证书: *.example.com
     - 保险: 证书被攻破,CA赔偿

Q: 自签名证书可以用吗?
A: 可以,但:
   - 浏览器会警告 "不安全"
   - 无法防止中间人攻击
   - 只适合开发环境
   - 生产环境必须用受信任的CA签发

Q: HTTP/2必须用HTTPS吗?
A: 理论上不需要,但:
   - 浏览器只支持HTTP/2 over TLS
   - 事实上,HTTP/2 = HTTPS

二、性能优化

2.1 数据库性能优化

Q3: 除了SQL优化,还有哪些数据库性能优化手段?

答案:

1. 连接池优化

问题:

每次请求都建立新连接:
1. 建立TCP连接 (三次握手)
2. MySQL认证
3. 执行SQL
4. 关闭连接 (四次挥手)

总耗时: 建立连接10ms + SQL执行5ms + 关闭连接10ms = 25ms
其中: 连接管理占80%的时间!

解决方案:

from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

# 连接池配置
engine = create_engine(
    "mysql://user:pass@localhost/db",
    poolclass=QueuePool,
    pool_size=10,              # 常驻连接数
    max_overflow=20,           # 峰值额外连接数
    pool_timeout=30,           # 等待连接超时时间
    pool_recycle=3600,         # 连接回收时间 (避免MySQL wait_timeout)
    pool_pre_ping=True,        # 使用前检查连接是否有效
    echo_pool=True             # 打印连接池日志
)

流程:
1. 请求到来,从连接池获取连接 (1ms)
2. 执行SQL (5ms)
3. 归还连接到连接池 (1ms)

总耗时: 7ms (相比25ms,提升3.5倍!)

连接池大小设置:
- pool_size: CPU核数 × 2 (如8核 → 16个连接)
- max_overflow: pool_size × 2
- 理由: MySQL连接是线程,过多连接导致上下文切换

监控:
- 活跃连接数
- 等待连接数
- 连接使用率

2. 读写分离

架构:

                写请求
应用 ───────────→ 主库 (Master)
  │                  │
  │                  │ binlog同步
  │                  ↓
  └─读请求─→  从库1 (Slave1)
           ├→ 从库2 (Slave2)
           └→ 从库3 (Slave3)

实现:

from sqlalchemy import create_engine

# 主库 (写)
master_engine = create_engine("mysql://user:pass@master-db/db")

# 从库 (读)
slave_engines = [
    create_engine("mysql://user:pass@slave1-db/db"),
    create_engine("mysql://user:pass@slave2-db/db"),
    create_engine("mysql://user:pass@slave3-db/db"),
]

import random

def get_db_session(read_only=False):
    if read_only:
        # 随机选择一个从库 (负载均衡)
        engine = random.choice(slave_engines)
    else:
        engine = master_engine
    return Session(bind=engine)

# 使用
# 写操作
with get_db_session(read_only=False) as session:
    session.execute("INSERT INTO users ...")

# 读操作
with get_db_session(read_only=True) as session:
    users = session.query(User).all()

注意事项:

1. 主从延迟
   - 写入主库后立即从从库读取,可能读不到
   - 解决: 写后读走主库,或延迟读取

2. 负载均衡策略
   - 随机: 简单,但可能不均衡
   - 轮询: 均衡,但需要状态维护
   - 权重: 根据从库性能分配
   - 最少连接: 动态均衡

3. 从库故障
   - 健康检查: 定期ping从库
   - 自动摘除: 故障从库移出列表
   - 自动恢复: 从库恢复后重新加入

3. 分库分表

垂直分库:

单体数据库:
users, orders, products, payments, logistics

拆分:
用户库: users
订单库: orders
商品库: products
支付库: payments
物流库: logistics

优势:
- 降低单库压力
- 不同业务独立扩展
- 故障隔离

劣势:
- 无法join
- 分布式事务

水平分表:

场景: 订单表1亿条记录,查询慢

分表策略:
orders_0: order_id % 10 = 0
orders_1: order_id % 10 = 1
...
orders_9: order_id % 10 = 9

路由规则:
def get_table_name(order_id):
    table_index = order_id % 10
    return f"orders_{table_index}"

查询:
order_id = 12345
table_name = get_table_name(12345)  # orders_5
sql = f"SELECT * FROM {table_name} WHERE order_id = 12345"

优势:
- 单表数据量降低10倍
- 查询速度提升10倍

劣势:
- 无法按其他字段查询 (如user_id)
- 需要查询所有分表,聚合结果

分库分表中间件:

ShardingSphere (推荐):
- 支持分库分表
- 读写分离
- 分布式事务
- 数据加密
- 影子库 (压测)

配置示例:
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        url: jdbc:mysql://localhost:3306/db0
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        url: jdbc:mysql://localhost:3306/db1
    rules:
      sharding:
        tables:
          orders:
            actual-data-nodes: ds$->{0..1}.orders_$->{0..9}
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: mod
        sharding-algorithms:
          mod:
            type: MOD
            props:
              sharding-count: 10

4. 缓存优化

查询缓存 (MySQL Query Cache) - 已废弃

问题:
- 表任何更新,所有缓存失效
- 并发写入场景,缓存失效频繁
- MySQL 8.0已移除

不推荐使用!

应用层缓存 (Redis) - 推荐

import redis

redis_client = redis.Redis(host='localhost', port=6379)

def get_user(user_id):
    # 1. 查询缓存
    cache_key = f"user:{user_id}"
    cached_user = redis_client.get(cache_key)
    if cached_user:
        return json.loads(cached_user)

    # 2. 缓存未命中,查询数据库
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)

    # 3. 写入缓存
    redis_client.setex(cache_key, 3600, json.dumps(user))

    return user

性能:
- Redis查询: 1ms
- MySQL查询: 10ms
- 提升10倍!

缓存策略:
- 热点数据: TTL较长 (1小时)
- 冷数据: TTL较短 (5分钟)
- 更新频繁: 不缓存或TTL极短

5. 批量操作优化

问题:

# 插入1000条记录
for i in range(1000):
    db.execute("INSERT INTO users (name) VALUES (?)", f"user{i}")

问题:
- 1000次网络往返
- 1000次SQL解析
- 1000次事务提交
- 总耗时: 1000 × 10ms = 10秒!

解决方案1: 批量插入

# 方式1: 单条SQL,多个VALUES
values = []
for i in range(1000):
    values.append(f"('user{i}')")

sql = f"INSERT INTO users (name) VALUES {','.join(values)}"
db.execute(sql)

# 方式2: executemany (推荐)
users = [(f"user{i}",) for i in range(1000)]
db.executemany("INSERT INTO users (name) VALUES (?)", users)

性能:
- 1次网络往返
- 1次事务提交
- 总耗时: 50ms
- 提升200倍!

解决方案2: 批量更新

# 方式1: CASE WHEN
UPDATE users
SET status = CASE
    WHEN id = 1 THEN 'ACTIVE'
    WHEN id = 2 THEN 'INACTIVE'
    WHEN id = 3 THEN 'ACTIVE'
END
WHERE id IN (1, 2, 3)

# 方式2: ON DUPLICATE KEY UPDATE
INSERT INTO users (id, name, status) VALUES
(1, 'Alice', 'ACTIVE'),
(2, 'Bob', 'INACTIVE'),
(3, 'Charlie', 'ACTIVE')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
status = VALUES(status)

6. 索引优化

覆盖索引:

-- 查询
SELECT id, name, age FROM users WHERE age = 25;

-- 普通索引
INDEX(age)

执行过程:
1. 在age索引中找到age=25的记录ID: [1001, 1002, 1003]
2. 回表查询: 根据ID到主索引获取name字段
3. 回表3次,性能差!

-- 覆盖索引
INDEX(age, name)

执行过程:
1. 在(age, name)索引中找到age=25的记录
2. 索引已包含id, name, age,无需回表!
3. 性能提升10!

应用:
- 分页查询
- 统计查询
- 频繁查询的字段组合

前缀索引:

-- 字段太长,索引占用空间大
email VARCHAR(255), INDEX(email)  -- 索引大小: 255字节

-- 前缀索引
INDEX(email(10))  -- 只索引前10个字符,大小: 10字节

优势:
- 索引大小降低25- 内存占用少
- B+树高度降低,查询更快

劣势:
- 区分度降低
- 无法用于ORDER BY, GROUP BY
- 需要选择合适的前缀长度

选择前缀长度:
SELECT
    COUNT(DISTINCT LEFT(email, 5)) / COUNT(*) AS selectivity_5,
    COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) AS selectivity_10,
    COUNT(DISTINCT LEFT(email, 15)) / COUNT(*) AS selectivity_15
FROM users;

选择区分度>0.9的最短前缀

7. 配置优化

InnoDB缓冲池 (Buffer Pool)

作用:
- 缓存数据页和索引页
- 减少磁盘IO

配置:
innodb_buffer_pool_size = 8G  # 服务器内存的60-70%

监控:
SHOW STATUS LIKE 'Innodb_buffer_pool%';

关键指标:
- Innodb_buffer_pool_read_requests: 读请求总数
- Innodb_buffer_pool_reads: 从磁盘读取次数
- 命中率 = 1 - (reads / read_requests)
- 目标: >99%

慢查询日志

配置:
slow_query_log = ON
long_query_time = 1  # 超过1秒记录
log_queries_not_using_indexes = ON  # 记录未使用索引的查询

分析:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

最大连接数

配置:
max_connections = 500  # 最大连接数

监控:
SHOW STATUS LIKE 'Threads_connected';  # 当前连接数
SHOW STATUS LIKE 'Max_used_connections';  # 历史最大连接数

调优:
- Max_used_connections接近max_connections → 增加max_connections
- 连接数长期很低 → 检查连接泄漏

总结:

优化手段性能提升复杂度适用场景
连接池3-5倍所有场景 (必须)
读写分离2-3倍读多写少
分库分表10倍+海量数据
缓存10-100倍热点数据
批量操作100倍+批量写入
索引优化10-1000倍所有场景
配置优化1.5-2倍所有场景

优化顺序:

  1. SQL优化 (索引、查询改写)
  2. 连接池优化
  3. 缓存优化
  4. 读写分离
  5. 分库分表 (最后考虑)

(未完待续...本文档包含网络协议和数据库优化部分,后续还有服务器优化、前端优化等内容)