引言

在当今互联网应用中,高并发场景已成为常态。无论是电商秒杀、社交平台的热点事件,还是金融交易系统,都面临着海量用户同时访问数据库的挑战。MySQL作为最流行的开源关系型数据库,如何在高并发环境下保持稳定、高效的性能,是每个开发者和DBA必须掌握的核心技能。本文将从数据库设计、索引优化、查询优化、架构扩展、缓存策略等多个维度,提供一套完整的MySQL高并发处理实战指南。

一、数据库设计阶段的高并发考量

1.1 合理的表结构设计

核心原则:减少数据冗余,避免大表,控制单表数据量

在高并发场景下,表结构设计直接影响查询效率。一个设计不良的表结构会导致全表扫描、锁竞争等问题。

实战示例:电商订单表设计

-- 不良设计:将所有信息塞入一张大表
CREATE TABLE bad_order (
    order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(255), -- 冗余字段,可能导致表过大
    product_price DECIMAL(10,2),
    order_amount DECIMAL(10,2),
    order_status TINYINT,
    shipping_address TEXT, -- 大文本字段,影响查询性能
    payment_info TEXT, -- 大文本字段
    created_at DATETIME,
    updated_at DATETIME,
    INDEX idx_user_id (user_id),
    INDEX idx_product_id (product_id),
    INDEX idx_created_at (created_at)
);

-- 优化设计:拆分关联表,控制字段大小
CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_amount DECIMAL(10,2) NOT NULL,
    order_status TINYINT NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    INDEX idx_user_id (user_id),
    INDEX idx_created_at (created_at),
    INDEX idx_status_created (order_status, created_at) -- 复合索引
) ENGINE=InnoDB;

CREATE TABLE order_items (
    item_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(100) NOT NULL, -- 控制长度
    product_price DECIMAL(10,2) NOT NULL,
    quantity INT NOT NULL,
    INDEX idx_order_id (order_id),
    INDEX idx_product_id (product_id)
) ENGINE=InnoDB;

CREATE TABLE order_shipping (
    shipping_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL UNIQUE,
    address VARCHAR(500) NOT NULL, -- 限制长度
    phone VARCHAR(20) NOT NULL,
    receiver_name VARCHAR(50) NOT NULL,
    INDEX idx_order_id (order_id)
) ENGINE=InnoDB;

设计要点:

  • 将大字段(TEXT、BLOB)拆分到单独的表中
  • 控制VARCHAR字段长度,避免过度分配
  • 使用合适的数据类型(如用TINYINT代替VARCHAR存储状态)
  • 为高频查询字段建立索引

1.2 分区表策略

对于数据量极大的表,分区可以显著提升查询性能,特别是在高并发读写场景下。

实战示例:按时间分区的日志表

-- 创建按月分区的日志表
CREATE TABLE user_behavior_log (
    log_id BIGINT AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    behavior_type TINYINT NOT NULL,
    behavior_data JSON,
    created_at DATETIME NOT NULL,
    PRIMARY KEY (log_id, created_at) -- 分区键必须包含在主键中
) PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202301 VALUES LESS THAN (202302),
    PARTITION p202302 VALUES LESS THAN (202303),
    PARTITION p202303 VALUES LESS THAN (202304),
    PARTITION p202304 VALUES LESS THAN (202305),
    PARTITION p202305 VALUES LESS THAN (202306),
    PARTITION p202306 VALUES LESS THAN (202307),
    PARTITION p202307 VALUES LESS THAN (202308),
    PARTITION p202308 VALUES LESS THAN (202309),
    PARTITION p202309 VALUES LESS THAN (202310),
    PARTITION p202310 VALUES LESS THAN (202311),
    PARTITION p202311 VALUES LESS THAN (202312),
    PARTITION p202312 VALUES LESS THAN (202401),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 查询时,MySQL会自动选择相关分区
EXPLAIN SELECT * FROM user_behavior_log 
WHERE created_at >= '2023-05-01' AND created_at < '2023-06-01';
-- 输出会显示只扫描p202305分区

