数据存储

难度等级:⭐⭐⭐ 前置知识:编程语言基础 后续衔接:微服务架构、缓存策略

学习路径


一、关系型数据库

1.1 SQL Server

SQL Server 是微软推出的企业级关系型数据库管理系统,采用 T-SQL(Transact-SQL)作为其扩展的 SQL 方言。T-SQL 在标准 SQL 的基础上增加了编程语言的特性,包括变量、流程控制、异常处理等。

存储过程与函数:存储过程是预编译的 SQL 代码块,存储在数据库服务器端,可以被多次调用。其优势在于减少网络传输、提高执行效率、增强安全性。

-- 创建存储过程示例
CREATE PROCEDURE usp_GetUserOrders
    @UserId INT,
    @StartDate DATE = NULL
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT u.UserName, o.OrderId, o.OrderDate, o.TotalAmount
    FROM Users u
    INNER JOIN Orders o ON u.UserId = o.UserId
    WHERE u.UserId = @UserId
      AND (@StartDate IS NULL OR o.OrderDate >= @StartDate)
    ORDER BY o.OrderDate DESC;
END

执行计划分析:SQL Server 提供了图形化的执行计划查看工具。通过分析执行计划,可以识别慢查询的瓶颈所在,如全表扫描、索引查找、哈希连接等操作。重点关注 Cost 占比高的操作符,以及是否存在 Missing Index 提示。

性能调优要点:

1.2 MySQL

MySQL 是全球最流行的开源关系型数据库,以其轻量、高效、易用著称。InnoDB 是 MySQL 的默认存储引擎,支持事务、行级锁和外键约束。

InnoDB 引擎架构:InnoDB 的核心组件包括 Buffer Pool(缓冲池)、Redo Log(重做日志)、Undo Log(回滚日志)和 Doublewrite Buffer(双写缓冲)。Buffer Pool 用于缓存数据页和索引页,是 InnoDB 性能优化的关键参数,通常建议设置为物理内存的 50%-80%。

索引结构:MySQL 主要使用 B+ 树作为索引结构。B+ 树的所有数据都存储在叶子节点,叶子节点之间通过双向链表连接,这使得范围查询和排序操作非常高效。

-- 查看表的索引信息
SHOW INDEX FROM users;

-- 创建复合索引
CREATE INDEX idx_user_status_created ON users(status, created_at);

-- 使用 EXPLAIN 分析查询执行计划
EXPLAIN SELECT * FROM users WHERE status = 'active' AND created_at > '2024-01-01';

主从复制原理:MySQL 主从复制基于 binlog(二进制日志)。主库将数据变更写入 binlog,从库的 I/O 线程读取 binlog 并写入 relay log,SQL 线程重放 relay log 中的事件实现数据同步。复制模式包括异步复制、半同步复制和组复制(MGR)。

查询优化技巧:

1.3 PostgreSQL

PostgreSQL 被誉为”世界上最先进的开源关系型数据库”,以其强大的扩展能力和对 SQL 标准的严格遵循而闻名。

MVCC 机制:PostgreSQL 采用多版本并发控制(MVCC)来实现高并发。每次事务修改数据时,不会直接覆盖旧数据,而是创建新版本。每个元组包含 xmin(创建事务 ID)和 xmax(删除事务 ID)系统列,通过对比事务 ID 来判断元组对当前事务是否可见。这种机制使得读写操作不会互相阻塞。

JSONB 数据类型:PostgreSQL 原生支持 JSON 和 JSONB 两种 JSON 类型。JSONB 是二进制格式,支持索引(GIN 索引),查询性能优于 JSON。这使得 PostgreSQL 在某些场景下可以替代 MongoDB 等文档数据库。

-- 创建包含 JSONB 列的表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    attributes JSONB
);

-- 创建 GIN 索引支持 JSONB 查询
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);

-- JSONB 查询示例
SELECT * FROM products 
WHERE attributes @> '{"category": "electronics"}';

-- JSONB 更新示例
UPDATE products 
SET attributes = jsonb_set(attributes, '{price}', '99.99')
WHERE id = 1;

窗口函数:PostgreSQL 支持完整的窗口函数,可以在不改变结果集行数的情况下进行聚合计算。

-- 计算每个部门内按薪资排名的员工
SELECT 
    name, department, salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
    AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;

