引言:理解高并发场景下的MySQL挑战

在当今互联网应用中,高并发场景已经成为常态。无论是电商秒杀、票务抢购,还是社交媒体热点事件,都会在短时间内产生巨大的流量洪峰,给数据库系统带来严峻挑战。MySQL作为最流行的关系型数据库之一,在高并发环境下可能面临连接耗尽、锁竞争激烈、磁盘I/O瓶颈等问题,导致响应延迟甚至系统崩溃。

高并发处理的核心目标是确保系统在流量洪峰期间保持稳定运行,同时提供可接受的性能指标。这不仅仅是简单的硬件升级,而是需要从架构设计、SQL优化、缓存策略、数据库配置等多个维度进行综合优化。本文将详细探讨MySQL高并发处理的策略,帮助读者构建稳定、高效的数据库系统。

一、高并发对MySQL的主要影响

1.1 连接资源耗尽

MySQL的连接数是有限的,每个连接都会消耗内存和CPU资源。当并发连接数超过max_connections参数设置时,新的连接请求将被拒绝,导致应用层出现”Too many connections”错误。

示例场景:假设一个Web应用每个请求都需要建立数据库连接,且连接未及时释放。在10000 QPS(每秒查询数)的场景下,如果平均响应时间为100ms,那么同时活跃的连接数可能达到1000个。如果max_connections设置为200,系统将立即崩溃。

1.2 锁竞争与死锁

InnoDB存储引擎使用行级锁来保证数据一致性,但在高并发更新场景下,锁竞争会显著增加。特别是热点数据更新(如库存扣减)会导致大量事务等待,甚至引发死锁。

示例:在秒杀场景中,多个用户同时尝试购买同一件商品,都会对同一行记录的库存进行UPDATE操作。这些事务会竞争行锁,导致事务排队执行,响应时间急剧上升。

1.3 磁盘I/O瓶颈

高并发查询会产生大量的随机I/O和顺序I/O。如果数据无法完全缓存在内存中,频繁的磁盘访问将成为性能瓶颈。特别是在执行复杂查询或全表扫描时,磁盘I/O会成为主要瓶颈。

1.4 CPU和内存压力

大量的排序、临时表、复杂计算会消耗大量CPU资源。同时,每个连接都会分配缓冲区,高并发下内存消耗巨大,可能导致系统频繁进行内存交换(swap),进一步降低性能。

二、MySQL高并发优化策略

2.1 连接池优化

2.1.1 合理配置连接参数

MySQL提供了多个与连接相关的参数,合理配置这些参数是高并发优化的基础。

-- 查看当前连接数配置
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

-- 动态调整连接数(需要SUPER权限)
SET GLOBAL max_connections = 2000;
SET GLOBAL wait_timeout = 600;
SET GLOBAL interactive_timeout = 600;

参数说明

  • max_connections:最大连接数,根据服务器内存和业务需求设置,一般建议200-2000之间
  • wait_timeout:非交互式连接超时时间(秒),建议设置为60-300秒
  • interactive_timeout:交互式连接超时时间(秒),建议设置为60-300秒

2.1.2 应用层连接池配置

在应用层使用连接池是管理数据库连接的最佳实践。以Java的HikariCP为例:

// HikariCP配置示例
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");

// 连接池核心参数
config.setMaximumPoolSize(50);        // 最大连接数
config.setMinimumIdle(10);            // 最小空闲连接
config.setConnectionTimeout(30000);   // 连接超时时间(ms)
config.setIdleTimeout(600000);        // 空闲超时时间(ms)
config.setMaxLifetime(1800000);       // 连接最大存活时间(ms)
config.setLeakDetectionThreshold(60000); // 泄漏检测阈值

// 连接测试
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(3000);

HikariDataSource dataSource = new HikariDataSource(config);

配置建议

  • maximumPoolSize:根据应用服务器CPU核数和业务特点设置,一般为CPU核数*2 + 1
  • minimumIdle:保持一定数量的空闲连接以应对突发流量
  • connectionTimeout:避免连接等待时间过长,建议30秒以内
  • maxLifetime:定期回收连接,避免数据库端连接超时导致的问题

2.2 SQL优化与索引策略