分区策略选择:

  • RANGE分区:适合按时间、ID范围的数据
  • LIST分区:适合按固定值分类的数据(如地区、类型)
  • HASH分区:适合均匀分布的数据
  • KEY分区:类似HASH,但使用MySQL内部哈希函数

二、索引优化策略

2.1 索引设计原则

黄金法则:覆盖索引 > 最左前缀 > 避免回表

在高并发场景下,索引设计不当会导致大量磁盘I/O和CPU消耗。

实战示例:复合索引设计

-- 用户订单查询场景
CREATE TABLE user_orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_status TINYINT NOT NULL,
    order_amount DECIMAL(10,2) NOT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL
);

-- 错误索引设计
ALTER TABLE user_orders ADD INDEX idx_user_id (user_id);
ALTER TABLE user_orders ADD INDEX idx_status (order_status);
ALTER TABLE user_orders ADD INDEX idx_created_at (created_at);

-- 优化后的索引设计
-- 1. 高频查询:用户订单列表(按时间倒序)
ALTER TABLE user_orders ADD INDEX idx_user_created (user_id, created_at DESC);

-- 2. 高频查询:用户未完成订单
ALTER TABLE user_orders ADD INDEX idx_user_status_created (user_id, order_status, created_at);

-- 3. 高频查询:统计用户订单金额
ALTER TABLE user_orders ADD INDEX idx_user_amount (user_id, order_amount);

-- 4. 删除低效索引
DROP INDEX idx_user_id ON user_orders;
DROP INDEX idx_status ON user_orders;
DROP INDEX idx_created_at ON user_orders;

索引优化技巧:

  • 覆盖索引:查询字段全部在索引中,避免回表
  • 前缀索引:对长字符串字段使用前缀索引
  • 索引下推:MySQL 5.6+支持,减少回表次数
  • 索引合并:合理利用多个单列索引的组合

2.2 索引使用分析

使用EXPLAIN分析索引使用情况

-- 分析查询语句的执行计划
EXPLAIN SELECT * FROM user_orders 
WHERE user_id = 12345 AND order_status = 1 
ORDER BY created_at DESC 
LIMIT 10;

-- 输出结果解读:
-- type: ALL(全表扫描)→ index(索引扫描)→ range(范围扫描)→ ref(索引查找)→ const(常量查找)
-- key: 使用的索引
-- rows: 预计扫描行数
-- Extra: 额外信息(Using index表示覆盖索引,Using filesort表示需要排序)

索引失效的常见场景:

-- 1. 隐式类型转换
SELECT * FROM user_orders WHERE user_id = '12345'; -- 字符串转数字,索引失效

-- 2. 函数操作
SELECT * FROM user_orders WHERE DATE(created_at) = '2023-05-01'; -- 索引失效

-- 3. 模糊查询前缀
SELECT * FROM user_orders WHERE user_id LIKE '%123'; -- 索引失效

-- 4. OR条件(部分版本)
SELECT * FROM user_orders WHERE user_id = 123 OR order_status = 1; -- 可能索引失效

-- 5. 联合索引未使用最左前缀
SELECT * FROM user_orders WHERE order_status = 1; -- idx_user_status_created索引失效

三、查询优化策略

3.1 SQL语句优化

*避免SELECT **,明确指定字段**

-- 不良实践
SELECT * FROM user_orders WHERE user_id = 12345;

-- 优化实践
SELECT id, order_amount, order_status, created_at 
FROM user_orders 
WHERE user_id = 12345 
ORDER BY created_at DESC 
LIMIT 10;

使用JOIN优化子查询

-- 低效:子查询
SELECT u.username, o.order_amount 
FROM users u 
WHERE u.id IN (
    SELECT user_id FROM user_orders 
    WHERE order_status = 1 
    GROUP BY user_id 
    HAVING COUNT(*) > 5
);