扩展生态:PostgreSQL 拥有丰富的扩展,如 PostGIS(地理空间数据)、pg_partman(分区管理)、TimescaleDB(时序数据)、pg_stat_statements(性能分析)等。

1.4 索引原理

B+ 树结构:B+ 树是一种自平衡的多路搜索树,是关系型数据库最常用的索引结构。其特点包括:

聚簇索引与非聚簇索引:

覆盖索引:当查询所需的所有列都包含在索引中时,无需回表查询,直接从索引中获取数据,这称为覆盖索引(Covering Index)。

-- 覆盖索引示例
-- 索引: idx_user_status_email (status, email)
SELECT email FROM users WHERE status = 'active';  -- 覆盖索引,无需回表
SELECT email, name FROM users WHERE status = 'active';  -- 需要回表查询 name

索引失效场景:

  1. 对索引列使用函数或表达式:WHERE YEAR(created_at) = 2024
  2. 隐式类型转换:字符串字段不加引号 WHERE phone = 13800000000
  3. 模糊查询左通配符:WHERE name LIKE '%john'
  4. OR 条件中部分列无索引:WHERE status = 'active' OR age > 25
  5. 联合索引不满足最左前缀原则
  6. 数据量过小或区分度过低,优化器选择全表扫描

1.5 事务与锁

ACID 特性:

事务隔离级别:

隔离级别 脏读 不可重复读 幻读 性能影响
READ UNCOMMITTED 可能 可能 可能 最低
READ COMMITTED 不可能 可能 可能 较低
REPEATABLE READ 不可能 不可能 可能(InnoDB 通过 Next-Key Lock 解决) 中等
SERIALIZABLE 不可能 不可能 不可能 最高
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 显式事务控制
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT;

锁类型:

死锁处理:

-- 查看死锁信息
SHOW ENGINE INNODB STATUS;

-- 设置锁等待超时(避免长时间阻塞)
SET innodb_lock_wait_timeout = 10;

1.6 分库分表

垂直拆分:

水平拆分:

分片策略:

-- 哈希分片示例(用户 ID 取模)
-- 用户表分为 4 个分片
shard_id = user_id % 4

-- 查询路由:user_id = 1024 → shard 0
SELECT * FROM users_0 WHERE user_id = 1024;

ShardingSphere:Apache ShardingSphere 是一套开源的分布式数据库解决方案,提供数据分片、读写分离、分布式事务等能力。支持 Java 和 Proxy 两种接入方式。

数据迁移方案:

  1. 双写迁移:新旧库同时写入,逐步切读,风险最低
  2. 停机迁移:业务暂停期间完成数据迁移,简单但影响可用性
  3. 增量同步:基于 binlog 实时同步,配合全量迁移使用

分库分表带来的问题:


二、NoSQL 数据库

2.1 Redis

Redis(Remote Dictionary Server)是一个开源的内存数据结构存储,可用作数据库、缓存和消息中间件。其数据模型完全基于内存,因此读写性能极高,单实例可达 10 万+ QPS。

核心数据结构:

数据结构 底层实现 典型场景 时间复杂度
String 简单动态字符串(SDS) 缓存、计数器、分布式锁 O(1)
Hash 压缩列表/哈希表 对象存储、购物车 O(1)
List 快速列表(quicklist) 消息队列、时间线 O(1) 两端操作
Set 整数集合/哈希表 标签、共同好友 O(1)
ZSet 跳表(skiplist)+ 哈希表 排行榜、延迟队列 O(log N)
# String 操作
SET user:1001:name "张三"
GET user:1001:name
INCR page_views
EXPIRE user:1001:name 3600

# Hash 操作
HSET user:1001 name "张三" age 25 email "zhangsan@example.com"
HGETALL user:1001
HINCRBY user:1001 age 1

# List 操作(消息队列)
LPUSH task_queue "task_1" "task_2" "task_3"
BRPOP task_queue 0  # 阻塞弹出

# ZSet 操作(排行榜)
ZADD leaderboard 100 "player_1" 85 "player_2" 92 "player_3"
ZREVRANGE leaderboard 0 9 WITHSCORES  # Top 10

持久化机制:

# 持久化配置示例
CONFIG SET save "900 1 300 10 60 10000"
CONFIG SET appendonly yes
CONFIG SET appendfsync everysec
CONFIG SET auto-aof-rewrite-percentage 100
CONFIG SET auto-aof-rewrite-min-size 64mb

集群模式:

分布式锁实现:

# 基于 SETNX 的简单分布式锁
SET lock:resource_1 unique_value NX EX 30

# 释放锁(使用 Lua 脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

# Redisson 实现(Java)
RLock lock = redisson.getLock("lock:resource_1");
lock.lock(30, TimeUnit.SECONDS);  // 自动续期
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

2.2 MongoDB

MongoDB 是一个基于分布式文件存储的文档数据库,使用 BSON(Binary JSON)格式存储数据。其灵活的模式(Schema-less)使其非常适合快速迭代的开发场景。

文档模型:MongoDB 的数据组织形式是文档(Document),类似于 JSON 对象。文档可以嵌套,支持数组和子文档,这使得数据模型更贴近面向对象的设计。

// 插入文档
db.users.insertOne({
    name: "张三",
    age: 25,
    email: "zhangsan@example.com",
    addresses: [
        { type: "home", city: "北京", detail: "朝阳区..." },
        { type: "work", city: "北京", detail: "海淀区..." }
    ],
    metadata: { created_at: new Date(), status: "active" }
});

// 嵌套查询
db.users.find({ "addresses.city": "北京" });

// 数组元素匹配
db.users.find({ addresses: { $elemMatch: { type: "home", city: "北京" } } });

聚合管道:MongoDB 的聚合框架通过管道(Pipeline)模式处理数据,每个阶段对文档进行转换或过滤。

// 聚合管道示例
db.orders.aggregate([
    { $match: { status: "completed", order_date: { $gte: ISODate("2024-01-01") } } },
    { $group: {
        _id: "$user_id",
        total_orders: { $sum: 1 },
        total_amount: { $sum: "$amount" },
        avg_amount: { $avg: "$amount" }
    }},
    { $sort: { total_amount: -1 } },
    { $limit: 10 },
    { $lookup: {
        from: "users",
        localField: "_id",
        foreignField: "_id",
        as: "user_info"
    }}
]);

索引类型:

// 创建复合索引
db.users.createIndex({ status: 1, created_at: -1 });

// 创建 TTL 索引(30 天后自动删除)
db.sessions.createIndex({ last_accessed: 1 }, { expireAfterSeconds: 2592000 });

// 查看查询执行计划
db.users.find({ status: "active" }).explain("executionStats");

副本集(Replica Set):MongoDB 的高可用方案,由一个主节点(Primary)、多个从节点(Secondary)和一个可选的仲裁节点(Arbiter)组成。主节点处理所有写操作,从节点通过 oplog 复制数据。当主节点故障时,自动选举新的主节点。

2.3 Elasticsearch

Elasticsearch 是一个分布式、RESTful 风格的搜索和分析引擎,基于 Apache Lucene 构建。其核心优势在于全文检索和实时数据分析。

倒排索引:Elasticsearch 使用倒排索引实现快速全文搜索。倒排索引将文档中的每个词映射到包含该词的文档列表。

词条 → 文档 ID 列表
"数据库" → [1, 3, 7, 12]
"索引" → [2, 3, 5, 7, 11]
"优化" → [1, 5, 8, 12]

当搜索 “数据库 优化” 时,取两个列表的交集即可快速定位相关文档 [1, 12]。

核心概念:

DSL 查询:

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "数据库优化" } }
      ],
      "filter": [
        { "range": { "publish_date": { "gte": "2024-01-01" } } },
        { "term": { "status": "published" } }
      ],
      "should": [
        { "match_phrase": { "content": "B+树" } }
      ],
      "minimum_should_match": 1
    }
  },
  "highlight": {
    "fields": { "title": {}, "content": {} }
  },
  "sort": [
    { "publish_date": "desc" },
    { "_score": "desc" }
  ],
  "from": 0,
  "size": 20
}

分片机制:创建索引时可以指定主分片数量(创建后不可修改)和副本分片数量(可动态调整)。每个分片是一个独立的 Lucene 索引,查询时协调节点将请求路由到相关分片并合并结果。

聚合分析:

{
  "size": 0,
  "aggs": {
    "by_category": {
      "terms": { "field": "category.keyword", "size": 10 },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } },
        "monthly_sales": {
          "date_histogram": {
            "field": "sale_date",
            "calendar_interval": "month"
          }
        }
      }
    }
  }
}

三、ORM 框架对比

3.1 EF Core(.NET)

