Mysql优化实战

一、查询缓存

  1. 启用

  2. 增大配置

二、change buff

  1. 唯一索引:每次更新因为要检查唯一性,必须要把数据读取到内存数据页中判断,所以直接更新了数据页数据即可,所以无法用change buff来加速。

  2. 普通索引:不需要唯一性检查,在更新时候,直接更新到change buff(虽然叫buff,也是可以落盘),然后在批量merge过程,在写多读少且数据量比较大,频繁写场景下场景,普通索引+change buff 提升性能还是比较明显。

三、日志文件

  1. binlog:mysql server层日志文件

  2. redolog:innodb 引擎独有日志文件

  3. 一条update语句完整的执行流程(innodb引擎)

  4. 两阶段提交:先写redolog,在写binlog,两个日志文件全部写完,才进行事务提交,用两阶段提交来保证,redolog和binlog的逻辑一致,同时保证了,binlog从库恢复数据的一致性。

  5. 相关配置:redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

  6. binlog日志格式:

MySQL 的二进制日志(binlog)是用于记录数据库变更的日志文件,在主从复制、数据恢复等场景中发挥着关键作用。binlog 主要有三种格式,下面为你详细介绍:

1. 基于语句的日志格式(Statement-Based Logging,SBL)

  • 原理:该格式会记录导致数据发生变化的 SQL 语句本身。例如,当执行一条 UPDATE 语句更新表中的数据时,binlog 会记录这条完整的 UPDATE 语句。
  • 优点
    • 日志量小:相比其他格式,记录 SQL 语句所需的存储空间较少,减少了磁盘 I/O 压力,降低了日志传输的开销。
    • 可读性高:可以直接查看 binlog 中的 SQL 语句,便于理解和分析数据库的变更情况。
  • 缺点
    • 部分语句存在问题:某些 SQL 语句在不同的服务器环境下可能会产生不同的结果,例如使用了 RAND()NOW() 等函数的语句,在主从复制时可能会导致数据不一致。
    • 并发性能问题:在处理一些复杂的 SQL 语句时,可能会出现主从复制延迟的情况。
  • 适用场景:适用于对日志量有严格要求,且 SQL 语句比较简单、不会产生不确定性结果的场景。

2. 基于行的日志格式(Row-Based Logging,RBL)

  • 原理:这种格式记录的是每一行数据的实际变更情况,而不是 SQL 语句。例如,当更新表中的某一行数据时,binlog 会记录该行数据更新前后的具体值。
  • 优点
    • 数据一致性高:由于记录的是实际的数据变更,避免了因 SQL 语句在不同环境下执行结果不一致而导致的主从数据不一致问题。
    • 支持复杂操作:对于一些复杂的操作,如 INSERT...SELECT 等,能够准确地复制数据。
  • 缺点
    • 日志量大:需要记录每一行数据的变更,会产生大量的日志信息,占用更多的磁盘空间。
    • 可读性差:由于记录的是数据的变更,而不是 SQL 语句,直接查看 binlog 时难以理解其含义。
  • 适用场景:适用于对数据一致性要求较高,尤其是在涉及复杂 SQL 操作和需要精确复制数据的场景。

3. 混合日志格式(Mixed-Based Logging,MBL)

  • 原理:结合了基于语句和基于行的日志格式。MySQL 会根据具体的 SQL 语句自动选择合适的日志记录方式。对于大多数简单的 SQL 语句,会采用基于语句的方式记录;对于可能导致数据不一致的 SQL 语句,则采用基于行的方式记录。
  • 优点
    • 兼顾性能和一致性:在保证数据一致性的同时,尽量减少日志量,提高了复制性能。
    • 灵活性高:可以根据实际情况自动选择合适的日志记录方式,减少了手动配置的复杂性。
  • 缺点:由于需要根据不同的 SQL 语句选择不同的记录方式,可能会增加一定的处理开销。
  • 适用场景:适用于大多数场景,尤其是在无法确定 SQL 语句是否会导致数据不一致的情况下,使用混合日志格式可以在性能和数据一致性之间取得较好的平衡。

你可以通过修改 MySQL 配置文件(如 my.cnf 或 my.ini)中的 binlog_format 参数来设置 binlog 的格式,示例如下:

[mysqld]
binlog_format = STATEMENT  # 设置为基于语句的日志格式
# binlog_format = ROW      # 设置为基于行的日志格式
# binlog_format = MIXED    # 设置为混合日志格式

设置完成后,重启 MySQL 服务使配置生效。