-- 高效:JOIN
SELECT u.username, o.order_amount 
FROM users u 
INNER JOIN (
    SELECT user_id, COUNT(*) as order_count 
    FROM user_orders 
    WHERE order_status = 1 
    GROUP BY user_id 
    HAVING order_count > 5
) o ON u.id = o.user_id;

3.2 分页优化

高并发分页查询的痛点:深度分页问题

-- 传统分页(深度分页时性能急剧下降)
SELECT * FROM user_orders 
WHERE user_id = 12345 
ORDER BY created_at DESC 
LIMIT 1000000, 10; -- 需要扫描前1000010条记录

-- 优化方案1:使用覆盖索引+延迟关联
SELECT t1.* 
FROM user_orders t1 
INNER JOIN (
    SELECT id 
    FROM user_orders 
    WHERE user_id = 12345 
    ORDER BY created_at DESC 
    LIMIT 1000000, 10
) t2 ON t1.id = t2.id;

-- 优化方案2:记录上次分页位置(适用于业务允许)
SELECT * FROM user_orders 
WHERE user_id = 12345 
AND created_at < '2023-05-01 12:00:00' -- 上次最后一条记录的时间
ORDER BY created_at DESC 
LIMIT 10;

-- 优化方案3:使用ES等搜索引擎处理深度分页

3.3 批量操作优化

高并发写入场景下的批量操作

-- 低效:逐条插入
INSERT INTO user_orders (user_id, order_amount, order_status) VALUES (1, 100, 1);
INSERT INTO user_orders (user_id, order_amount, order_status) VALUES (2, 200, 1);
INSERT INTO user_orders (user_id, order_amount, order_status) VALUES (3, 300, 1);

-- 高效:批量插入
INSERT INTO user_orders (user_id, order_amount, order_status) VALUES 
(1, 100, 1),
(2, 200, 1),
(3, 300, 1);

-- 批量更新
UPDATE user_orders 
SET order_status = 2 
WHERE id IN (1, 2, 3, 4, 5);

-- 批量删除(注意:大表删除可能锁表)
DELETE FROM user_orders 
WHERE id IN (1, 2, 3, 4, 5);
-- 优化:分批删除
DELETE FROM user_orders 
WHERE id BETWEEN 1 AND 1000;
-- 循环执行直到删除完成

四、架构扩展策略

4.1 读写分离

主从复制架构实现读写分离

-- 主库配置(my.cnf)
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
binlog-do-db = your_database

-- 从库配置(my.cnf)
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read-only = 1

-- 在主库创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

-- 在从库启动复制
CHANGE MASTER TO 
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=4;

START SLAVE;

应用层读写分离实现(Java示例)

