MySQL-Note

mysql_logo

看网课的一些笔记 📝

影响性能的几个方面 🔰

  • 服务器硬件 CPU 内存 I/O子系统 网络
  • 服务器操作系统
  • 数据库存储引擎的选择
  • 数据库参数配置 => 影响巨大
  • 数据库结构设计和SQL语句 => 慢查询

数据库结构优化 🔧

目的:

  • 减少数据冗余
  • 尽量避免数据维护中出现更新, 插入, 删除异常
    • 插入异常: 表中某个实体随另一个实体存在
    • 更改表中某个实体的单独属性时, 需要对多行进行更新
    • 删除b表中的某一实体会导致其他实体的消失

逻辑设计

范式化:

  • 可以尽量减少数据冗余 😊
  • 更新操作比反范式化快 😊
  • 表通常比反范式化小 😊
  • 对于查询需要对多个表进行关联 😔
  • 更难进行索引优化 😔

反范式化:

  • 可以减少表的关联 😊
  • 更好进行索引优化 😊
  • 存在数据冗余和数据维护异常 😔
  • 对数据的修改需要更多成本 😔

在范式化基础上适当反范式化

物理设计

选择合适的存储引擎:
引擎

为表中的字段选择合适的数据类型:
当一个列可以选择多种数据类型时, 应该优先考虑数字类型, 其次是日期或二进制类型, 最后是字符类型. 对于相同级别的数据类型, 应该优先选择占用空间小的数据类型.

  • 财务相关数据应选择decimal
  • varchar和char的宽度是以字符(而不是字节)为单位, 如UTF-8一个字符占用3个字节
  • varchar不定长, 适用场景:
    1. 字符串列的最大长度比平均长度大很多
    2. 字符串列很少被更新
    3. 使用了多字节字符集(UTF-8)存储字符串
  • char定长, 适用场景:
    1. 适合存储长度近似的值(md5, 身份证)
    2. 短字符串(性别)
    3. 经常更新的字符串列

日期类型:

  • datatime: YYYY-MM-DD HH:MM:SS[.fraction]
    与时区无关, 占用8个字节
    范围: 1000-01-01 00:00:00 – 9999-12-31 23:59:59
  • timestamp: YYYY-MM-DD HH:MM:SS[.fraction]
    时区有关, 时间戳, 占用4个字节 在行的数据修改时可以自动修改timestamp列的值
    范围: 1970-01-01 00:00:00 – 2038-01-19 23:59:59
  • date: YYYY-MM-DD
    可以用date存储日期, 只要3个字节
    可以用日期时间函数进行日期间的计算
    范围: 1000-01-01 – 9999-12-31
  • time: HH:MM:SS[.fraction]

不要用字符串类型存储日期时间

如何为InnoDB选择逐渐:

  • 主键应该尽可能的小
  • 主键应该是顺序增长的
  • InnoDB的主键和业务主键可以不同

数据库索引优化 📌

为什么使用索引:

  • 大大减少了存储引擎需要扫描的数据量
  • 进行排序时避免使用临时表
  • 可以把随机I/O变为顺序I/O

索引不是越多越好:

  • 会增加写操作的成本
  • 会增加查询优化器的选择时间

B-tree索引

特点:

  • 以B+树结构存储数据
  • 加快数据查询速度
  • 适合进行范围查找

什么时候用到 😊

  • 等值查询
    user_name=”bxy”
  • 匹配最左前缀查询
    联合索引(user_name, id) 查询条件为user_name列时, 会使用该联合索引
  • 匹配列前缀查询
    user_name like ‘bx%’
  • 范围查找
    id > 10 and id < 100
  • 精确匹配左前列并范围匹配另外一列
    精确匹配user_name 并范围查询id
  • 只访问索引的查询
    只需访问索引, 无需访问数据行(无需回表操作) 即覆盖索引

使用限制 😔

  • 如果不是按照索引最左列开始查找, 则无法使用索引
    联合索引(user_name, id) 查询条件为id列时, 无法使用该联合索引
  • 使用索引时不能跳过索引中的列
    联合索引(user_name, id, role) 查询条件包含user_name和role 只有user_name会用到该联合索引
  • not in 和 <> 操作无法使用索引
  • 如果查询中有某个列的范围查询, 则其右边的所有列都无法使用索引
    联合索引(id, role, …) id使用范围查询, 则其右边的列都无法使用索引

Hash索引

特点:

  • 基于hash表实现
  • 只能用在等值查询中
  • hash索引中的所有列, 存储引擎都会每一行计算一个hash码, hash索引中存储的是hash码

