1.MySQL体系结构

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表而不是基于库的,所以存储引擎也可以被称为表引擎。
- 默认存储引擎是InnoDB
2.相关语句
创建表时指定存储引擎
CREATE TABLE 表名( ... ) ENGINE=INNODB;
查看当前数据库支持的存储引擎
show engines;
3.InnoDB
InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎。
InnoDB 逻辑存储结构

6.存储引擎的比较
| 特点 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| 存储限制 | 64TB | 有 | 有 |
| 事务安全 | 支持 | - | - |
| 锁机制 | 行锁 | 表锁 | 表锁 |
| B+tree索引 | 支持 | 支持 | 支持 |
| Hash索引 | - | - | 支持 |
| 全文索引 | 支持(5.6版本之后) | 支持 | - |
| 空间使用 | 高 | 低 | N/A |
| 内存使用 | 高 | 低 | 中等 |
| 批量插入速度 | 低 | 高 | 高 |
| 支持外键 | 支持 | - | - |
7.存储引擎的选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
-
InnoDB: 如果应用对事物的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
-
MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
-
Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。
五、性能分析
1.查看执行频次
MySQL客户端连接成功后,通过show [session|global] status命令可以提供服务器状态信息,通过以下指令可以查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次
SHOW GLOBAL|SESSION STATUS LIKE 'Com_______';(7个下划线)
2.慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
查看慢查询日志开关状态(ON为开启,OFF为关闭)
show variables like 'slow_query_log';
启用慢查询日志(重启后失效)
SET GLOBAL slow_query_log = 'ON';
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息(重启后不会失效)
1.开启慢查询日志开关
slow_query_log=1
2.设置慢查询日志的时间(例如2秒),SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
-
在执行这两条命令前,需要以管理员身份打开慢查询日志文件,具体操作可以上网搜索
-
更改后记得重启MySQL服务,日志文件位置:/var/lib/mysql/localhost-slow.log
3.profile
show profile 能在做SQL优化时帮我们了解时间都耗费在哪里。通过 have_profiling 参数,能看到当前 MySQL 是否支持 profile 操作
查看当前 MySQL 是否支持 profile 操作
SELECT @@have_profiling;
查看当前profiling是否开启
select @@profiling;
profiling 默认关闭,可以通过set语句在session/global级别开启 profiling
SET profiling = 1;
查看所有语句的耗时
show profiles;
查看指定query_id(SQL语句的编号)的SQL语句各个阶段的耗时
show profile for query query_id;
查看指定query_id(SQL语句的编号)的SQL语句CPU的使用情况
show profile cpu for query query_id;
4.explain
EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序
语法(直接在select语句之前加上关键字 explain / desc)
EXPLAIN|DESC SELECT 字段列表 FROM 表名 HWERE 条件;

EXPLAIN 各字段含义:
-
id:select 查询的序列号,表示查询中执行 select 子句或者操作表的顺序(id相同,执行顺序从上到下(多表查询);id不同,值越大越先执行(子查询))
-
select_type:表示 SELECT 的类型,常见取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
-
type:表示连接类型,性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all
-
NULL:查询不访问任何表时出现,一般不会出现
-
system:当访问系统表或表中仅有一行记录时出现
-
const:使用
PRIMARY KEY或者UNIQUE索引进行精确匹配,且只匹配到一行记录时会出现 -
eq_ref:在连接查询里使用
PRIMARY KEY或者UNIQUE索引进行连接,且对于每个来自前面表的记录,在当前表中都能通过索引找到唯一匹配的记录时出现 -
ref:使用非唯一性索引查询时会出现
-
range:使用索引进行范围查询时出现
-
index:查询需要扫描整个索引树来获取数据时出现(不一定扫描全表)
-
all:扫描全表数据时出现
-
-
从优到劣:NULL> system > const > eq_ref > ref > range > index > ALL
-
possible_key:可能应用在这张表上的索引,一个或多个
-
Key:实际使用的索引,如果为 NULL,则没有使用索引
-
Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
-
rows:MySQL认为必须要执行的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的
-
filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
、索引
1.概述
索引是帮助 MySQL 高效获取数据 的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。
优点:
-
提高数据检索效率,降低数据库的IO成本
-
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:
-
索引列也是要占用空间的
-
索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE
2.索引结构
| 索引结构 | 描述 |
|---|---|
| B+Tree | 最常见的索引类型,大部分引擎都支持B+树索引 |
| Hash | 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
| R-Tree(空间索引) | 空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
| Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES |
| 索引 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| B+Tree索引 | 支持 | 支持 | 支持 |
| Hash索引 | 不支持 | 不支持 | 支持 |
| R-Tree索引 | 不支持 | 支持 | 不支持 |
| Full-text | 5.6版本后支持 | 支持 | 不支持 |
2.1 B+Tree