// 使用Spring Boot + MyBatis实现读写分离
@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("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();
        }
    }
    
    // 数据源上下文
    public static class DataSourceContextHolder {
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
        
        public static void setDataSourceType(String dataSourceType) {
            contextHolder.set(dataSourceType);
        }
        
        public static String getDataSourceType() {
            return contextHolder.get() == null ? "master" : contextHolder.get();
        }
        
        public static void clearDataSourceType() {
            contextHolder.remove();
        }
    }
    
    // AOP切面实现读写分离
    @Aspect
    @Component
    public class DataSourceAspect {
        
        @Before("execution(* com.example.service.*.get*(..)) || execution(* com.example.service.*.list*(..))")
        public void setReadDataSource() {
            DataSourceContextHolder.setDataSourceType("slave");
        }
        
        @Before("execution(* com.example.service.*.add*(..)) || execution(* com.example.service.*.update*(..)) || execution(* com.example.service.*.delete*(..))")
        public void setWriteDataSource() {
            DataSourceContextHolder.setDataSourceType("master");
        }
        
        @After("execution(* com.example.service.*.*(..))")
        public void clearDataSource() {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

4.2 分库分表

垂直分库:按业务模块拆分

-- 原始单库设计
CREATE DATABASE ecommerce;
USE ecommerce;

CREATE TABLE users (id BIGINT, username VARCHAR(50), ...);
CREATE TABLE orders (id BIGINT, user_id BIGINT, ...);
CREATE TABLE products (id BIGINT, name VARCHAR(100), ...);

-- 垂直分库后
CREATE DATABASE user_db;
CREATE DATABASE order_db;
CREATE DATABASE product_db;

-- user_db
CREATE TABLE users (id BIGINT, username VARCHAR(50), ...);

-- order_db
CREATE TABLE orders (id BIGINT, user_id BIGINT, ...);

-- product_db
CREATE TABLE products (id BIGINT, name VARCHAR(100), ...);

水平分表:按数据范围或哈希分片

-- 按用户ID哈希分表(16个分表)
-- 分表规则:user_id % 16
CREATE TABLE user_orders_0 (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_amount DECIMAL(10,2),
    created_at DATETIME
);

CREATE TABLE user_orders_1 (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_amount DECIMAL(10,2),
    created_at DATETIME
);

-- ... 创建user_orders_2到user_orders_15

-- 分表路由逻辑(Java示例)
public class ShardingUtil {
    private static final int SHARD_COUNT = 16;
    
    public static String getTableName(String baseTable, long userId) {
        int shardIndex = (int) (userId % SHARD_COUNT);
        return baseTable + "_" + shardIndex;
    }
    
    public static String getTableName(String baseTable, String userId) {
        return getTableName(baseTable, Long.parseLong(userId));
    }
}

// 使用示例
String tableName = ShardingUtil.getTableName("user_orders", userId);
String sql = "SELECT * FROM " + tableName + " WHERE user_id = ?";

使用ShardingSphere实现分库分表

# sharding.yaml配置
dataSources:
  ds0: !!com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/db0
    username: root
    password: password
  ds1: !!com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/db1
    username: root
    password: password

shardingRule:
  tables:
    user_orders:
      actualDataNodes: ds${0..1}.user_orders_${0..15}
      tableStrategy:
        standard:
          shardingColumn: user_id
          preciseAlgorithmClassName: com.example.ModShardingAlgorithm
      databaseStrategy:
        standard:
          shardingColumn: user_id
          preciseAlgorithmClassName: com.example.ModShardingAlgorithm
  defaultDatabaseStrategy:
    standard:
      shardingColumn: user_id
      preciseAlgorithmClassName: com.example.ModShardingAlgorithm

# 分片算法实现
public class ModShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        long value = shardingValue.getValue();
        int size = availableTargetNames.size();
        int index = (int) (value % size);
        return availableTargetNames.stream()
            .skip(index)
            .findFirst()
            .orElseThrow(() -> new ShardingException("No available target"));
    }
}

五、缓存策略

5.1 多级缓存架构

应用层缓存 + 数据库缓存 + CDN缓存

// 使用Redis实现多级缓存
@Service
public class OrderService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private OrderMapper orderMapper;
    
    private static final String ORDER_CACHE_KEY = "order:%s";
    private static final long CACHE_TTL = 300; // 5分钟
    
    // 查询订单(先查缓存,再查数据库)
    public Order getOrderById(Long orderId) {
        String cacheKey = String.format(ORDER_CACHE_KEY, orderId);
        
        // 1. 查询Redis缓存
        Order order = (Order) redisTemplate.opsForValue().get(cacheKey);
        if (order != null) {
            return order;
        }
        
        // 2. 查询数据库
        order = orderMapper.selectById(orderId);
        if (order != null) {
            // 3. 写入缓存
            redisTemplate.opsForValue().set(cacheKey, order, CACHE_TTL, TimeUnit.SECONDS);
        }
        
        return order;
    }
    
    // 更新订单(删除缓存)
    public void updateOrder(Order order) {
        // 1. 更新数据库
        orderMapper.updateById(order);
        
        // 2. 删除缓存(避免脏数据)
        String cacheKey = String.format(ORDER_CACHE_KEY, order.getId());
        redisTemplate.delete(cacheKey);
    }
}