Entity Framework Core 是微软官方推出的轻量级、跨平台 ORM 框架,是 EF 的现代化重写版本。

Code First 模式:通过 C# 类定义数据库表结构,配合 Migration 工具自动创建和更新数据库。

// 实体定义
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public ICollection<Order> Orders { get; set; }
}

// DbContext 配置
public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .HasIndex(u => u.Email)
            .IsUnique();
            
        modelBuilder.Entity<User>()
            .HasMany(u => u.Orders)
            .WithOne(o => o.User)
            .HasForeignKey(o => o.UserId);
    }
}

// LINQ 查询
var activeUsers = await context.Users
    .Where(u => u.Status == "active")
    .Include(u => u.Orders)
    .OrderByDescending(u => u.CreatedAt)
    .Take(20)
    .ToListAsync();

性能优化:

3.2 Spring Data JPA(Java)

Spring Data JPA 是基于 Hibernate 的抽象层,通过 Repository 接口大幅简化了数据访问层的代码。

Repository 抽象:

// 实体定义
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    private String name;
    
    @CreatedDate
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime updatedAt;
}

// Repository 接口
public interface UserRepository extends JpaRepository<User, Long>, 
                                       JpaSpecificationExecutor<User> {
    
    // 方法名派生查询
    Optional<User> findByEmail(String email);
    List<User> findByNameContainingAndStatus(String name, String status);
    
    // JPQL 查询
    @Query("SELECT u FROM User u WHERE u.status = :status ORDER BY u.createdAt DESC")
    Page<User> findActiveUsers(@Param("status") String status, Pageable pageable);
    
    // 原生 SQL 查询
    @Query(value = "SELECT * FROM users WHERE created_at > ?1", nativeQuery = true)
    List<User> findUsersCreatedAfter(Date date);
}

// Specification 动态查询
public class UserSpecifications {
    public static Specification<User> hasStatus(String status) {
        return (root, query, cb) -> cb.equal(root.get("status"), status);
    }
    
    public static Specification<User> createdAfter(Date date) {
        return (root, query, cb) -> cb.greaterThan(root.get("createdAt"), date);
    }
}

// 使用
Specification<User> spec = UserSpecifications.hasStatus("active")
    .and(UserSpecifications.createdAfter(startDate));
List<User> users = userRepository.findAll(spec);

审计功能:通过 @EntityListeners(AuditingEntityListener.class) 和 @EnableJpaAuditing 自动维护创建时间、修改时间、创建人等字段。

3.3 MyBatis(Java)

MyBatis 是一个半自动化的 ORM 框架,将 SQL 与 Java 代码解耦,适合需要精细控制 SQL 的场景。