四、锁

  1. 全局锁:

    1. 全库逻辑备份:Flush tables with read lock

    2. msyqldump --single-transaction 导数据之前就会启动一个事务,利用MVCC支持,拿到一致性试图,这个过程中,数据是可以正常更新。有了这个功能,为什么还需要 FTWRL 呢?一致性读是好,但前提是引擎要支持这个隔离级别。

  2. 表级锁

    1. 表锁:lock tables t1 read, t2 write;

    2. 元数据锁:MDL(metadata lock)

  3. 行级锁

  4. 间隙锁和next-key锁:可重复读事务隔离级别当前读场景下,利用间隙锁解决幻读的问题。

五、减少行锁时间

  1. DB是行锁是两阶段锁:行锁是在需要的时候才加,但是并不是不需要就立刻释放,而是要等到事务结束时才释放。

    1. 优化点:

      1. 如果一个事物需要锁多个行,要把最可能造成锁冲突,最可能影响并发度的锁尽量往后放,减小行锁持有时间。

      2. 防止大事务,如果事务很大,且执行比较慢,那锁定时间就比较长,把大的事务拆分成多个小事务,减小行树及锁定时间。

六、死锁和死锁检测

  1. 原因:并发场景下多个线程出现循环资源依赖情况下,会出现死锁。

  2. 死锁处理两种策略:

    1. 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。

    2. 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。

七、事务隔离级别

  1. 读未提交
  2. 读已提交
  3. 可重复读
  4. 串行化

八、索引优化

1)慢sql治理原则

  1. join用法
    1. 超过三个表禁止join。
    2. 需要join的字段,数据类型保持绝对一致。
    3. 注意join顺序,让小表(注意小表的计算方式:表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表)驱动大表,同时被驱动表要有合适的索引,命中Index Nested-Loop Join策略。
    4. 注意join_buffer_size优化。
  2. varchar类型大字段时要指定索引长度,没有必要对全字段建立索引,根据实际文本区分度来决定索引长度,索引区分度公式:count(distinct left(列名, 索引长度))/count(*)的区分度来确定。区分度可以在90%以上。
  3. 严禁左模糊查询或者全模糊查询。
  4. 注意字段类型与值/关联字段要保持一致,避免类型不同导致的隐形转换,索引失效。
  5. 控制索引数量不要太多,避免一个查询,建一个索引。也避免误解索引不要太少,认为索引会消耗空间,会拖慢更新和新增速度。
  6. 根据业务场景,合并多个单独索引为联合索引,提升索引命中,提高覆盖索引,减少回表次数。
  7. 明确字段读取,减少回表次数,尽量走覆盖索引,或者索引下推,提升性能。
  8. 删除冗余索引,避免优化器选错索引。在对业务了解充分下,可以用force index来强制语句走指定索引。
  9. 统计总行数用count(*)优于count(1)和count(id) 性能,而且innodb引擎对由于事务隔离原因,每次统计都要每条取出来计算,所以代价要高,统计总行数,请单独用表,通过事务来统计,不要频繁使用count读全表来统计。
  10. 注意mysql加锁机制,避免出现锁等待,导致拖慢正常语句。
  11. delete语句加limit限制,一方面安全删除,一方面减少锁范围。

2)join_buffer_size 优化

join_buffer_size 是 MySQL 中的一个重要系统变量,主要用于优化多表连接操作的性能,下面从基本概念、工作原理、参数设置、查看和修改方法等方面详细介绍。

基本概念

join_buffer_size 表示 MySQL 在执行多表连接(如 JOIN 操作)时,用于存储中间结果的缓冲区大小,单位是字节。当 MySQL 执行 JOIN 操作时,如果无法将所有需要连接的数据一次性加载到内存中,就会使用这个缓冲区来临时存储部分数据,以减少磁盘 I/O 操作,提高连接查询的效率。

工作原理

当 MySQL 执行 JOIN 操作时,对于没有使用索引进行连接的情况,会将参与连接的表中的数据分批读取到 join_buffer 中进行匹配。具体过程如下:

  1. MySQL 会先将驱动表(通常是 JOIN 操作中记录数较少的表)的数据读取到 join_buffer 中。
  2. 然后逐行读取被驱动表的数据,与 join_buffer 中的数据进行匹配。
  3. 如果 join_buffer 空间不足,MySQL 会将已经匹配的数据输出,清空 join_buffer,再读取下一批数据进行匹配。

参数设置

  • 默认值join_buffer_size 的默认值因 MySQL 版本和系统配置而异,通常在较小的范围内(如 256KB 或 512KB)。
  • 合理设置:如果 JOIN 操作涉及的表数据量较大,且没有合适的索引支持,适当增大 join_buffer_size 可以减少磁盘 I/O,提高查询性能。但如果设置得过大,会占用过多的内存资源,影响系统的整体性能。

查看和修改方法

查看当前值

可以使用以下 SQL 语句查看当前 join_buffer_size 的值:

SHOW VARIABLES LIKE 'join_buffer_size';
修改参数值
  • 临时修改:在当前会话中临时修改 join_buffer_size 的值,该修改只在当前会话中生效,会话结束后恢复为原来的值。
SET SESSION join_buffer_size = 2097152; -- 设置为 2MB
  • 永久修改:要永久修改 join_buffer_size 的值,需要修改 MySQL 的配置文件(通常是 my.cnf 或 my.ini)。在配置文件中添加或修改以下行:
[mysqld]
join_buffer_size = 2097152  -- 设置为 2MB

修改完成后,重启 MySQL 服务使配置生效。

注意事项

  • 内存使用:增大 join_buffer_size 会增加内存的使用量,因此需要根据服务器的内存资源合理设置该参数。
  • 索引优化:虽然增大 join_buffer_size 可以在一定程度上提高 JOIN 操作的性能,但更有效的方法是通过优化索引来减少磁盘 I/O。确保在 JOIN 操作涉及的列上创建合适的索引,可以显著提高查询性能。

3)无效索引删除

1 - 根据统计分析出命中率较低(甚至没有使用过)得索引

SELECT DISTINCT

    b.TABLE_SCHEMA,

    b.TABLE_NAME,

    b.INDEX_NAME,

    a.count_star,

    a.COUNT_READ,

    a.COUNT_INSERT,

    a.COUNT_UPDATE,

    a.COUNT_DELETE

FROM

    performance_schema.table_io_waits_summary_by_index_usage a,

    INFORMATION_SCHEMA.STATISTICS b

WHERE

    a.OBJECT_SCHEMA = b.TABLE_SCHEMA

        AND a.INDEX_NAME = b.INDEX_NAME

        AND a.OBJECT_NAME = b.TABLE_NAME

        AND a.OBJECT_SCHEMA = b.INDEX_SCHEMA

        AND b.CARDINALITY > 0

ORDER BY a.count_star;

九、Springboot 事务传播机制

在 Spring Boot 中,事务传播机制定义了在多个事务方法相互调用时,事务如何进行传播和处理。Spring 框架提供了 7 种事务传播行为,这些行为可以通过 @Transactional 注解的 propagation 属性来指定。以下是对这 7 种事务传播机制的详细介绍:

1. PROPAGATION_REQUIRED(默认值)

  • 描述:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        methodB();
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释:在 methodA 中调用 methodB 时,由于 methodA 已经开启了一个事务,methodB 会加入到这个事务中。如果 methodB 抛出异常,整个事务会回滚。

2. PROPAGATION_SUPPORTS

  • 描述:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        methodB();
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释:如果 methodA 开启了事务,methodB 会加入该事务;如果 methodA 没有开启事务,methodB 会以非事务方式执行。

3. PROPAGATION_MANDATORY

  • 描述:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void methodA() {
        // 没有开启事务
        methodB();
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释:由于 methodA 没有开启事务,调用 methodB 时会抛出异常。

4. PROPAGATION_REQUIRES_NEW

  • 描述:无论当前是否存在事务,都会创建一个新的事务。如果当前存在事务,则将当前事务挂起。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        try {
            methodB();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 模拟异常
        throw new RuntimeException("Exception in methodA");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释methodB 会创建一个新的事务,即使 methodA 已经存在事务。如果 methodB 执行成功,而 methodA 抛出异常,methodB 的事务不会回滚。

5. PROPAGATION_NOT_SUPPORTED

  • 描述:以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        methodB();
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释methodB 会以非事务方式执行,即使 methodA 存在事务。

6. PROPAGATION_NEVER

  • 描述:以非事务方式执行操作。如果当前存在事务,则抛出异常。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        methodB();
    }

    @Transactional(propagation = Propagation.NEVER)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
    }
}
  • 解释:由于 methodA 开启了事务,调用 methodB 时会抛出异常。

7. PROPAGATION_NESTED

  • 描述:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。嵌套事务是外层事务的一部分,外层事务回滚时,嵌套事务也会回滚;但嵌套事务回滚不会影响外层事务。
  • 示例代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        userRepository.save(new User("John"));
        try {
            methodB();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 模拟异常
        throw new RuntimeException("Exception in methodA");
    }

    @Transactional(propagation = Propagation.NESTED)
    public void methodB() {
        // 执行一些数据库操作
        userRepository.save(new User("Jane"));
        // 模拟异常
        throw new RuntimeException("Exception in methodB");
    }
}
  • 解释methodB 会在嵌套事务内执行。如果 methodB 抛出异常,methodB 的事务会回滚,但 methodA 的事务不会回滚。如果 methodA 抛出异常,整个事务会回滚。

通过合理使用这些事务传播机制,可以更好地控制事务的范围和行为,确保数据的一致性和完整性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值