5.2 缓存穿透、击穿、雪崩解决方案

缓存穿透:查询不存在的数据

// 解决方案:布隆过滤器 + 空值缓存
public class BloomFilterCacheService {
    
    private BloomFilter<String> bloomFilter;
    private RedisTemplate<String, Object> redisTemplate;
    
    public Object get(String key) {
        // 1. 检查布隆过滤器(可能误判,但不会漏判)
        if (!bloomFilter.mightContain(key)) {
            return null;
        }
        
        // 2. 查询缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return value;
        }
        
        // 3. 查询数据库
        value = queryFromDB(key);
        if (value != null) {
            redisTemplate.opsForValue().set(key, value, 300, TimeUnit.SECONDS);
        } else {
            // 4. 缓存空值(防止穿透)
            redisTemplate.opsForValue().set(key, "NULL", 60, TimeUnit.SECONDS);
        }
        
        return value;
    }
}

缓存击穿:热点key过期时大量请求

// 解决方案:分布式锁 + 互斥锁
public class HotKeyCacheService {
    
    private RedisTemplate<String, Object> redisTemplate;
    private StringRedisTemplate stringRedisTemplate;
    
    public Object getHotKey(String key) {
        // 1. 查询缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return value;
        }
        
        // 2. 获取分布式锁
        String lockKey = "lock:" + key;
        String lockValue = UUID.randomUUID().toString();
        boolean locked = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
        
        if (locked) {
            try {
                // 3. 双重检查
                value = redisTemplate.opsForValue().get(key);
                if (value != null) {
                    return value;
                }
                
                // 4. 查询数据库
                value = queryFromDB(key);
                if (value != null) {
                    redisTemplate.opsForValue().set(key, value, 300, TimeUnit.SECONDS);
                }
            } finally {
                // 5. 释放锁(使用Lua脚本保证原子性)
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), 
                    Collections.singletonList(lockKey), lockValue);
            }
        } else {
            // 6. 等待并重试
            try {
                Thread.sleep(50);
                return getHotKey(key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        
        return value;
    }
}

缓存雪崩:大量key同时过期

// 解决方案:随机过期时间 + 熔断降级
public class CacheAvalancheService {
    
    private RedisTemplate<String, Object> redisTemplate;
    
    public void setCacheWithRandomTTL(String key, Object value) {
        // 基础TTL 300秒,随机增加0-60秒
        int baseTTL = 300;
        int randomTTL = baseTTL + new Random().nextInt(60);
        redisTemplate.opsForValue().set(key, value, randomTTL, TimeUnit.SECONDS);
    }
    
    // 熔断降级:当缓存失效时,直接返回降级数据
    public Object getWithFallback(String key) {
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value != null) {
                return value;
            }
            
            // 缓存失效,查询数据库
            value = queryFromDB(key);
            if (value != null) {
                setCacheWithRandomTTL(key, value);
                return value;
            }
            
            // 数据库也查不到,返回降级数据
            return getDefaultData();
            
        } catch (Exception e) {
            // 异常时返回降级数据
            return getDefaultData();
        }
    }
    
    private Object getDefaultData() {
        // 返回默认数据或空数据
        return new HashMap<>();
    }
}

六、事务与锁优化

6.1 事务隔离级别选择

不同隔离级别的性能对比

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

-- 设置隔离级别(会话级)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置隔离级别(全局级)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 不同隔离级别的特点:
-- 1. READ UNCOMMITTED:最低级别,可能读到脏数据,性能最好
-- 2. READ COMMITTED:默认级别,避免脏读,可能有不可重复读
-- 3. REPEATABLE READ:MySQL默认级别,避免脏读和不可重复读,可能有幻读
-- 4. SERIALIZABLE:最高级别,完全串行化,性能最差

高并发场景下的事务优化