2.2.1 避免全表扫描

全表扫描是高并发场景下的性能杀手。必须确保查询能够使用索引。

反例

-- 没有索引的查询
SELECT * FROM orders WHERE user_id = 12345;
-- 如果user_id没有索引,将扫描全表

正例

-- 创建索引
CREATE INDEX idx_user_id ON orders(user_id);

-- 查询使用索引
SELECT * FROM orders WHERE user_id = 12345;

索引优化原则

  1. 为WHERE子句、JOIN条件、ORDER BY字段创建索引
  2. 遵循最左前缀原则
  3. 避免索引失效的写法(如在索引列上使用函数)

2.2.2 优化复杂查询

高并发场景下,复杂查询会消耗大量资源。应尽量简化查询逻辑。

反例

-- 复杂子查询和多表关联
SELECT o.*, u.name, p.product_name 
FROM orders o 
JOIN users u ON o.user_id = u.id 
JOIN products p ON o.product_id = p.id 
WHERE u.status = 'active' 
  AND p.category = 'electronics' 
  AND o.create_time > '2024-01-01'
ORDER BY o.amount DESC 
LIMIT 1000;

优化策略

  1. 添加合适的复合索引:(user_id, status)(product_id, category)(create_time, amount)
  2. 考虑分页优化,避免深度分页
  3. 将部分逻辑移到应用层或使用物化视图

2.2.3 使用覆盖索引

覆盖索引可以避免回表操作,显著提升查询性能。

-- 创建覆盖索引
CREATE INDEX idx_cover ON orders(user_id, status, amount, create_time);

-- 查询只需要扫描索引
SELECT user_id, status, amount FROM orders 
WHERE user_id = 12345 AND status = 'paid';

2.3 缓存策略

2.3.1 应用层缓存

使用Redis等内存数据库作为缓存层,减少数据库访问。

示例:商品详情页缓存

// Spring Boot + Redis缓存示例
@Service
public class ProductService {
    
    @Autowired
    private ProductMapper productMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String PRODUCT_CACHE_KEY = "product:%s";
    private static final long CACHE_TTL = 300; // 5分钟
    
    public Product getProductById(Long id) {
        String key = String.format(PRODUCT_CACHE_KEY, id);
        
        // 1. 先从缓存获取
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }
        
        // 2. 缓存未命中,查询数据库
        product = productMapper.selectById(id);
        if (product != null) {
            // 3. 写入缓存
            redisTemplate.opsForValue().set(key, product, CACHE_TTL, TimeUnit.SECONDS);
        }
        
        return product;
    }
    
    // 更新商品时清除缓存
    public void updateProduct(Product product) {
        productMapper.updateById(product);
        String key = String.format(PRODUCT_CACHE_KEY, product.getId());
        redisTemplate.delete(key);
    }
}

2.3.2 MySQL查询缓存

MySQL 8.0之前支持查询缓存,但在高并发更新场景下效率较低,建议使用应用层缓存。

2.3.3 缓存穿透与雪崩防护

// 防止缓存穿透:缓存空对象
public Product getProductById(Long id) {
    String key = String.format(PRODUCT_CACHE_KEY, id);
    String nullKey = String.format("null:%s", key);
    
    // 检查是否已缓存空值
    if (redisTemplate.hasKey(nullKey)) {
        return null;
    }
    
    Product product = (Product) redisTemplate.opsForValue().get(key);
    if (product != null) {
        return product;
    }
    
    product = productMapper.selectById(id);
    if (product != null) {
        redisTemplate.opsForValue().set(key, product, CACHE_TTL, TimeUnit.SECONDS);
    } else {
        // 缓存空值,防止穿透
        redisTemplate.opsForValue().set(nullKey, "", 60, TimeUnit.SECONDS);
    }
    
    return product;
}

2.4 分库分表策略

2.4.1 垂直拆分

按业务模块拆分数据库,减少单库压力。

示例

  • 用户库:user_db(用户表、用户扩展表)
  • 订单库:order_db(订单表、订单详情表)
  • 商品库:product_db(商品表、库存表)

2.4.2 水平拆分

将大表按规则拆分到多个实例中。

示例:订单表按用户ID取模分表

