Skip to content

第八章:扩展与安全

本章将介绍数据库的扩展策略和安全防护。你将了解 MySQL 和 PostgreSQL 等服务器型数据库的特点,学习垂直扩展与水平扩展的区别,掌握复制和分片技术的基本概念。同时,你还将学习数据库安全的核心实践:访问控制和 SQL 注入防护。通过本章学习,你将对生产环境数据库的运维和安全有全面的认识。

1. 从 SQLite 到服务器型数据库

1.1 SQLite 的局限性

SQLite 是一个优秀的嵌入式数据库,但在以下场景中存在局限:

场景SQLite 局限
高并发写入数据库级锁,写操作串行执行
大规模数据单文件存储,TB 级数据管理困难
网络访问无内置网络服务,仅本地访问
用户权限文件级权限,无细粒度访问控制
高可用无内置主从复制、故障转移机制

1.2 MySQL 简介

MySQL 是世界上最流行的开源关系型数据库之一。

特点:

特性说明
客户端/服务器架构独立的服务进程,支持远程连接
高并发支持数千并发连接
丰富的存储引擎InnoDB(事务安全)、MyISAM(高性能读)等
复制机制内置主从复制,支持读写分离
生态系统LAMP 栈核心,文档丰富,社区活跃

适用场景:

  • Web 应用(WordPress、Drupal、Laravel 等)
  • 中小型电商系统
  • 需要快速开发和部署的项目

基本操作对比:

sql
-- SQLite: 直接连接文件
sqlite3 database.db

-- MySQL: 连接服务器
mysql -h localhost -u root -p

-- MySQL: 创建数据库
CREATE DATABASE bookstore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE bookstore;

-- MySQL: 创建用户并授权
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE ON bookstore.* TO 'app_user'@'localhost';

1.3 PostgreSQL 简介

PostgreSQL 是一个功能强大的开源对象关系型数据库。

特点:

特性说明
标准兼容最符合 SQL 标准的数据库之一
高级类型支持数组、JSON、地理信息等高级数据类型
扩展丰富PostGIS(地理)、TimescaleDB(时序)等
并发控制多版本并发控制(MVCC),读写不阻塞
复杂查询优化器强大,适合复杂分析和报表

适用场景:

  • 金融系统(强一致性要求)
  • 地理信息系统(GIS)
  • 数据分析与数据仓库
  • 需要复杂查询的企业应用

PostgreSQL 特有功能:

sql
-- 数组类型
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title TEXT,
    tags TEXT[]  -- 字符串数组
);

INSERT INTO posts (title, tags) VALUES ('SQL 教程', '{database, sql, tutorial}');

-- JSONB 类型(二进制 JSON,支持索引)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    profile JSONB
);

INSERT INTO users (profile) VALUES ('{"name": "张三", "age": 25}'::JSONB);

-- 查询 JSON 字段
SELECT * FROM users WHERE profile->>'name' = '张三';

1.4 三种数据库对比

特性SQLiteMySQLPostgreSQL
架构嵌入式C/S 架构C/S 架构
部署零配置需要安装服务需要安装服务
并发写入很高
数据类型简单(5种)丰富非常丰富
扩展性有限良好优秀
学习曲线平缓中等较陡
适用规模小型应用中小型应用中大型应用
典型场景移动 App、IoTWeb 应用、电商企业级、分析型

2. 数据库扩展策略

当业务增长,单台数据库服务器无法满足需求时,需要进行扩展。

2.1 垂直扩展(Scale Up)

垂直扩展是指提升单台服务器的硬件性能。

优点:

  • ✅ 实施简单,无需修改应用代码
  • ✅ 数据一致性容易保证

缺点:

  • ❌ 硬件有上限(单机性能极限)
  • ❌ 成本递增(高端硬件价格昂贵)
  • ❌ 单点故障风险

适用场景:

  • 数据量 < 1TB
  • 并发连接 < 1000
  • 预算有限,希望快速扩展

2.2 水平扩展(Scale Out)

水平扩展是指增加更多服务器,将负载分布到多台机器上。

优点:

  • ✅ 理论上无限扩展
  • ✅ 使用廉价硬件
  • ✅ 高可用性(某台故障不影响整体)

缺点:

  • ❌ 架构复杂,需要额外组件
  • ❌ 数据一致性挑战
  • ❌ 应用可能需要改造

水平扩展的两种方式:

2.2.1 复制(Replication)

主从复制(Master-Slave Replication):

复制的工作原理:

  1. Master 记录所有写操作到二进制日志(binlog)
  2. Slave 读取 Master 的 binlog
  3. Slave 在本地重放(replay)这些操作
  4. 最终 Slave 数据与 Master 保持一致

复制类型:

类型说明延迟
异步复制Master 不等待 Slave 确认低,但可能丢数据
半同步复制Master 等待至少一个 Slave 确认中等,数据更安全
同步复制Master 等待所有 Slave 确认高,性能影响大

读写分离:

javascript
// 伪代码:应用层实现读写分离