-- 1. 缩短事务时间
-- 不良实践:在事务中进行复杂计算或网络调用
START TRANSACTION;
-- 复杂计算或网络调用
UPDATE account SET balance = balance - 100 WHERE user_id = 123;
COMMIT;

-- 优化实践:将非数据库操作移到事务外
-- 先进行网络调用
int result = callExternalService();
-- 再开始事务
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE user_id = 123;
COMMIT;

-- 2. 避免长事务
-- 设置事务超时时间
SET SESSION innodb_lock_wait_timeout = 5; -- 5秒超时

6.2 锁优化策略

InnoDB锁类型:

  • 行锁:锁定单行数据,高并发性能好
  • 表锁:锁定整张表,性能差
  • 间隙锁:锁定范围,防止幻读
  • 临键锁:行锁+间隙锁的组合

锁优化实战

-- 1. 避免全表扫描导致的锁升级
-- 不良实践:没有索引的WHERE条件
UPDATE user_orders SET order_status = 2 WHERE user_id = 12345;
-- 如果user_id没有索引,会锁全表

-- 优化:确保WHERE条件使用索引
ALTER TABLE user_orders ADD INDEX idx_user_id (user_id);
UPDATE user_orders SET order_status = 2 WHERE user_id = 12345;

-- 2. 避免间隙锁导致的死锁
-- 场景:两个事务同时插入相同范围的数据
-- 事务A:INSERT INTO user_orders (user_id, order_amount) VALUES (100, 100);
-- 事务B:INSERT INTO user_orders (user_id, order_amount) VALUES (101, 200);
-- 如果主键是自增的,可能产生间隙锁冲突

-- 解决方案:使用INSERT IGNORE或ON DUPLICATE KEY UPDATE
INSERT INTO user_orders (user_id, order_amount) 
VALUES (100, 100) 
ON DUPLICATE KEY UPDATE order_amount = order_amount + 100;

-- 3. 乐观锁 vs 悲观锁
-- 悲观锁:SELECT ... FOR UPDATE
START TRANSACTION;
SELECT * FROM user_orders WHERE id = 1 FOR UPDATE;
-- 业务处理
UPDATE user_orders SET order_status = 2 WHERE id = 1;
COMMIT;

-- 乐观锁:使用版本号
ALTER TABLE user_orders ADD COLUMN version INT DEFAULT 0;

-- 更新时检查版本号
UPDATE user_orders 
SET order_status = 2, version = version + 1 
WHERE id = 1 AND version = 0;

-- 如果更新失败(版本号不匹配),重试或提示用户

七、性能监控与调优

7.1 MySQL性能监控指标

关键性能指标(KPI):

  • QPS:每秒查询数
  • TPS:每秒事务数
  • 连接数:当前连接数
  • 慢查询数:执行时间超过阈值的查询
  • 锁等待时间:锁等待的平均时间
  • InnoDB缓冲池命中率:理想值 > 95%

使用Performance Schema监控

-- 启用Performance Schema(默认已启用)
-- 查看最耗时的查询
SELECT 
    DIGEST_TEXT,
    COUNT_STAR,
    SUM_TIMER_WAIT/1000000000000 as total_seconds,
    AVG_TIMER_WAIT/1000000000000 as avg_seconds
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- 查看表I/O统计
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    COUNT_READ,
    COUNT_WRITE,
    SUM_TIMER_READ/1000000000000 as read_seconds,
    SUM_TIMER_WRITE/1000000000000 as write_seconds
FROM performance_schema.table_io_waits_summary_by_table
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- 查看索引使用情况
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_READ,
    COUNT_WRITE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
ORDER BY COUNT_READ DESC;

7.2 慢查询日志分析

配置慢查询日志

# my.cnf配置
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1  -- 执行时间超过1秒的查询记录
log_queries_not_using_indexes = 1  -- 记录未使用索引的查询
min_examined_row_limit = 100  -- 至少检查100行的查询才记录

使用pt-query-digest分析慢查询日志