-- 分表规则:user_id % 16
-- 订单表拆分为order_0到order_15

-- 插入数据时根据user_id路由到对应表
INSERT INTO order_0 (user_id, amount, status) VALUES (16, 100, 'paid');
INSERT INTO order_1 (user_id, amount, status) VALUES (17, 200, 'paid');

分表工具

  • ShardingSphere:Apache顶级项目,支持分库分表、读写分离
  • MyCat:开源的数据库中间件

2.5 读写分离

2.5.1 主从复制配置

MySQL支持主从复制,可以将读请求分发到从库。

主库配置(my.cnf)

[mysqld]
server-id = 1
log_bin = mysql-bin
binlog_format = ROW
expire_logs_days = 7

从库配置

[mysqld]
server-id = 2
relay_log = mysql-relay-bin
read_only = 1

创建复制用户

-- 在主库执行
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

-- 查看主库状态
SHOW MASTER STATUS;

启动复制

-- 在从库执行
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=1234;

START SLAVE;
SHOW SLAVE STATUS\G

2.5.2 应用层读写分离

// Spring Boot动态数据源配置
@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public DataSource routingDataSource() {
        DynamicDataSource routingDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave", slaveDataSource());
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(masterDataSource());
        return routingDataSource;
    }
}

// 数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

// 使用示例
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    // 写操作使用主库
    @Transactional
    public void createOrder(Order order) {
        DataSourceContextHolder.setDataSourceType("master");
        orderMapper.insert(order);
        DataSourceContextHolder.clearDataSourceType();
    }
    
    // 读操作使用从库
    public Order getOrder(Long id) {
        DataSourceContextHolder.setDataSourceType("slave");
        Order order = orderMapper.selectById(id);
        DataSourceContextHolder.clearDataSourceType();
        return order;
    }
}

2.6 批量操作与异步处理

2.6.1 批量插入优化

高并发场景下,单条插入效率低下,应使用批量插入。

反例

// 单条插入,性能差
for (Order order : orders) {
    orderMapper.insert(order);
}

正例

// 批量插入
orderMapper.batchInsert(orders);

