数据存储

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

学习路径


一、关系型数据库

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 可以获取 InsertUpdateDeleteGet 等扩展方法,进一步简化操作。

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 等工具进行生产问题排查