XML 映射:

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="email" column="user_email"/>
        <collection property="orders" ofType="Order" columnPrefix="o_">
            <id property="id" column="id"/>
            <result property="amount" column="amount"/>
        </collection>
    </resultMap>
    
    <select id="findActiveUsers" resultMap="UserResultMap">
        SELECT u.user_id, u.user_name, u.user_email,
               o.id AS o_id, o.amount AS o_amount
        FROM users u
        LEFT JOIN orders o ON u.user_id = o.user_id
        WHERE u.status = #{status}
        ORDER BY u.created_at DESC
    </select>
    
    <!-- 动态 SQL -->
    <select id="searchUsers" resultType="User">
        SELECT * FROM users
        <where>
            <if test="name != null">
                AND user_name LIKE CONCAT('%', #{name}, '%')
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
            <if test="minAge != null">
                AND age >= #{minAge}
            </if>
        </where>
    </select>
    
    <!-- 批量插入 -->
    <insert id="batchInsert">
        INSERT INTO users (name, email, status) VALUES
        <foreach collection="users" item="user" separator=",">
            (#{user.name}, #{user.email}, #{user.status})
        </foreach>
    </insert>
</mapper>

插件机制:通过拦截器(Interceptor)可以实现分页、审计、性能监控等功能。PageHelper 是最常用的分页插件。

代码生成器:MyBatis Generator 可以根据数据库表自动生成实体类、Mapper 接口和 XML 文件,减少样板代码编写。

3.4 SQLAlchemy(Python)

SQLAlchemy 是 Python 生态中最流行的 ORM 框架,提供了从高层 ORM 到低层 SQL 表达式的完整抽象。

Declarative 模式:

from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
from datetime import datetime

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    status = Column(String(20), default='active')
    created_at = Column(DateTime, default=datetime.utcnow)
    
    orders = relationship('Order', back_populates='user')

class Order(Base):
    __tablename__ = 'orders'
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    amount = Column(Integer)
    
    user = relationship('User', back_populates='orders')

# Session 使用
from sqlalchemy.orm import Session

with Session(engine) as session:
    # 查询
    active_users = session.query(User)\
        .filter(User.status == 'active')\
        .order_by(User.created_at.desc())\
        .limit(20)\
        .all()
    
    # 关联查询(预加载避免 N+1)
    from sqlalchemy.orm import joinedload
    users_with_orders = session.query(User)\
        .options(joinedload(User.orders))\
        .filter(User.status == 'active')\
        .all()
    
    # 新增
    new_user = User(name='张三', email='zhangsan@example.com')
    session.add(new_user)
    session.commit()

关系映射:支持一对一、一对多、多对多关系映射,通过 relationship() 和 back_populates 实现双向关联。

3.5 Dapper(.NET)

Dapper 是一个轻量级的”微 ORM”,以高性能著称,通常比原生 ADO.NET 稍慢,但远快于 EF Core。

// 基本查询
using var connection = new SqlConnection(connectionString);
var users = await connection.QueryAsync<User>(
    "SELECT * FROM Users WHERE Status = @Status ORDER BY CreatedAt DESC",
    new { Status = "active" });

// 多映射(一对多)
var userDictionary = new Dictionary<int, User>();
var usersWithOrders = await connection.QueryAsync<User, Order, User>(
    @"SELECT u.*, o.Id as OrderId, o.Amount, o.UserId 
      FROM Users u 
      LEFT JOIN Orders o ON u.Id = o.UserId 
      WHERE u.Status = @Status",
    (user, order) => {
        if (!userDictionary.TryGetValue(user.Id, out var userEntry)) {
            userEntry = user;
            userEntry.Orders = new List<Order>();
            userDictionary.Add(userEntry.Id, userEntry);
        }
        if (order != null) userEntry.Orders.Add(order);
        return userEntry;
    },
    new { Status = "active" },
    splitOn: "OrderId");

// 批量操作
await connection.ExecuteAsync(
    "INSERT INTO Users (Name, Email) VALUES (@Name, @Email)",
    users.Select(u => new { u.Name, u.Email }));

扩展方法:通过 Dapper.Contrib 可以获取 Insert、Update、Delete、Get 等扩展方法,进一步简化操作。

3.6 选型指南

维度 EF Core Spring Data JPA MyBatis SQLAlchemy Dapper
学习曲线 中等 中等 较低 中等 低
SQL 控制力 中等 中等 高 中等 极高
开发效率 高 高 中等 高 中等
性能 中等 中等 高 中等 极高
适合场景 快速开发 企业级应用 复杂 SQL Python 项目 高性能场景

选型建议:


四、缓存策略

4.1 缓存模式

Cache Aside(旁路缓存):最常用的缓存模式。读操作先查缓存,未命中则查数据库并写入缓存;写操作先更新数据库,再删除缓存。

读流程:
1. 查询缓存 → 命中则返回
2. 未命中 → 查询数据库
3. 将结果写入缓存
4. 返回数据

写流程:
1. 更新数据库
2. 删除缓存(而非更新,避免并发问题)

为什么写操作要删除缓存而不是更新缓存?原因有三:一是更新缓存可能涉及复杂的计算(如多表关联);二是并发场景下,先更新缓存再更新数据库,可能导致其他线程读取到旧缓存数据;三是删除操作比更新操作更简单、更高效。

Read Through(读穿透):应用程序只与缓存交互,缓存负责从数据库加载数据。缓存对应用程序透明,适合缓存中间件场景。

Write Through(写穿透):写操作同时写入缓存和数据库,保证缓存与数据库的强一致性。缺点是写入性能下降,因为需要等待数据库写入完成。

Write Behind(写回):写操作只写入缓存,缓存异步将数据刷入数据库。性能最高但存在数据丢失风险,适合对一致性要求不高的场景,如计数器、浏览量统计等。

4.2 缓存问题

缓存穿透:大量请求查询的数据在缓存和数据库中都不存在,导致所有请求都打到数据库。

解决方案:

// 缓存空值示例
public User getUserById(Long id) {
    String key = "user:" + id;
    User user = redisTemplate.opsForValue().get(key);
    if (user != null) {
        return user == EMPTY_USER ? null : user;  // 空值标记
    }
    user = userMapper.selectById(id);
    if (user == null) {
        redisTemplate.opsForValue().set(key, EMPTY_USER, 5, TimeUnit.MINUTES);
    } else {
        redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
    }
    return user;
}

缓存击穿:某个热点 key 过期瞬间,大量并发请求同时打到数据库。

解决方案:

// 互斥锁解决缓存击穿
public User getUserWithLock(Long id) {
    String key = "user:" + id;
    String lockKey = "lock:user:" + id;
    User user = redisTemplate.opsForValue().get(key);
    if (user != null) {
        return user;
    }
    // 获取分布式锁
    Boolean locked = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
    if (Boolean.TRUE.equals(locked)) {
        try {
            // 双重检查
            user = redisTemplate.opsForValue().get(key);
            if (user != null) return user;
            // 回源数据库
            user = userMapper.selectById(id);
            if (user != null) {
                redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
            }
        } finally {
            redisTemplate.delete(lockKey);
        }
    } else {
        // 未获取到锁,短暂等待后重试
        try { Thread.sleep(50); } catch (InterruptedException e) {}
        return getUserWithLock(id);
    }
    return user;
}

缓存雪崩:大量 key 同时过期或 Redis 宕机,导致数据库瞬间承受巨大压力。

解决方案:

// 过期时间加随机值
int baseExpire = 1800;  // 30 分钟
int randomExpire = baseExpire + new Random().nextInt(300);  // 随机 +0~5 分钟
redisTemplate.opsForValue().set(key, value, randomExpire, TimeUnit.SECONDS);

4.3 分布式缓存

Redis Cluster:官方推荐的分布式方案,采用去中心化架构。数据通过哈希槽(16384 个)分布在多个主节点上,每个主节点可以有从节点提供高可用。客户端直接连接对应节点,无需代理。

# Redis Cluster 哈希槽分配
key → CRC16(key) % 16384 → 槽号 → 对应节点

# 示例:三个主节点
Node A: 槽 0-5460
Node B: 槽 5461-10922
Node C: 槽 10923-16383

Codis:豌豆荚开源的 Redis 代理方案,通过 Proxy 层实现数据分片,对客户端透明。支持平滑扩容缩容,但增加了代理层的性能开销。目前已逐渐被 Redis Cluster 取代。

一致性 Hash:解决传统哈希分片扩容时数据迁移量大的问题。一致性 Hash 将哈希空间组织成一个环,节点和数据都映射到环上,数据归属于顺时针方向最近的节点。当节点增减时,只影响相邻节点的数据。

一致性 Hash 环示意:

         节点A(100)
       /          \
  数据(50)        数据(150)
     |              |
  数据(80)        节点B(200)
       \          /
         节点C(300)

节点B 下线 → 原属于 B 的数据迁移到 C
节点D 加入 → 从相邻节点分出一部分数据给 D

缓存与数据库一致性策略:


五、学习资源推荐

书籍推荐

书名 作者 适合阶段 核心内容
《SQL 必知必会》 Ben Forta 入门 SQL 基础语法、查询、聚合、连接
《高性能 MySQL》 Baron Schwartz 等 进阶 索引优化、查询优化、复制与高可用
《MySQL 技术内幕:InnoDB 存储引擎》 姜承尧 进阶 InnoDB 架构、锁、事务、调优
《Redis 设计与实现》 黄健宏 进阶 Redis 数据结构、持久化、集群原理
《数据密集型应用系统设计》(DDIA) Martin Kleppmann 精通 分布式系统、存储引擎、一致性
《PostgreSQL 指南》 Gilles Darold 等 进阶 PostgreSQL 内部机制、优化、扩展

在线课程

官方文档

实践建议

  1. 动手搭建:在本地或云服务器搭建 MySQL 主从、Redis Cluster、MongoDB 副本集
  2. 性能压测:使用 sysbench 对 MySQL 进行压测,使用 redis-benchmark 测试 Redis 性能
  3. 执行计划分析:对日常开发中的慢查询使用 EXPLAIN 分析,理解索引使用情况
  4. 参与开源:关注 ShardingSphere、Canal 等开源项目,学习分布式数据库架构设计
  5. 生产排查:学习使用 pt-query-digest、slowlog、performance_schema 等工具进行生产问题排查