二叉树的缺点可以用红黑树来解决:

2.2 B-Tree(多路平衡查找树)
以一棵最大度数(max-degree,指一个节点的子节点个数)为5(5阶)的 b-tree 为例(每个节点最多存储4个key,5个指针):
- 每一个节点都存储数据

2.3 B+Tree

-
所有的数据都会出现在叶子节点
-
叶子节点形成一个单向链表
MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能:

2.4 Hash
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
- 如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决

特点:
-
Hash索引只能用于对等比较(=、in),不支持范围查询(betwwn、>、<、...)
-
无法利用索引完成排序操作
-
查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引
存储引擎支持:
在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的。
2.5 思考
为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
-
相对于二叉树,层级更少,搜索效率高
-
对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
-
相对于 Hash 索引,B+Tree 支持范围匹配及排序操作
3.索引分类
3.1 分类
| 分类 | 含义 | 特点 | 关键字 |
|---|---|---|---|
| 主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
| 唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
| 常规索引 | 快速定位特定数据 | 可以有多个 | |
| 全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:
| 分类 | 含义 | 特点 |
|---|---|---|
| 聚集索引(Clustered Index) | 将数据存储与索引放一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
| 二级索引(Secondary Index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |


3.3 聚集索引选取规则
-
如果存在主键,主键索引就是聚集索引
-
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
-
如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引
4.语法
创建索引
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
- 如果不加 CREATE 后面索引类型参数,则创建的是常规索引
查看索引
SHOW INDEX FROM table_name;
删除索引
DROP INDEX index_name ON table_name;
5.索引使用规则
5.1 最左前缀法则
如果索引关联了多列(联合索引),要遵守最左前缀法则 ,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
- 比较特殊的一种情况是如果直接跳跃第一列,那么第一列后面的索引都会失效,即此时联合索引完全失效
联合索引中,出现范围查询(<, >),范围查询右侧的列索引失效 。可以用>=或者<=来规避索引失效问题。
- 字段的位置可以任意,只要缺少复合索引某个字段,后面的字段索引全部失效
5.2 索引失效情况
-
在索引列上进行运算操作 ,索引将失效 。如:
explain select * from tb_user where substring(phone, 10, 2) = '15'; -
字符串类型字段使用时 ,不加引号 ,索引将失效 。如:
explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号 -
模糊查询 中,如果仅仅是尾部模糊匹配 ,索引不会失效 ;如果是头部模糊匹配 ,索引失效 。如:
explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效 -
用or 分割开的条件 ,如果 or 其中一个条件的列没有索引 ,那么涉及的索引都不会被用到
-
如果 MySQL 评估使用索引比全表更慢 ,则不使用索引
5.3 SQL 提示
是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
使用索引:
select * from 表名 use index(索引名) where 查询条件;
不使用哪个索引:
select * from 表名 ignore index(索引名) where 查询条件;
必须使用哪个索引:
select * from 表名 force index(索引名) where 查询条件;
- use 是建议,不一定使用,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force就是无论如何都强制使用该索引。
5.4 覆盖索引
即查询使用了索引,并且需要返回的列,在该索引中已经全部能找到
尽量使用覆盖索引,减少select *的书写
5.4.1 explain 中 extra 字段含义
-
using index condition:查找使用了索引,但是需要回表查询数据 -
using where; using index;:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询
5.4.2 面试题
一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案: select id, username, password from tb_user where username='itcast';
解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引
5.5 前缀索引
当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率,此时可以只降字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
create index 索引名 on 表名(列名(n));
CREATE INDEX idx_username ON user(username(10));
// 对 username 字段,只取前 10 个字符建索引
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
求选择性公式(截取长度可以任取,不断运行,直到合适为止):
select count(distinct 列名) / count(*) from 表名;
select count(distinct substring(列名, 1, 截取长度)) / count(*) from 表名;
-
show index 里面的sub_part可以看到接取的长度
-
distinct去重 只保留不重复的值,重复的只显示一条。
-- 全名区分度:0.95(非常高)
SELECT COUNT(DISTINCT name)/COUNT(*) FROM user;
-- 前2个字符区分度:0.6(太低)
SELECT COUNT(DISTINCT SUBSTRING(name,1,2))/COUNT(*) FROM user;
-- 前5个字符区分度:0.94(接近全名,够用)
SELECT COUNT(DISTINCT SUBSTRING(name,1,5))/COUNT(*) FROM user;
SUBSTRING(列名, 1, n) 里的 1 代表从第一个字符开始截取
MySQL 里所有跟 "字符串、索引、截取" 相关的位置,全部都是从 1 开始算的
5.6 不满足最左前缀法则仍可能触发复合索引的情况
只是可能触发,优化器可能仍选择全表扫描。
5.6.1 覆盖索引
当查询的字段完全包含在联合索引中,即使 WHERE 条件不满足最左前缀,MySQL 可能选择 全索引扫描(而非全表扫描)来直接返回数据例如:
索引(a,b,c),SELECT b, c FROM table WHERE b = 10;,数据可直接从索引中提取(无需回表),优化器可能选择扫描整个索引。
5.6.2 索引下推(ICP)
MySQL 5.6+ 支持 ICP,当查询条件包含部分联合索引列时,即使不满足最左前缀,存储引擎层仍会利用索引过滤数据,减少回表次数,例如:
索引(a,b,c),SELECT * FROM table WHERE a = 1 AND c = 3;,a 作为最左前缀生效,c 的条件通过 ICP 在存储引擎层过滤。
- 索引下推(ICP)需在 MySQL 5.6+ 且开启
optimizer_switch=index_condition_pushdown=on
5.6.3 排序/索引优化
若 ORDER BY 或 GROUP BY 的字段顺序与联合索引一致,即使 WHERE 条件不满足最左前缀,仍可能利用索引优化排序或分组,例如:
索引(a,b),SELECT * FROM table WHERE a > 1 ORDER BY b;,索引 (a, b) 天然按 a, b 排序,优化器可能选择索引避免 filesort。
5.6.4 范围查询后的等值查询
若查询条件中 最左前缀为范围查询,后续列的等值条件可能仍会使用索引,例如:
索引(a,b,c),SELECT * FROM table WHERE a > 1 AND b = 2;,索引会先按 a 的范围查找,再匹配 b 的等值条件(需结合索引下推)。
5.7 单列索引和联合索引
单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引
- 多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询
5.8 设计原则
-
针对于数据量较大,且查询比较频繁 的表建立索引
-
针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
-
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
-
如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
-
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
-
要控制索引的数量 ,索引并不是多多益善,索引越多 ,维护索引结构的代价就越大,会影响增删改的效率
-
如果索引列不能存储NULL值 ,请在创建表时使用NOT NULL约束它 。当优化器 知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询
七、SQL 优化
1.插入数据
1.1 普通插入
-
采用批量插入(一次插入的数据不建议超过1000条)
-
手动提交事务
-
主键顺序插入
1.2 大批量插入
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入
# 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入)
mysql --local-infile -u root -p
# 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
select @@local_infile;
# 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';
- 在Windows的cmd命令行中进行load导入数据时,
/root/sql1.log路径应使用绝对路径,格式为C:\\path\\to\\file.log(注意转义反斜杠)或C:/path/to/file.log(使用正斜杠),而且需要根据不同文件的默认换行符使用合适的换行符替换\n
二、逐段拆解(必考)
1. LOAD DATA LOCAL INFILE '文件路径'
- LOAD DATA:加载数据
- LOCAL :文件在客户端本地(不加 LOCAL 表示在服务器)
- INFILE:从文件读取
'/root/sql1.log':要导入的文件路径
2. INTO TABLE tb_user
- 导入到 tb_user 表
3. FIELDS TERMINATED BY ','
- FIELDS:字段
- TERMINATED BY:以... 分隔
',':字段用逗号分隔
4. LINES TERMINATED BY '\n'
- LINES:行
'\n':换行符,一行就是一条数据
三、文件内容长什么样?(对应格式)
sql1.log 里面必须长这样:
plaintext
1,张三,20
2,李四,21
3,王五,22
- 逗号分隔字段
- 换行分隔行→ 导入后就是 3 行数据
2.主键优化
数据组织方式:在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)
2.1 页分裂
页可以为空,也可以填充一半,也可以填充100%,每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列
2.1.1 主键顺序插入
当主键是顺序递增时,InnoDB会将新记录插入到当前页的末尾。如果当前页已满或剩余空间不足以插入这条记录,则分配一个新页并维护一个双向指针建立两个页的联系,然后继续插入,依次类推,直到插入完毕
按照顺序插入1,2,3,4,5,6,7,8,9,10,11,12,13,14
2.1.2 主键乱序插入
当主键无序时,InnoDB需要找到合适的插入位置进行插入。如果目标页已满或剩余空间不足以插入这条记录,会触发页分裂,即从当前页中间处分裂成两个子页并维护双向指针建立页之间的联系,然后继续插入,依次类推,直到插入完毕
插入50