// XML配置
<insert id="batchInsert" parameterType="list">
    INSERT INTO orders (user_id, amount, status, create_time) VALUES
    <foreach collection="list" item="order" separator=",">
        (#{order.userId}, #{order.amount}, #{order.status}, #{order.createTime})
    </foreach>
</insert>

2.6.2 异步处理

将非核心操作异步化,减少主线程压力。

// 使用线程池异步处理
@Service
public class OrderService {
    
    @Autowired
    private ThreadPoolExecutor asyncExecutor;
    
    public void createOrderAsync(Order order) {
        // 主流程快速返回
        orderMapper.insert(order);
        
        // 异步处理后续逻辑
        asyncExecutor.submit(() -> {
            // 更新统计、发送消息等
            updateStatistics(order);
            sendNotification(order);
        });
    }
}

2.7 MySQL参数调优

2.7.1 InnoDB缓冲池配置

[mysqld]
# 缓冲池大小,建议设置为物理内存的50%-70%
innodb_buffer_pool_size = 8G

# 缓冲池实例数,建议16GB内存以下设为1,以上可设为8
innodb_buffer_pool_instances = 8

# 刷新相邻页策略
innodb_flush_neighbors = 0

# 日志文件大小
innodb_log_file_size = 2G

# 刷新方式
innodb_flush_log_at_trx_commit = 1  # 1=严格模式,0=性能模式

2.7.2 连接与线程配置

[mysqld]
# 最大连接数
max_connections = 2000

# 线程缓存
thread_cache_size = 100

# 表缓存
table_open_cache = 2000

# 排序缓冲区
sort_buffer_size = 2M

# 连接缓冲区
join_buffer_size = 2M

2.7.3 查询缓存配置(MySQL 8.0已移除)

# MySQL 5.7及以下
query_cache_type = 0  # 建议关闭
query_cache_size = 0

2.8 监控与告警

2.8.1 关键指标监控

使用Prometheus + Grafana监控MySQL:

# prometheus.yml配置
scrape_configs:
  - job_name: 'mysql'
    static_configs:
      - targets: ['mysql-exporter:9104']

关键监控指标

  1. 连接数MySQL_global_status_threads_connected
  2. QPS/TPSrate(mysql_global_status_queries[1m])
  3. 慢查询mysql_global_status_slow_queries
  4. 锁等待mysql_global_status_table_locks_waited
  5. 缓冲池命中率mysql_global_status_buffer_pool_read_requests / mysql_global_status_buffer_pool_reads

2.8.2 慢查询日志分析

-- 开启慢查询日志
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;  -- 超过1秒的查询
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 分析慢查询
mysqldumpslow /var/log/mysql/slow.log

三、应对流量洪峰的实战策略

3.1 限流策略

在应用层和数据库层实施限流,防止系统过载。

应用层限流

// 使用Guava RateLimiter
@Service
public class OrderService {
    
    private final RateLimiter rateLimiter = RateLimiter.create(1000.0); // 每秒1000个请求
    
    public void createOrder(Order order) {
        // 获取许可,超时等待
        if (rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
            orderMapper.insert(order);
        } else {
            throw new RuntimeException("系统繁忙,请稍后重试");
        }
    }
}

数据库层限流

-- 使用MySQL的资源组(MySQL 8.0+)
CREATE RESOURCE GROUP rg_limited 
TYPE = USER 
VCPU = 0-1 
THREAD_PRIORITY = 10;

-- 将特定用户分配到限流组
SET RESOURCE GROUP rg_limited FOR user_limited;

3.2 降级策略

在系统压力过大时,暂时关闭非核心功能。

// 降级开关
@Component
public class DegradationConfig {
    
    private static boolean degradationMode = false;
    
    public static boolean isDegradationMode() {
        return degradationMode;
    }
    
    public static void setDegradationMode(boolean mode) {
        degradationMode = mode;
    }
}

// 业务代码中使用
public void createOrder(Order order) {
    if (DegradationConfig.isDegradationMode()) {
        // 降级:只记录日志,不实际写入数据库
        log.warn("降级模式,订单未持久化: {}", order);
        return;
    }
    orderMapper.insert(order);
}

3.3 熔断策略

使用Hystrix或Resilience4j实现熔断。

// Resilience4j熔断配置
@CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback")
public void createOrder(Order order) {
    orderMapper.insert(order);
}

public void createOrderFallback(Order order, Exception e) {
    // 熔断降级逻辑
    log.error("订单创建失败,执行降级: {}", e.getMessage());
}

3.4 预热与扩容

在流量洪峰来临前进行系统预热和扩容。

数据库预热

-- 预热InnoDB缓冲池
SELECT SQL_NO_CACHE * FROM hot_table WHERE id > 0 LIMIT 10000;

应用层预热

// 启动时预热连接池和缓存
@PostConstruct
public void warmup() {
    // 预热数据库连接
    dataSource.getConnection().close();
    
    // 预热Redis缓存
    for (int i = 0; i < 1000; i++) {
        redisTemplate.opsForValue().get("warmup:" + i);
    }
}

四、高并发场景下的特殊优化

4.1 秒杀场景优化

4.1.1 独立数据库设计

秒杀系统应使用独立的数据库实例,避免影响核心业务。

-- 秒杀库存表
CREATE TABLE seckill_stock (
    product_id BIGINT PRIMARY KEY,
    stock INT NOT NULL,
    version INT NOT NULL DEFAULT 0  -- 乐观锁版本号
) ENGINE=InnoDB;

-- 秒杀订单表
CREATE TABLE seckill_order (
    order_id BIGINT PRIMARY KEY,
    user_id BIGINT,
    product_id BIGINT,
    create_time DATETIME,
    INDEX idx_user_product (user_id, product_id)
) ENGINE=InnoDB;

4.1.2 预扣库存策略

// 使用Redis预扣库存
public boolean deductStock(Long productId, int count) {
    String stockKey = "seckill:stock:" + productId;
    
    // 1. Redis原子扣减
    Long remaining = redisTemplate.opsForValue().decrement(stockKey, count);
    if (remaining < 0) {
        // 库存不足,回滚
        redisTemplate.opsForValue().increment(stockKey, count);
        return false;
    }
    
    // 2. 发送消息到MQ
    sendSeckillMessage(productId, count);
    return true;
}

// 消费者异步处理数据库扣减
@RabbitListener(queues = "seckill.queue")
public void processSeckillMessage(SeckillMessage message) {
    // 数据库最终扣减
    int updated = seckillMapper.deductStock(message.getProductId(), message.getCount());
    if (updated == 0) {
        // 数据库扣减失败,补偿Redis
        redisTemplate.opsForValue().increment(
            "seckill:stock:" + message.getProductId(), 
            message.getCount()
        );
    }
}

4.2 热点数据优化

4.2.1 热点数据识别

-- 查看热点数据
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage 
WHERE OBJECT_SCHEMA = 'mydb' 
ORDER BY COUNT_FETCH DESC LIMIT 10;

4.2.2 热点行更新优化

对于热点行更新,可以使用乐观锁或拆分热点。

乐观锁示例

-- 表结构
CREATE TABLE hot_data (
    id BIGINT PRIMARY KEY,
    value INT,
    version INT
);

-- 更新操作
UPDATE hot_data 
SET value = value + 1, version = version + 1 
WHERE id = 1 AND version = 5;

-- 检查影响行数,如果为0说明版本冲突,需要重试

热点拆分

-- 将单个热点拆分为多个行
-- 原始:UPDATE hot_item SET stock = stock - 1 WHERE id = 1
-- 优化:将id=1拆分为10行,随机选择一行更新
UPDATE hot_item_1 SET stock = stock - 1 WHERE id = 1;
-- 或
UPDATE hot_item_2 SET stock = stock - 1 WHERE id = 1;

4.3 大表优化

4.3.1 归档历史数据

-- 创建归档表
CREATE TABLE orders_archive LIKE orders;

-- 归档历史数据
INSERT INTO orders_archive 
SELECT * FROM orders 
WHERE create_time < '2024-01-01';

-- 删除已归档数据
DELETE FROM orders 
WHERE create_time < '2024-01-01'
LIMIT 1000;  -- 分批删除,避免锁表

4.3.2 分区表

-- 按时间分区
CREATE TABLE logs (
    id BIGINT,
    log_time DATETIME,
    message TEXT
) PARTITION BY RANGE (YEAR(log_time) * 100 + MONTH(log_time)) (
    PARTITION p202401 VALUES LESS THAN (202402),
    PARTITION p202402 VALUES LESS THAN (202403),
    PARTITION p202403 VALUES LESS THAN (202404),
    PARTITION p_max VALUES LESS THAN MAXVALUE
);

五、实战案例:电商秒杀系统

5.1 系统架构设计

客户端 → Nginx → 应用服务器 → Redis集群 → 消息队列 → MySQL集群

5.2 核心代码实现

5.2.1 秒杀接口

@RestController
@RequestMapping("/seckill")
public class SeckillController {
    
    @Autowired
    private SeckillService seckillService;
    
    // 限流:每秒最多1000个请求
    @RateLimiter(name = "seckill", fallbackMethod = "seckillFallback")
    @PostMapping("/buy")
    public Result buy(@RequestBody SeckillRequest request) {
        // 参数校验
        if (request.getProductId() == null || request.getCount() <= 0) {
            return Result.error("参数错误");
        }
        
        // 用户限流:每个用户只能秒杀一次
        String userKey = "seckill:user:" + request.getUserId();
        if (!redisTemplate.opsForValue().setIfAbsent(userKey, 1, 10, TimeUnit.MINUTES)) {
            return Result.error("您已参与过秒杀");
        }
        
        // 执行秒杀
        boolean success = seckillService.seckill(request.getProductId(), request.getCount());
        return success ? Result.success("秒杀成功") : Result.error("秒杀失败");
    }
    
    public Result seckillFallback(SeckillRequest request, Exception e) {
        return Result.error("系统繁忙,请稍后重试");
    }
}

5.2.2 秒杀服务

@Service
public class SeckillService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Autowired
    private SeckillMapper seckillMapper;
    
    public boolean seckill(Long productId, int count) {
        String stockKey = "seckill:stock:" + productId;
        
        // 1. Redis预扣库存(原子操作)
        Long remaining = redisTemplate.opsForValue().decrement(stockKey, count);
        if (remaining < 0) {
            // 库存不足,回滚
            redisTemplate.opsForValue().increment(stockKey, count);
            return false;
        }
        
        // 2. 发送消息到MQ
        SeckillMessage message = new SeckillMessage();
        message.setProductId(productId);
        message.setCount(count);
        message.setTimestamp(System.currentTimeMillis());
        
        rabbitTemplate.convertAndSend("seckill.exchange", "seckill.key", message);
        
        return true;
    }
}