# 安装Percona Toolkit
sudo apt-get install percona-toolkit

# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log > slow_report.txt

# 输出报告示例:
# 1. 查询概览:总查询数、不同查询数、总时间、平均时间
# 2. 查询详情:每个查询的指纹、出现次数、总时间、平均时间
# 3. 查询样本:具体的SQL语句和执行计划

7.3 性能调优工具

使用EXPLAIN ANALYZE(MySQL 8.0+)

-- 查看详细的执行计划和实际执行时间
EXPLAIN ANALYZE 
SELECT u.username, o.order_amount 
FROM users u 
INNER JOIN user_orders o ON u.id = o.user_id 
WHERE u.id = 12345 
ORDER BY o.created_at DESC 
LIMIT 10;

-- 输出示例:
-- -> Sort: o.created_at DESC  (actual time=0.001..0.002 rows=1 loops=1)
--     -> Nested loop inner join  (cost=... rows=... actual time=... rows=... loops=1)
--         -> Index lookup on u using PRIMARY (id=12345)  (actual time=0.001..0.001 rows=1 loops=1)
--         -> Index lookup on o using idx_user_created (user_id=12345)  (actual time=0.001..0.001 rows=10 loops=1)

使用MySQL Workbench Performance Dashboard

-- 查看实时性能指标
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW GLOBAL STATUS LIKE 'Innodb_%';
SHOW ENGINE INNODB STATUS;

-- 查看当前连接和查询
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;

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

8.1 秒杀场景分析

秒杀特点:

  • 瞬时高并发:短时间内大量请求
  • 读多写少:商品查询多,下单少
  • 库存有限:需要防止超卖
  • 数据一致性要求高

8.2 秒杀系统架构设计

// 秒杀服务架构
@RestController
@RequestMapping("/seckill")
public class SeckillController {
    
    @Autowired
    private SeckillService seckillService;
    
    // 秒杀接口
    @PostMapping("/order/{seckillId}")
    public Result seckill(@PathVariable Long seckillId, @RequestParam Long userId) {
        // 1. 参数校验
        if (userId == null || seckillId == null) {
            return Result.error("参数错误");
        }
        
        // 2. 限流(令牌桶算法)
        if (!rateLimiter.tryAcquire()) {
            return Result.error("请求过于频繁,请稍后再试");
        }
        
        // 3. 执行秒杀
        return seckillService.createOrder(seckillId, userId);
    }
}

@Service
public class SeckillService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private SeckillMapper seckillMapper;
    
    @Autowired
    private OrderMapper orderMapper;
    
    // 秒杀库存KEY模板
    private static final String SECKILL_STOCK_KEY = "seckill:stock:%s";
    private static final String SECKILL_ORDER_KEY = "seckill:order:%s:%s";
    
    /**
     * 秒杀下单流程
     */
    @Transactional
    public Result createOrder(Long seckillId, Long userId) {
        // 1. 检查是否已秒杀过
        String orderKey = String.format(SECKILL_ORDER_KEY, seckillId, userId);
        if (redisTemplate.hasKey(orderKey)) {
            return Result.error("您已参与过该秒杀");
        }
        
        // 2. 检查库存(使用Redis原子操作)
        String stockKey = String.format(SECKILL_STOCK_KEY, seckillId);
        Long stock = redisTemplate.opsForValue().decrement(stockKey);
        
        if (stock == null || stock < 0) {
            // 库存不足,恢复库存
            redisTemplate.opsForValue().increment(stockKey);
            return Result.error("库存不足");
        }
        
        try {
            // 3. 创建订单(异步处理)
            Long orderId = createOrderAsync(seckillId, userId);
            
            // 4. 记录已秒杀
            redisTemplate.opsForValue().set(orderKey, orderId, 30, TimeUnit.MINUTES);
            
            return Result.success("秒杀成功", orderId);
            
        } catch (Exception e) {
            // 5. 异常时恢复库存
            redisTemplate.opsForValue().increment(stockKey);
            return Result.error("秒杀失败");
        }
    }
    
    /**
     * 异步创建订单
     */
    @Async
    public Long createOrderAsync(Long seckillId, Long userId) {
        // 1. 查询秒杀商品信息
        SeckillProduct seckillProduct = seckillMapper.selectById(seckillId);
        
        // 2. 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(seckillProduct.getProductId());
        order.setOrderAmount(seckillProduct.getPrice());
        order.setOrderStatus(1);
        order.setCreatedAt(new Date());
        
        // 3. 插入数据库
        orderMapper.insert(order);
        
        // 4. 扣减数据库库存(最终一致性)
        seckillMapper.decreaseStock(seckillId);
        
        return order.getId();
    }
}