限制:

  • hash索引必须进行二次查找
    索引中没有保存字段的值, 先通过hash索引找到对应的行, 再从行中读取字段
  • 无法用于排序
  • 不支持部分索引查找也不支持范围查找
  • hash码的计算可能存在hash冲突, 不适合用用在选择性差的列上(如性别, 只有男和女)

索引优化策略

  • 索引列上不能使用函数或表达式
    索引优化
  • 注意控制前缀索引的长度和索引列的选择性
  • 建立联合索引
    • 经常会被使用到的列优先
    • 选择性高的列优先
    • 宽度小的列优先
  • 使用索引覆盖

利用索引优化锁:

  • 索引可以减少锁定的行数
  • 索引可以加快处理速度, 同时也加快锁的释放

删除重复和冗余索引:

  • primary key(id), unique key(id), index(id)
  • index(a), index(a, b)
  • primary key(id), index(a, id)

SQL查询优化 🔎

获取有性能问题的SQL途径:

  • slow_query_log 启动慢查询日志
    常用慢查询日志分析工具: mysqldumpslow pt-query-digest
  • 通过information_schema中的processlist表来实时获取有性能问题的SQL

确定查询处理各个阶段消耗的时间:

  • 使用profile
  • 使用performance_schema

特定的SQL查询优化:

  • 修改大表的表结构 使用pt-online-schema-change
  • 如何优化not in和<>查询
    优化not_in
  • 使用汇总表优化查询
  • 待汇总…

数据库分库分表 📦

分库分表可以分担主数据库的写负载, 主从复制可以分担主数据库的读负载

不到万不得已不用考虑数据库分片

待汇总…

MySQL主从复制 💾

复制解决了什么问题:

  • 实现在不同服务器上的数据分布
  • 实现数据读取的负载均衡
  • 增强了数据的安全性
  • 实现数据库高可用和故障切换
  • 实现数据库在线升级

二进制日志

二进制日志binlog是MySQL服务层日志, 记录了所有对MySQL数据库的修改事件, 包括CRUD和对表结构的修改事件

二进制日志的格式:

  • 基于段的格式 binlog_format=STATEMENT 记录的是SQL语句
    • 日志记录量相对较小, 节约磁盘及网络I/O 😊
      只对一条记录修改或插入, row格式所产生的日志量小于段产生的日志量
    • 必须要记录上下文信息, 保证语句在服务器上执行结果和在主服务器上相同 😔
      特定函数如UUID(), user() 这样非确定性函数还是无法复制
      可能造成MySQL复制的主备服务器数据不一致
  • 基于行的日志格式 binlog_format=ROW (version > 5.7 default) 记录的是修改记录行的信息
    • 可以避免MySQL复制中出现的主从不一致问题 😊
      对每一行数据的修改比基于段的复制高效
    • 记录日志量较大 😔
      binlog_row_image=[FULL | MINIMAL | NOBLOB]
  • 混合日志格式 binlog_format=MIXED
    • 根据SQL语句由系统决定在段or行的日志格式中选择
    • 数据量的大小由所执行的SQL语句决定

二进制日志格式对复制的影响

基于SQL语句的复制(SBR)

优点:

  • 生成的日志量少, 节约网络传输I/O
  • 不强制要求主从数据库的表定义完全相同
  • 相比于基于行的复制方式更为灵活

缺点:

  • 对于非确定性事件, 无法保证主从复制数据一致性
  • 对于存储过程, 触发器, 自定义函数进行的修改也可能造成数据不一致
  • 相比于基于行的复制方式在从库上执行需要更多的行锁

基于行的复制(RBR) 👍

优点:

  • 可以应用于任何SQL的复制包括非确定性函数, 存储过程等
  • 可以减少从库锁的使用

缺点:

  • 要求主从数据库的表结构相同, 否则可能会中断复制
  • 无法在从库上单独执行触发器

MySQL复制工作方式

复制工作方式

  1. 主服务器将变更写入二进制日志(需先开启binlog, 否则再开启要重启数据库服务器)
  2. 从服务器读取主服务器的二进制日志变更并写入到relay_log中
    在从服务器上启动一个工作线程(I/O线程)与主库建立客户端连接, 在主库上启动二进制转储线程(binlogdump), I/O线程就通过binlogdump读取主库的binlog中的事件
    根据从什么位置开始读取binlog, 可以分为
    • 基于日志点的复制
    • 基于GTID的复制
  3. 从服务器读取relay_log中的事件, 在从库上重放(由SQL线程完成)

MySQL复制性能优化

影响主从延迟的因素

  • 主库写入二进制日志的时间 => 控制主库的事务大小, 分割大事务
  • 二进制日志传输时间 => 使用MIXED日志格式或设置set binlog_row_image=minimal
  • 默认情况只有一个SQL线程, 主库上b并发的修改在从库上变成了串行 => 使用多线程复制
0%