5.2.3 消息消费者

@Component
@RabbitListener(queues = "seckill.queue")
public class SeckillConsumer {
    
    @Autowired
    private SeckillMapper seckillMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @RabbitHandler
    public void process(SeckillMessage message) {
        try {
            // 1. 数据库扣减库存
            int updated = seckillMapper.deductStock(message.getProductId(), message.getCount());
            
            if (updated == 0) {
                // 库存不足,补偿Redis
                String stockKey = "seckill:stock:" + message.getProductId();
                redisTemplate.opsForValue().increment(stockKey, message.getCount());
                log.warn("数据库库存不足,补偿Redis: productId={}", message.getProductId());
                return;
            }
            
            // 2. 创建订单
            Order order = new Order();
            order.setProductId(message.getProductId());
            order.setCount(message.getCount());
            order.setStatus("SUCCESS");
            order.setCreateTime(new Date());
            
            seckillMapper.insertOrder(order);
            
        } catch (Exception e) {
            // 异常补偿
            log.error("处理秒杀消息失败: {}", e.getMessage());
            // 发送到死信队列,人工处理
        }
    }
}

5.3 数据库表结构

-- 秒杀库存表
CREATE TABLE seckill_stock (
    product_id BIGINT PRIMARY KEY,
    stock INT NOT NULL,
    version INT NOT NULL DEFAULT 0,
    INDEX idx_stock (stock)
) ENGINE=InnoDB;