// 写操作 → Master
function createOrder(userId, productId) {
  const conn = getMasterConnection()
  conn.execute('INSERT INTO orders ...')
}

// 读操作 → Slave
function getOrder(orderId) {
  const conn = getSlaveConnection() // 负载均衡选择 Slave
  return conn.execute('SELECT * FROM orders WHERE id = ?', [orderId])
}

2.2.2 分片(Sharding)

分片是将数据水平分割到多个数据库实例上。

路由规则:

  • User ID % 3 = 0 → Shard 1
  • User ID % 3 = 1 → Shard 2
  • User ID % 3 = 2 → Shard 3

分片策略:

策略说明示例
哈希分片对分片键取哈希shard = hash(user_id) % shard_count
范围分片按 ID 范围分配User 1-100万→Shard1, 100-200万→Shard2
列表分片按离散值分配北京/上海→Shard1, 广州/深圳→Shard2

分片的挑战:

sql
-- 问题1:跨分片 JOIN 困难
-- User 1 在 Shard 1,Order 100 在 Shard 3
-- 需要分别查询后合并结果

-- 问题2:跨分片事务困难
-- 转账:User 1(Shard 1)转给 User 2(Shard 2)
-- 需要分布式事务,复杂且性能低

-- 解决方案:数据冗余(反规范化)
-- 在每个分片存储必要的关联数据

2.3 扩展策略选择

决策流程:

  1. 先垂直扩展(简单)
  2. 读多写少 → 读写分离(复制)
  3. 数据量大 → 分片
  4. 综合考虑缓存、CDN 等其他手段

3. 数据库安全

3.1 访问控制

访问控制是限制用户对数据库资源操作权限的机制。

MySQL 权限管理

sql
-- 1. 创建用户
CREATE USER 'zhangsan'@'localhost' IDENTIFIED BY 'StrongPassword123!';
CREATE USER 'lisi'@'%' IDENTIFIED BY 'AnotherStrongPass456!';  -- 任何主机

-- 2. 授权
-- 基本语法:GRANT 权限 ON 数据库.表 TO 用户;

-- 只读权限
GRANT SELECT ON bookstore.* TO 'zhangsan'@'localhost';

-- 读写权限
GRANT SELECT, INSERT, UPDATE, DELETE ON bookstore.* TO 'lisi'@'%';

-- 特定表权限
GRANT SELECT, UPDATE (price, stock) ON bookstore.products TO 'manager'@'localhost';

-- 所有权限(谨慎使用)
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;

-- 3. 撤销权限
REVOKE DELETE ON bookstore.* FROM 'lisi'@'%';

-- 4. 查看权限
SHOW GRANTS FOR 'zhangsan'@'localhost';

-- 5. 刷新权限(立即生效)
FLUSH PRIVILEGES;

-- 6. 删除用户
DROP USER 'zhangsan'@'localhost';

常用权限:

权限说明
SELECT查询数据
INSERT插入数据
UPDATE更新数据
DELETE删除数据
CREATE创建表/数据库
DROP删除表/数据库
ALTER修改表结构
INDEX创建/删除索引
ALL PRIVILEGES所有权限

最小权限原则

❌ 错误做法:
   应用连接数据库使用 root 账户
   GRANT ALL PRIVILEGES ON *.* TO 'app'@'%';

✅ 正确做法:
   为不同操作创建不同用户

   应用只读用户:
   GRANT SELECT ON mydb.* TO 'app_read'@'app-server';

   应用写入用户:
   GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app_write'@'app-server';

   后台管理用户:
   GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'admin'@'admin-server';

3.2 SQL 注入攻击

SQL 注入是最常见的数据库安全漏洞之一。攻击者通过在输入中注入恶意 SQL 代码,非法操作数据库。

攻击示例

假设登录代码如下:

javascript
// 不安全的代码(字符串拼接)
function login(username, password) {
  const sql = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`
  return db.execute(sql)
}

// 正常输入
login('zhangsan', '123456')
// 生成 SQL:
// SELECT * FROM users WHERE username = 'zhangsan' AND password = '123456'

// 恶意输入(SQL 注入)
login("admin' --", '任意密码')
// 生成 SQL:
// SELECT * FROM users WHERE username = 'admin' --' AND password = '任意密码'
// -- 后面的内容被注释掉,只需用户名即可登录!

// 更危险的注入
login("'; DROP TABLE users; --", '')
// 生成 SQL:
// SELECT * FROM users WHERE username = ''; DROP TABLE users; --' AND password = ''
// 结果:用户表被删除!

防护措施:预处理语句

预处理语句(Prepared Statements) 是防止 SQL 注入的最有效方法。

原理:

普通查询:

预处理语句:

代码示例:

javascript
// Node.js + mysql2(使用参数化查询)
const mysql = require('mysql2/promise')

async function loginSafe(username, password) {
  const conn = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    database: 'test',
  })

  // 使用 ? 占位符
  const [rows] = await conn.execute(
    'SELECT * FROM users WHERE username = ? AND password = ?',
    [username, password] // 参数作为数组传递
  )

  return rows[0]
}

// Node.js + pg (PostgreSQL)
const { Client } = require('pg')

async function loginSafePg(username, password) {
  const client = new Client()
  await client.connect()

  // 使用 $1, $2 占位符
  const result = await client.query(
    'SELECT * FROM users WHERE username = $1 AND password = $2',
    [username, password]
  )

  return result.rows[0]
}

其他安全措施

1. 输入验证:

javascript
function validateUsername(username) {
  // 只允许字母、数字、下划线
  if (!/^[a-zA-Z0-9_]+$/.test(username)) {
    throw new Error('用户名包含非法字符')
  }
  return username
}

2. 最小权限原则:

sql
-- 应用用户不应该有 DROP 权限
REVOKE DROP ON mydb.* FROM 'app_user'@'%';

-- 即使发生注入,也无法删除表

3. 错误信息处理:

javascript
// 不要向用户暴露详细的数据库错误
async function safeQuery(sql, params) {
  try {
    return await db.execute(sql, params)
  } catch (e) {
    // 记录详细错误到日志(开发者查看)
    logger.error(`Database error: ${e.message}`)

    // 向用户返回模糊错误信息
    throw new Error('查询失败,请稍后重试')
  }
}

4. 使用 ORM(对象关系映射):

javascript
// Sequelize(Node.js ORM)
const user = await User.findOne({
  where: { username, password },
})

// TypeORM(Node.js ORM)
const user = await userRepository.findOne({
  where: { username, password },
})

// Prisma(Node.js ORM)
const user = await prisma.user.findFirst({
  where: { username, password },
})

4. 生产环境最佳实践

4.1 配置安全

sql
-- MySQL 安全配置检查清单

-- 1. 删除匿名用户
DELETE FROM mysql.user WHERE User = '';

-- 2. 删除远程 root 访问
DELETE FROM mysql.user WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1');

-- 3. 删除测试数据库
DROP DATABASE IF EXISTS test;

-- 4. 设置强密码策略
SET GLOBAL validate_password.policy = STRONG;

-- 5. 启用 SSL 连接
-- 在 my.cnf 中配置 require_secure_transport = ON

4.2 备份策略

bash
# MySQL 备份(mysqldump)
# 完整备份
mysqldump -u root -p --all-databases > full_backup.sql

# 单库备份
mysqldump -u root -p mydb > mydb_backup.sql

# 增量备份(基于二进制日志)
mysqlbinlog mysql-bin.000001 > incremental_backup.sql

# 自动备份脚本(crontab)
# 每天凌晨 2 点备份
0 2 * * * mysqldump -u backup -p'password' mydb > /backup/mydb_$(date +\%Y\%m\%d).sql

4.3 监控指标

指标说明告警阈值
QPS每秒查询数根据容量规划
连接数当前连接数> 80% 最大连接数
慢查询执行时间 > 1s 的查询> 10/分钟
复制延迟主从延迟时间> 1 秒
磁盘空间数据目录使用率> 80%
CPU/内存服务器资源使用> 80%

5. 本章小结

核心概念回顾

概念一句话理解
垂直扩展升级单台服务器硬件
水平扩展增加服务器数量,分散负载
主从复制Master 写,Slave 读,数据同步
读写分离写操作走 Master,读操作走 Slave
分片(Sharding)将数据水平分割到多个数据库
哈希分片按哈希值分配数据到分片
范围分片按 ID 范围分配数据到分片
最小权限原则只授予用户必要的最小权限
SQL 注入通过输入注入恶意 SQL 代码的攻击
预处理语句将 SQL 和数据分离,防止注入
GRANT授予用户权限
REVOKE撤销用户权限

数据库选择决策树

扩展策略决策树

安全 checklist

  • [ ] 使用强密码策略
  • [ ] 遵循最小权限原则
  • [ ] 所有 SQL 使用预处理语句
  • [ ] 验证和过滤用户输入
  • [ ] 不暴露详细错误信息
  • [ ] 定期备份数据
  • [ ] 启用 SSL 加密连接
  • [ ] 定期更新数据库版本(修补安全漏洞)

学习总结

通过本教程的学习,你已经掌握了:

  1. 数据库基础 —— 什么是数据库,SQL 基本语法
  2. 设计与规范化 —— 表设计、范式、约束
  3. 基础查询 —— SELECT、WHERE、聚合函数
  4. 关系设计 —— JOIN、子查询、ER 模型
  5. 数据操作 —— INSERT、UPDATE、DELETE、触发器
  6. 视图与 CTE —— 视图创建、递归 CTE
  7. 性能优化 —— 索引、事务、锁机制
  8. 扩展与安全 —— MySQL/PostgreSQL、扩展策略、SQL 注入防护

这些知识足以应对大多数应用开发场景。继续深入的最佳方式是:动手实践,在实际项目中应用所学知识,遇到问题时查阅官方文档,不断积累经验。