MySQL索引、锁和优化

MySQL

1.索引

  • 定义: 索引是存储引擎用于快速找到记录的一种数据结构

  • 作用: 索引可快速访问数据库表中的特定信息,索引是对一列或多列值进行排序的数据结构

  • 实现: 索引是在存储引擎层实现,不是服务层,不同的存储引擎具有不同的索引类型和实现

索引优化是对查询性能优化最有效的手段(索引能轻易将查询性能提高几个数量级)

2.索引的类型

  • B+Tree索引
  • 哈希索引
  • 全文索引
  • 空间数据索引

2.1 B+Tree索引

MySQL多数存储引擎的默认类型

利用索引,不再需要建立全表扫描来获取需要的数据

B+Tree索引:

  • 全键值查找
  • 键值范围查找
  • 键前缀值查找

最左前缀查找

B+Tree相比于BTree的优势

  • B+Tree非叶子结点只存储索引,B+Tree的高度更低,IO次数更少
  • B+Tree叶子节点前后管理,更加方便范围查询,结果都在叶子节点,查询效率稳定
  • B+Tree有利于对数据扫描,避免BTree的回溯扫描

2.2 哈希索引

哈希索引基于哈希表实现,哈希索引查找的速度非常快

哈希索引无法实现排序

CRC32(column) 计算哈希

3.索引的优点

  • 可以大大较少服务器需要扫描的数据量
  • 帮助服务器避免排序和临时表
  • 索引可以将随机I/O变成顺序I/O

索引的原则:

  • 索引不是越多越好,维护索引需要空间和时间

  • 频繁更新的数据,不宜建立索引

  • 数据量较少的表(数据量超大)的表没必要建立索引

  • 重复率小的列建立索引

  • 数据具有唯一性,建立索引

  • 频繁group by ,order by的列,建立索引

  • 经常用于查询条件的字段建议生成索引

4.高性能索引

正确的创建使用索引是实现高性能查询的基础

4.1独立的列

索引不能是表达式的一部分,不能是函数的参数

  • 无法启动列索引
SELECT actor_id FROM actor WHERE actor_id + 1 = 5;
  • 可以启动actor_id的索引
SELECT actor_id FROM actor WHERE actor_id  = 4;

4.2 前缀索引和索引选择性

对于很长的字符列,建立索引会让索引变得大且慢

  • 1.找到合适的索引字符长度
  • 2.创建前缀索引
ALTER TABLE city_demo ADD KEY (city(7));   # 索引字符长度为7

4.3多列索引

常见的错误:为每个列创建独立的列

4.4选择合适的索引列顺讯

  • 对于多个索引列,可先查看列对应数据基数
SELECT sum(staff_id = 2),SUM(customer_id = 584) FROM payment;

# SUM(staff_id = 2):7972
# SUM(customer_id = 584) :30

SELECT COUNT(DISTINCT staff_id)/COUNT(*) as staff_id_selectivity,
       COUNT(DISTINCT customer_id)/COUNT(*) as customer_id_selectivity,
       COUNT(*)
FROM payment;

选择性较高的作为索引列的第一列,应该将 customer_id 放到前面

5.查询性能优化

除了建立最好的索引,还需要合理的设计查询

优化三驾马车,需要齐头并进

  • 查询优化
  • 索引优化
  • 库表结构优化

5.1 慢查询基础

查询性能低下最基本的原因是访问的数据太多,大部分可以通过减少访问的数据量方式进行优化

  • 1.确认应用程序是否在检索大量超过需要的数据(过多的行,过多的列)
  • 2.确认MySQL服务器是否存在分析大量超过需要的数据行

请求不需要的数据

扫描额外的记录

  • 响应时间 = 服务时间 + 排队时间
  • 扫描的行数
  • 返回的行数

5.2 重构查询方式

复杂查询 vs 简单查询

切分查询

DELETE FROM messages WHERE created < DATA_SUB(NOW(),INTERVAL 3 MONTH);

# 优化
# 对大量删除数据进行切分,防止服务器一次性完成大量工作
# 锁住很多数据,占满整个事务日志,耗尽系统资源,阻塞小且重要的查询
row_affected = 0
do{
	 row_affected = do_query(
     "DELETE FROM messages WHERE created < DATA_SUB(NOW(),INTERVAL 3 MONTH) LIMIE 10000")
}while row_affected > 0

5.3 分解关联查询

  • 缓存的效率更高
  • 分解成单个查询,可以减少锁的竞争
  • 应用层关联,更加容易对数据库进行拆分(做到高性能和可扩展)
  • 减少冗余记录的查询
  • 引用层实现哈希关联代替MySQL的嵌套循环关联(IN 的方式代替关联查询)
# 查询当前会话的Last_query_cost的值来计算当前查询的成本
SHOW STATUS LIKE 'Last_query_cost';

6.主从复制

主要涉及三个线程

  • binlog线程 - 负责将主服务器上的数据更改写入二进制日志(Binary log)中
  • I/O线程 - 负责从主服务器读取二进制日志,并写入从服务器的中继日志(Relay log)
  • SQL线程 - 负责读取中继日志,解析出主服务器已经执行的数据更改并在从服务器中重放(Replay)

读写分离提高性能

  • 主服务器处理写操作
  • 从服务器处理读操作

主从服务器负责各自的读和写,极大程度缓解了锁的争用

从服务器可以使用MyISAM,提升查询性能以及节约系统开销

增加冗余,提高可用性

实现:

  • 增加反向代理(Reverse Proxy),代理服务器接受应用层传来的读写请求,决定转发道那个服务器

7.MySQL语句执行流程

Server层

  • 连接器
  • 查询缓存(MySQL 8.0版本后取消)
  • 分析器
  • 优化器
  • 执行器

存储引擎层

  • 数据的存储和读取

8.MySQL中的锁

  • 悲观锁
  • 乐观锁

间隙锁: Gap Lock

行锁只能锁住行,如果在记录之间插入数据就无法解决,引入间隙锁[左开右闭区间]

间隙锁+行锁 = NextKeyLock

  • 当前读(加悲观锁的实现,SELECT FOR UPDATE ,DELETE)
  • 快照读(不加锁)
posted @ 2021-07-25 14:41  dengshuo7412  阅读(34)  评论(0编辑  收藏  举报