-- 秒杀订单表
CREATE TABLE seckill_order (
    order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    count INT NOT NULL,
    create_time DATETIME NOT NULL,
    INDEX idx_user_product (user_id, product_id),
    INDEX idx_create_time (create_time)
) ENGINE=InnoDB;

-- 初始化库存
INSERT INTO seckill_stock (product_id, stock) VALUES (1001, 1000);

5.4 压测与优化

使用JMeter进行压测,监控以下指标:

  • QPS:目标10000+
  • 平均响应时间:< 100ms
  • 错误率:< 0.1%
  • 数据库连接数:< 500

优化点

  1. Redis集群分片,避免单点瓶颈
  2. 消息队列消费端多线程并行处理
  3. MySQL连接池大小调优
  4. 开启InnoDB缓冲池预热

六、总结与最佳实践

6.1 高并发处理黄金法则

  1. 缓存为王:90%以上的查询应该命中缓存
  2. 异步化:非核心操作全部异步处理
  3. 限流熔断:保护系统不被过载
  4. 读写分离:分散读压力
  5. 分库分表:突破单机瓶颈

6.2 不同场景下的策略选择

场景 主要策略 关键配置
读多写少 缓存 + 读写分离 缓存命中率 > 95%
写多读少 批量写入 + 异步 批量大小 100-1000
热点更新 乐观锁 + 热点拆分 重试机制
大表查询 分区 + 归档 分区键选择
秒杀场景 Redis预扣 + MQ 限流 + 降级

6.3 持续优化建议

  1. 定期慢查询分析:每周分析慢查询日志
  2. 监控告警:建立完善的监控体系
  3. 容量规划:提前预测业务增长
  4. 演练:定期进行故障演练和压测
  5. 文档化:记录所有优化措施和效果

6.4 工具推荐

  • 监控:Prometheus + Grafana + Alertmanager
  • 慢查询分析:pt-query-digest, Percona Toolkit
  • 性能分析:Performance Schema, sys schema
  • 压力测试:sysbench, JMeter
  • 数据库管理:MySQL Shell, MySQL Workbench

通过以上策略的综合运用,可以有效应对高并发场景下的流量洪峰,确保MySQL系统稳定运行。记住,没有银弹,需要根据具体业务场景选择合适的组合策略,并持续监控和优化。