SQL与NoSQL数据库选型与性能优化

2026-06-08

深入对比SQL和NoSQL数据库,分享性能优化经验

作者:Elegy

SQL与NoSQL数据库选型与性能优化

选择合适的数据库是系统设计的关键决策。本文将对比SQL和NoSQL数据库,分享性能优化的实战经验。

🔍 SQL vs NoSQL对比

关系型数据库(SQL)

代表:MySQL、PostgreSQL、Oracle

特点

  • ✅ ACID事务保证
  • ✅ 结构化数据,强类型
  • ✅ 复杂查询和关联
  • ✅ 成熟的生态系统
  • ❌ 水平扩展困难
  • ❌ 固定schema

适用场景

  • 金融交易系统
  • ERP、CRM系统
  • 需要复杂查询和关联的应用

非关系型数据库(NoSQL)

代表:MongoDB、Redis、Cassandra

特点

  • ✅ 灵活的schema
  • ✅ 水平扩展容易
  • ✅ 高性能读写
  • ❌ 弱一致性(最终一致性)
  • ❌ 缺乏标准化

适用场景

  • 社交网络
  • 实时分析
  • 缓存系统
  • 大数据应用

🚀 SQL性能优化

1. 索引优化

-- 创建单列索引
CREATE INDEX idx_user_email ON users(email);

-- 创建复合索引
CREATE INDEX idx_user_name_age ON users(name, age);

-- 查看索引使用情况
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

索引最佳实践

  • 在WHERE、JOIN、ORDER BY常用字段上建索引
  • 避免过多索引(影响写入性能)
  • 定期分析和重建索引

2. 查询优化

-- ❌ 避免SELECT *
SELECT * FROM users;

-- ✅ 只选择需要的列
SELECT id, name, email FROM users;

-- ❌ 避免在WHERE中使用函数
SELECT * FROM orders WHERE YEAR(created_at) = 2026;

-- ✅ 使用索引友好的查询
SELECT * FROM orders 
WHERE created_at >= '2026-01-01' 
  AND created_at < '2027-01-01';

-- 使用批量操作代替循环
-- ❌ 避免
INSERT INTO users (name) VALUES ('Alice');
INSERT INTO users (name) VALUES ('Bob');

-- ✅ 推荐
INSERT INTO users (name) VALUES ('Alice'), ('Bob');

3. 连接优化

-- ❌ 避免子查询
SELECT * FROM orders 
WHERE user_id IN (SELECT id FROM users WHERE status = 'active');

-- ✅ 使用JOIN
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';

4. 分页优化

-- ❌ 深分页性能差
SELECT * FROM articles ORDER BY id LIMIT 10000, 20;

-- ✅ 使用游标分页
SELECT * FROM articles 
WHERE id > 10000 
ORDER BY id 
LIMIT 20;

📊 NoSQL实战 - MongoDB

1. 文档设计

// ❌ 避免过度嵌套
{
  "_id": "user1",
  "name": "Alice",
  "posts": [
    { "title": "Post 1", "content": "...", "comments": [...] }
  ]
}

// ✅ 合理拆分
// users集合
{ "_id": "user1", "name": "Alice" }

// posts集合
{ "_id": "post1", "userId": "user1", "title": "Post 1" }

2. 索引和查询

// 创建索引
db.users.createIndex({ email: 1 }, { unique: true });
db.posts.createIndex({ userId: 1, createdAt: -1 });

// 复合索引
db.articles.createIndex({ category: 1, tags: 1, publishDate: -1 });

// 查询优化
// ❌ 避免全表扫描
db.users.find({ age: { $gt: 18 } });

// ✅ 使用索引
db.users.find({ email: "test@example.com" });

// 分析查询性能
db.users.find({ email: "test@example.com" }).explain("executionStats");

3. 聚合管道

// 复杂数据分析
db.orders.aggregate([
  // 筛选
  { $match: { status: "completed" } },
  
  // 分组统计
  { $group: {
    _id: "$userId",
    totalSpent: { $sum: "$amount" },
    orderCount: { $sum: 1 }
  }},
  
  // 排序
  { $sort: { totalSpent: -1 } },
  
  // 限制结果
  { $limit: 10 }
]);

⚡ Redis缓存策略

1. 缓存模式

// Cache-Aside模式
async function getUser(userId) {
  // 1. 尝试从缓存获取
  let user = await redis.get(`user:${userId}`);
  
  if (user) {
    return JSON.parse(user);
  }
  
  // 2. 缓存未命中,查询数据库
  user = await db.users.findById(userId);
  
  // 3. 写入缓存
  await redis.setex(`user:${userId}`, 3600, JSON.stringify(user));
  
  return user;
}

// 更新数据时删除缓存
async function updateUser(userId, data) {
  await db.users.update(userId, data);
  await redis.del(`user:${userId}`);  // 删除缓存
}

2. 防止缓存穿透

// 布隆过滤器
const BloomFilter = require('bloomfilter');
const bloom = new BloomFilter(10000, 4);

// 初始化:将所有有效ID加入布隆过滤器
allUserIds.forEach(id => bloom.add(id));

async function getUser(userId) {
  // 先检查布隆过滤器
  if (!bloom.test(userId)) {
    return null;  // 确定不存在
  }
  
  // 可能存在,继续查询
  return await fetchUser(userId);
}

3. 防止缓存雪崩

// 设置随机过期时间
const baseExpire = 3600;
const randomExpire = baseExpire + Math.floor(Math.random() * 300);

await redis.setex(key, randomExpire, value);

🎯 数据库分库分表

水平分表

// 按用户ID哈希分表
function getTableName(userId) {
  const tableNum = userId % 10;
  return `users_${tableNum}`;
}

// 查询
const tableName = getTableName(userId);
const user = await db.query(`SELECT * FROM ${tableName} WHERE id = ?`, [userId]);

分库策略

// 按地区分库
const dbConfig = {
  'us': { host: 'db-us.example.com' },
  'eu': { host: 'db-eu.example.com' },
  'asia': { host: 'db-asia.example.com' }
};

function getDatabase(region) {
  return new Database(dbConfig[region]);
}

📈 监控和调优

1. 慢查询日志

-- MySQL开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过1秒记录

-- 查看慢查询
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;

2. 连接池配置

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

const pool = new Pool({
  host: 'localhost',
  port: 5432,
  database: 'myapp',
  user: 'admin',
  password: 'secret',
  max: 20,              // 最大连接数
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

🎓 选型建议

使用SQL当:

  • 需要ACID事务
  • 数据结构固定
  • 需要复杂查询和JOIN
  • 数据一致性要求高

使用NoSQL当:

  • 需要快速开发迭代
  • 数据结构灵活多变
  • 需要水平扩展
  • 读写性能要求极高

混合使用:

  • MySQL/PostgreSQL:核心业务数据
  • Redis:缓存和会话
  • MongoDB:日志和非结构化数据
  • Elasticsearch:全文搜索

🔗 总结

数据库优化是持续的过程:

  • 根据业务选择合适的数据库
  • 建立合理的索引策略
  • 优化查询和schema设计
  • 使用缓存减轻数据库压力
  • 监控慢查询并及时优化
  • 必要时考虑分库分表

记住:过早优化是万恶之源,先让系统运行起来,再基于实际数据优化!