8.3 秒杀系统优化要点

1. 库存预热

// 秒杀开始前,将库存加载到Redis
public void preloadStock(Long seckillId, Integer stock) {
    String stockKey = String.format(SECKILL_STOCK_KEY, seckillId);
    redisTemplate.opsForValue().set(stockKey, stock, 30, TimeUnit.MINUTES);
}

2. 请求队列削峰

// 使用消息队列异步处理订单
public void createOrderWithMQ(Long seckillId, Long userId) {
    // 1. 检查库存(Redis)
    // 2. 发送消息到MQ
    mqTemplate.send("seckill-order", new SeckillMessage(seckillId, userId));
    // 3. 返回排队中
}

// 消费者处理消息
@RabbitListener(queues = "seckill-order")
public void processSeckillOrder(SeckillMessage message) {
    // 1. 检查库存(数据库)
    // 2. 创建订单
    // 3. 发送通知
}

3. 限流策略

// 令牌桶限流
@Component
public class RateLimiter {
    private final RateLimiter limiter = RateLimiter.create(1000); // 每秒1000个令牌
    
    public boolean tryAcquire() {
        return limiter.tryAcquire();
    }
}

// 分布式限流(Redis + Lua)
@Component
public class DistributedRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean allowRequest(String key, int maxRequests, int timeWindow) {
        String luaScript = 
            "local current = redis.call('incr', KEYS[1]) " +
            "if current == 1 then " +
            "    redis.call('expire', KEYS[1], ARGV[1]) " +
            "end " +
            "if current > tonumber(ARGV[2]) then " +
            "    return 0 " +
            "else " +
            "    return 1 " +
            "end";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Long.class),
            Collections.singletonList(key),
            timeWindow, maxRequests
        );
        
        return result != null && result == 1;
    }
}

九、总结与最佳实践

9.1 高并发处理黄金法则

  1. 设计先行:在数据库设计阶段就考虑高并发需求
  2. 索引为王:合理的索引设计是性能的基础
  3. 缓存为盾:多级缓存架构保护数据库
  4. 架构扩展:读写分离、分库分表应对海量数据
  5. 监控调优:持续监控,持续优化

9.2 性能优化检查清单

  • [ ] 表结构是否合理,是否拆分大字段
  • [ ] 索引是否覆盖高频查询
  • [ ] 是否避免了SELECT *
  • [ ] 分页查询是否优化
  • [ ] 是否使用了批量操作
  • [ ] 是否实现了读写分离
  • [ ] 是否有合理的缓存策略
  • [ ] 事务是否短小精悍
  • [ ] 是否避免了锁竞争
  • [ ] 是否有性能监控和慢查询分析

9.3 持续优化建议

  1. 定期审查:每月审查慢查询日志和执行计划
  2. 压力测试:使用JMeter等工具模拟高并发场景
  3. A/B测试:优化前后对比性能指标
  4. 知识沉淀:建立团队的性能优化知识库
  5. 技术演进:关注MySQL新版本特性(如8.0的直方图、窗口函数)

通过以上全方位的策略和实战案例,您可以构建一个稳定、高效的MySQL高并发系统。记住,性能优化是一个持续的过程,需要根据业务发展和技术演进不断调整和优化。