2.1.3 应插入位置不在页末尾的情况
若插入点位于页中间且页内有空间,InnoDB会执行以下操作:
-
页内空间充足:移动页内现有记录,为新记录腾出空间
-
页内空间不足:触发页分裂
2.2 页合并
当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录到达 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前后)看看是否可以将这两个页合并(将要合并的页合并到被删除元素的页中)以优化空间使用。
MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或创建索引时指定
2.3 主键设计原则
-
满足业务需求的情况下,尽量降低主键的长度
-
插入数据时,尽量选择顺序插入 ,选择使用 AUTO_INCREMENT 自增主键
-
尽量不要使用 UUID 做主键或者是其他的自然主键,如身份证号
-
业务操作时,避免对主键的修改
3.order by优化
-
Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序 操作效率低
-
Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高
如果order by字段全部使用升序排序或者降序排序,则都会走索引,但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort,如果要优化掉Using filesort,则需要另外再创建一个索引,如:create index idx_user_age_phone_ad on tb_user(age asc, phone desc);,此时使用select id, age, phone from tb_user order by age asc, phone desc;会全部走索引
对于语句explain select id,age,phone from tb_user order by phone,age;Using filesort和Using index都会出现,原因是底层会先排序phone字段,由于缺少age,不满足最左前缀法则,不会使用idx_user_age_phone索引,出现Using filesort,然后排序age字段,满足最左前缀法则,使用索引idx_user_age_phone,出现Using index
总结:
-
根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
-
尽量使用覆盖索引
-
多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
-
如果不可避免出现filesort ,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)
group by 优化(核心就两点)
-
分组可以用索引提升效率索引本身是有序的,MySQL 直接按索引分组,不用额外排序。
-
group by 同样遵守 最左前缀法则 联合索引必须从左到右依次使用,不能跳过、不能乱序。
你举的例子(标准示范)
索引:
sql
idx_user_pro_age_stat (profession, age, status)
SQL:
sql
select ...
from tb_user
where profession = '软件工程'
group by age;
为什么符合最左前缀?
- where 用了 profession(最左第一列)
- group by 用了 age(第二列)
- 顺序:1→2 连续,没有跳跃→ 完全满足最左前缀,索引生效 ✅
反例:失效场景
sql
group by status;
跳过了 profession、age → 不满足最左 → 索引失效 ❌
sql
group by age, profession;
顺序反了 → 不满足最左 → 索引失效 ❌
总结
group by 优化可以利用有序索引避免排序,且分组字段必须遵循联合索引的最左前缀法则,where 条件在前、group by 紧跟其后,连续不跳跃即可高效利用索引。
5.limit优化
常见的问题如limit 2000000, 10,此时需要 MySQL 排序前2000010条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
疑问:这里明明没有使用order by,为什么要先排序前2000010条记录?
MySQL并不能保证数据插入顺序和读取顺序一致。 因为必须存在一个聚集索引,所以数据在插入时实际是按照主键顺序插入到B+树(索引底层结构就是一个树)中,所以实际存储是按照主键顺序存储的,但是插入时主键不一定有序,例如插入1、3、5、4、2,但
select * from table却是1、2、3、4、5,因此select * from table limit 2000000, 10本质上是select * from table order by id limit 2000000, 10,所以这里需要先排序再返回。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化
-- 此语句耗时很长
select * from tb_sku limit 9000000, 10;
-- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;
-- 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit
-- select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
-- 通过连表查询即可实现第一句的效果,并且能达到第二句的速度
select * from tb_sku as s, (select id from tb_sku order by id limit 9000000, 10) as a where s.id = a.id;
6.count优化
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where)
InnoDB 在执行 count(*) 时,需要把数据一行一行地从引擎里面读出来,然后累计计数。
优化方案:自己计数,如创建key-value表存储在内存或硬盘,或者使用redis。
6.1 count的几种用法
-
如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值
-
用法:count(*)、count(主键)、count(字段)、count(1)
-
count(主键)跟count(*)一样,因为主键不能为空 -
count(字段)只计算字段值不为NULL的行
-
count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟
count(*)一样,也可用其他非0数字代替1 -
count(null)返回0
-
6.2 各种用法的性能
-
count(主键):InnoDB引擎会遍历整张表,把每行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为空) -
count(字段):没有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加;有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加 -
count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一层,放一个数字 1 进去,直接按行进行累加 -
count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加
按效率排序 :count(字段) < count(主键) < count(1) < count(*) ,所以尽量使用 count(*)
7.update优化(避免行锁升级为表锁)
InnoDB 的行锁是针对索引 加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
如以下两条语句:
-
update student set no = '123' where id = 1;,这句由于id有主键索引,所以只会锁这一行 -
update student set no = '123' where name = 'test';,这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引