1. MySQL的索引
1.1 概述
索引是通过某种算法,构建出一个数据模型,用于快速找出在某个列中有以特定值的行,不使用索引,MySQL必须从一条记录开始读完整个表,直到找出相关的行,表越大查询数据所花的时间越多,如果表中查询的列有一个索引,MySQL能够快速达到一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间。
1.2 分类
索引是存储引擎用来快速查找记录的一种数据结构:
-
按照实现的方式类分,主要有Hash索引和B+Tree索引。
-
按照功能划分,单列索引(普通索引、唯一索引、主键索引),组合索引,全文索引,空间索引
1.3 特点
-
优点
- 大大加快数据查询的速度;
- 使用分组和排序进行数据查询时,可以显著减少查询时分组和排序的时间
- 创建唯一索引,能够保证数据库表中每一行数据的唯一性
- 在实现数据的参考完整性方面,可以加速表和表之间的连接。
-
缺点
- 创建索引和维护索引需要消耗时间,并且随着数据量的增加,时间也会增加
- 索引需要占据磁盘空间
- 对数据表中的数据进行增加、修改、删除时,索引也要动态的维护,降低了维护的速度
-
创建索引的原则
- 更新频繁的列不应该设置为索引
- 数据量小的表不要使用索引
- 重复数据多的字段不应设置为索引(一般来说,重复的数量超过15%就不该建索引)
- 首先应考虑对WHERE和ORDER BY涉及的列上建立索引
2. 存储引擎——MySQL的核心
存储引擎就是存储数据、建立索引、更新、查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可以被称为表类型。
2.1 分类
-
MyISAM:MySQL5.5之前的默认数据库引擎,最为常用。拥有较高的插入,查询速度,但不支持事务。
-
InnoDB:事务型速记的首选引擎,支持ACID事务,支持行级锁定,MySQL5.5成为默认数据库引擎。
-
Memory:所有数据置于内存的存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间,并且其内容会在MySQL重新启动会丢失。
-
Archive:非常适合存储大量的独立的、作为历史纪录的数据。因为它们不经常被读取。Archive拥有高效的插入速度,但其对查询的支持相对较差。
-
Federated:将不同的MySQL服务器联合起来,逻辑上组成一个完整的数据库。非常适合分布式应用。
-
CSV:逻辑上由都好分割数据的存储引擎。他会在数据库子目录里为每个数据表创建一个.csv文件。这是一种普通文本文件,每个数据行占用一个文本行。CSV存储引擎不支持索引。
-
BlockHole:黑洞引擎,写入的任何数据都会消失,一般用于记录binlog做赋值的中继。
-
ERFORMANCE_SCHEMA:该存储引擎主要用于收集数据库服务器性能参数。
功能 | MyISAM | MEMORY | InnoDB |
---|---|---|---|
存储限制 | 256TB | RAM | 64TB |
支持事务 | No | No | Yes |
支持全文索引 | Yes | No | No |
支持B树索引 | Yes | Yes | Yes |
支持哈希索引 | No | Yes | No |
支持集群索引 | No | No | Yes |
支持数据索引 | No | Yes | Yes |
支持数据压缩 | Yes | No | No |
空间使用率 | 低 | N/A | 高 |
支持外键 | No | No | Yes |
支持锁机制 | 表锁 | 表锁 | 表锁/行锁 |
3. MyISAM和InnoDB
MyISAM和InnoDB都是MySQL数据库中常见的存储引擎,各自特点和使用场景如下:
- MyISAM
-
特点
- 基于表格的存储引擎,对每个表对应三个文件,.frm文件存储表结构定义,.MYD文件存储数据,MYI文件存储索引。
- 不支持事务,也不支持外键。
- 支持全文索引(Full-Text-Indexing),适合于搜索场景
- 读操作比写操作效率高,适用于读多写少的场景。
-
使用场景
- 非事务性的应用,例如博客、新闻网站等;
- 对数据的读操作频繁,写操作相对较少的场景;
- 对全文搜索需求较多的场景;
- InnoDB
MySQL默认的事务型引擎。
- 特点
- 提供了对事务的支持(ACID兼容),具有提交、回滚和崩溃恢复的能力;
- 支持行级锁定,提高了并发性能;
- 支持外键约束。
- 采用聚合索引(Clustered Indexing),数据按照主键的顺序存储,可以提高查询性能。
- 使用场景
- 需要事务支持的应用,例如电子商务网站、金融系统等。
- 需要较高并发性能的应用,例如论坛、社交网络等。
- 需要支持外键约束的应用。
在存储引擎时,需要根据应用的具体需求来决定使用哪种引擎。如果是简单的应用,只需要基本的增删改查功能,可以选择MyISAM。如果需要支持事务、并发性能较高或者有复杂的数据关系,建议选择InnoDB。
MyISAM | InnoDB | |
---|---|---|
事务 | 不支持 | 支持 |
锁 | 表锁 | 表锁、行锁 |
文件存储 | 3个 | 1个 |
外键 | 不支持 | 支持 |
4. 事务
4.1 特性(ACID)
-
原子性(Atomicity):事务开始后所有操作,要么全部完成,要么全部不做,不可能停止在中间环节。事务执行过程中出错,会回滚到事务开始前的状态。即事务是一个整体。
-
一致性(Consistecy):事务开始前和结束后,数据库的完整性约束没有被破坏。
-
隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
-
持久性(Durability):事务完成后,事务对数据的所有更新将被保存到数据库,不能回滚。
4.2 事务靠什么保证
-
原子性:由undo log日志保证,它记录了需要回滚的日志信息,回滚时撤销一致性的SQL;
-
一致性:由其他三大特性共同保证,是事务的目的;
-
隔离性:由MVCC保证。
-
持久性:由redo log日志和内存保证,MYSQL修改数据时内存和redo log会记录操作,宕机时可恢复。
4.3 undo log和redo log的区别
-
缓冲池(buffer pool):主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从主磁盘加载并缓存),以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。
-
数据页(page):是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16KB,液中存储的是行数据。
-
redo log
- 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
- 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
-
undo log
- 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。undo log和redo log记录物理日志不一样,它是逻辑日志。
- 可以认为当delete一条记录时,undo log中会记录一条对应的Insert记录,反之亦然
- 当update一条记录时,他记录一条对应相反的update的记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
- undo log可以实现事物的一致性和原子性
事物的隔离性是如何保证的
-
锁:排他锁(如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁)
-
MVCC:多版本并发控制
4.4 事务的隔离级别
高并发情况下,并发事务会产生脏读、不可读重复、幻读等问题,这时需要用隔离级别来控制。
-
读未提交:允许一个事务读取另一个事务已提交的数据,可能出现不可重复度、幻读。
-
读提交:只允许事务读取另一个事务没有提交的数据可能会出现不可重复度,幻读。
-
可重复读:确保同一字段多次读取结果一致,可能出现幻读。
-
可串行化:所有事务逐次执行,没有并发问题。
InnoDB默认隔离级别为可重复读级别,分为快照读和当前读,并且通过间隙锁解决了幻读问题。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
4.5 事务的并发问题
-
脏读:事务A读取了事务B更新的数据,然后事务B回滚操作,那么事务A读取到的数据是脏数据。
-
不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致。
-
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这时插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,就成为幻读。
-
如何解决脏读、幻读、不可重复读
- 脏读:隔离级别设置为读提交、可重复读、串行化可解决脏读
- 不可重复读:隔离级别设置为可重复读、串行化可以解决不可重复读
- 幻读:隔离级别为串行化可以解决幻读、通过MVCV + 区间锁可以解决幻读
4.6 快照读和当前读
-
快照读:读取的是当前数据的可见版本,可能是会过期数据,不加锁的SELECT就是快照读。
-
当前读:读取的是数据的最新版本,并且当前读返回的记录都会上锁,保证其他事务不会并发修改这条记录。如UPDATE,DELETE,INSERT,SELECT FOR UNDATE(排他锁),SELECT LOCKIN SHARE MODE(共享锁)都是当前读。
4.7 MVCC
MVCC是多版本并发控制,为每次事务生成一个新版本数据,每个事务都有自己的版本,从而不加锁就解决读写冲突,这种读为快照读。只在读已提交和可重复读中生效。
实现的原理由以下四项保证:
-
undo log日志:记录数据历史版本
-
readView:事务进行快照度时动态生成产生的视图,记录了当前系统中活跃的事务id,控制哪个历史版本对当前事务可见。
-
隐藏字段DB_TRC_ID:最近修改记录的事务ID
-
隐藏字段DB_Roll_PTR:回滚指针,配合undolog指向数据的上一个版本
5. 锁机制
5.1 定义
锁是计算机协调多个进程或线程并发访问某一资源的机制(避免竞争)。在数据库中,除传统的计算资源(如CPU,RAM,I/O等)的争用外,数据也是一种供许多用户共享的资源。锁机制能保证数据并发访问的一致性、有效性,同时也影响数据库并发访问性能。
5.2 分类
-
根据对数据操作的粒度
- 表锁:操作时,会锁定整个表
- 行锁:操作时,会锁定当前操作行
锁类型 特点 表级锁 偏向MyISAM存储引擎,开销大,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 行级锁 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁。锁粒度最小,发生锁冲突的概率最低,并发度也最高。 - 表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用。
- 行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
-
根据对数据操作的类型
- 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
- 写锁(排它锁):当前操作没有完成之前前,会阻断其他写锁和读锁。
存储引擎 表级锁 行级锁 MyISAM 支持 不支持 InnoDB 支持 支持 MEMORY 支持 不支持 BOB 支持 不支持
6. 日志
分类:错误日志、二进制日志、查询日志、慢查询日志
6.1 错误日志
-
错误日志是MySQl中最重要的日志之一,记录了当MySQL启动个停止时,以及服务器在运行过程中发生任何严重错误时的相关信息,当数据库出现任何故障导致无法正常使用时,可以首先查看错误日志。
-
该日志默认是开启的,默认存放目录是mysql的数据目录,默认的日志文件名为hosyname.err(hostname为主机名)。
-
查看日志位置指令
SHOW variables LIKE 'log_error%'
6.2 二进制日志
二进制日志(BINLOG)记录了所有的DDL语句和DML语句,但不包括DQL语句。此日志对灾难时的数据恢复起着极其重要的作用,MySQL的主从复制就是通过改BINLOG实现的。
二进制日志在MySQL8默认开启,低版本需要通过配置文件开启,并配置MySQL日志的格式。
Windows系统:my.ini Linux系统: my.cnf
6.3 查询日志
查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。
默认情况下,查询日志是未开启的。
6.4 慢查询日志
慢查询日志记录了所有执行时间超过参数long_query_time设置值并且扫描记录数不小于min_examined_row_limit的所有SQL语句的日志。long_query_time默认为10秒,最小为0,精度可以到微秒。
7. 索引
7.1 定义
索引(index)是帮助MySQL高效的获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B+树),这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。这样可以提高数据检索的效率,降低数据库的IO成本(不需要全表扫描)。通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
B树与B+树对比
- 阶数更多,路径更短
- 磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据
- 查询效率B+树更加稳定
- B+树便于扫库和区间查询,非叶子节点是一个双向链表
7.2 聚簇索引和非聚簇索引
7.2.1 聚簇索引
- 含义:将数据存储与索引放到了一起,索引结构的叶子节点保存了行数据
- 特点:必须有,而且只有一个
7.2.2 二级索引(非聚簇索引)
- 含义:将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键
- 特点:可以存在多个
7.2.3 聚簇索引选取规则
- 如果存在主键,主键索引就是聚簇索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚簇索引。
- 如果表没有主键,或没有适合的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚簇索引。
7.2.4 回表查询
(首先介绍聚簇索引和非聚簇索引,然后介绍回表查询)
通过二级索引找到对应的主键值,到聚簇索引中查找整行数据,这个过程就是回表。
7.3 覆盖索引
覆盖索引是指查询使用了索引,并且需要返回的列在该索引中已经全部能够找到。
-
使用id查询,直接走聚簇索引,一次索引扫描,直接返回数据,性能高;
-
如果返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *;
(可以使用索引覆盖解决MySQL超大分页)
7.4 MySQL超大分页处理
-
在数据量比较大时,如果进行limit分页查询,在查询时,越往后分页查询的效率越低。
-
优化思路:一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。
7.5 创建索引的原则
-
针对数据量较大,且查询比较频繁的表建立索引(单表超过10万数据,增加用户体验)[重要]
-
针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引[重要]
-
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
-
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
-
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,避免回表,提高查询效率【重要】
-
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率【重要】
-
如果索引列不能存储NULL值,在创建表时使用NOT NULL约束。当优化器直到每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询
7.6 什么情况下索引会失效?
- 违反最左前缀法则
- 范围查询右边的列,不能使用索引
- 不要再索引列上进行运算操作,索引将会失效
- 字符串不加单引号,造成索引失效(类型失效)
- 以%开头的Like模糊查询,索引失效
8. 优化
SQL优化一般可以从设计、查询、索引和存储四方面进行。
8.1 如何定位慢查询?
-
采用运维工具(Skywalking),可以检测出是哪个接口,最终找到SQL问题
-
在数据库中开启慢日志查询,设置阈值(可以设置为2秒),一旦SQL执行超过阈值就hi记录到日志中。
8.2 一个SQL语句执行很慢,如何分析
可以使用MySQL自带的分析工具EXPLAIN或者DESC
-
通过key和key_len检查是否命中了索引(索引本身存在是否失效的情况)
-
通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描
-
通过extra建议判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复。
- possible_key:当前sql可能会使用到的索引
- key:当前sql实际命中的索引
- key_len:索引占用的大小
- Extra:额外的优化建议
- type:这条SQL的连接类型,性能由好到差依次为:NULL,system,const,eq_ref,ref,range,index,all
- system:查询系统中的表
- const:根据主键查询
- eq_ref:主键索引查询或唯一索引查询
- ref:索引查询
- range:范围查询
- index:索引扫描
- all:全盘扫描
extra 含义 Using where;Using index 查找使用了索引,需要的数据都在索引列中能找到,不需要回表查询数据 Using index condition 查找使用了索引,但是需要回表查询数据
8.3 SQL优化的经验
-
表的设计优化
参考阿里开发手册《嵩山版》
- 比如设置合适的数值(tinyint int bigint),要根据实际情况选择
- 比如设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低
-
索引优化[参考优化创建原则和索引失效]
-
SQL语句优化
- SELECT语句无比指定字段名称(避免直接使用SELECT *)
- SQL语句要避免造成索引失效的写法
- 尽量用UNION ALL代替UNION UNION会多一次过滤,效率低
- 避免在where子句中对字段进行表达式操作
- Join优化:能用INNERJOIN就不用LEFT JOIN RIGHT JOIN,如必须使用一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外面,把大表放到里面。LEFT JOIN或RIGHT JOIN,不会重新调整顺序。
-
主从复制、读写分离
如果数据库的使用场景读的操作比较多的时候,为了避免写的操作所造成的性能影响,可以采用读写分离的架构,读写分离解决的是数据库的写入,影响了查询的效率。
-
分库分表
8.4 主从同步原理
MySQL主从复制的核心是二进制bin log(记录DDL和DML)
- 主从在事务提交时,会把数据变更记录在二进制日志文件bin log中
- 从库读取主库的二进制日志文件bin log,写入到从库的中继日志Relay log
- 从库重做中继日志中的事件,将改变反应它自己的数据
8.5 分库分表
-
时机
- 前提:项目业务数据逐渐增多,或业务发展比较迅速(单表的数据量达1000W或20G以后)
- 优化已解决不了性能问题(主从读写分离,查询索引…)
- IO瓶颈(磁盘IO,网络IO)、CPU瓶颈(聚合查询、连接数太多)
-
拆分策略
-
垂直拆分
-
垂直分库:以表为依据,根据业务将不同表拆分到不同库中
特点
- 将业务对数据分级管理、维护、监控、扩展
- 在高并发下,提高磁盘IO和数据量连接数
-
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中
特点
- 冷数据分离
- 减少IO过度争抢,两表互不影响
-
-
水平拆分
-
水平分库:将一个库的数据拆分到多个库中
特点
- 解决了单库大数量,高并发的性能瓶颈问题
- 提高了系统的稳定性和可用性
-
水平分表:将一个表的数据拆分到多个表中(可以在同一个库内),按照记录分表
特点
- 优化单一表数量过大而产生的性能问题
- 避免IO争抢并减少锁表的几率
-
-