Mysql体系结构:
连接层
位于最上层,是一些客户端和连接服务,主要完成一些类似于连接处理,授权认证及相关的安全方案。
服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
第二层,主要完成大多数的核心服务功能,如sql接口,并完成缓存的查询,sql的分析和优化,部分内置函数的执行。
所有跨存储引擎的功能也在这一层实现,如:过程,函数等。
引擎层
第三层,存储引擎真正的负责了mysql中数据的存储和提取,服务器通过API和存储引擎进行通信。
不同的存储引擎具有不同的功能,可以根据自己的需要来选取合适的存储引擎。
存储层
最底层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
存储引擎就是存储数据,建立索引,更新/查询数据等技术的实现方式。
存储引擎是基于表而不是基于库的
,所以存储引擎也可以被称为表引擎
。
在 MySQL 5.5 之后,默认的存储引擎是InnoDB。
相关操作:
-- 查询建表语句(以account表为例)
show create table account;
-- 建表时指定存储引擎
CREATE TABLE 表名(
...
) ENGINE=INNODB;
-- 查看当前数据库支持的存储引擎
show engines;
InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎
,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎
。
特点:
文件:
xxx.ibd
: xxx代表表名,使用InnoDB 作为引擎的数据表都会对应这样一个表空间文件,其存储对应数据表的表结构(frm、sdi)、数据和索引。参数:
innodb_file_per_table,决定多张表共享一个表空间还是每张表对应一个表空间
知识点:
查看 Mysql 变量:
show variables like 'innodb_file_per_table';
从ibd文件提取表结构数据:
(在cmd运行)
ibd2sdi xxx.ibd
InnoDB 逻辑存储结构:
MyISAM
是 MySQL 早期的默认存储引擎。
特点:
不支持事务
,不支持外键
不支持行锁
文件:
Memory 引擎的表数据是存储在内存中
的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存
使用。
特点:
文件:
特点 | InnoDB | MyISAM | Memory |
---|---|---|---|
存储限制 | 64TB | 有 | 有 |
事务安全 | 支持 | - | - |
锁机制 | 行锁 | 表锁 | 表锁 |
B+tree索引 | 支持 | 支持 | 支持 |
Hash索引 | - | - | 支持 |
全文索引 | 支持(5.6版本之后) | 支持 | - |
空间使用 | 高 | 低 | N/A |
内存使用 | 高 | 低 | 中等 |
批量插入速度 | 低 | 高 | 高 |
支持外键 | 支持 | - | - |
在选择存储引擎时,应该根据系统的特点选择合适的存储引擎。
对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
InnoDB
: 如果应用对事物的完整性
有比较高的要求,在并发条件下要求数据的一致性
,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择MyISAM
: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。Memory
: 将所有数据保存在内存中,访问速度快
,通常用于临时表及缓存
。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性如: 电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。
查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 等语句的访问频次:
SHOW GLOBAL STATUS LIKE 'Com_______';
或者
SHOW SESSION STATUS LIKE 'Com_______';
例子:
show global status like 'Com_______'
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
日志文件位置:/var/lib/mysql/localhost-slow.log
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
(更改后记得重启MySQL服务)
# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
查看慢查询日志开关状态:
show variables like 'slow_query_log';
show profile 能在做SQL优化时帮我们了解时间都耗费在哪里
。
通过 have_profiling 参数,能看到当前 MySQL 是否支持 profile 操作:
SELECT @@have_profiling;
profiling 默认关闭,可以通过set语句在session/global级别开启 profiling:
SET profiling = 1;
查看所有语句的耗时:
show profiles;
查看指定query_id的SQL语句各个阶段的耗时:
show profile for query query_id;
查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
EXPLAIN
或者 DESC
命令获取 MySQL 如何执行 SELECT 语句的信息
,包括在 SELECT 语句执行过程中表如何连接和连接的顺序
。
语法:
-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 HWERE 条件;
例子:
EXPLAIN 结果的各字段含义:
id
:select 查询的序列号,这是查询中每个操作的唯一标识符
表示查询中执行 select 子句或者操作表的顺序(id相同,执行顺序从上到下;id不同,值越大越先执行)
select_type
:表示 SELECT 的类型,常见取值有 SIMPLE(简单表,即不适用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
table
:显示操作涉及的表的名称
partitions
:表示查询操作所涉及的分区
type
:表示连接类型,指示数据库引擎在表之间进行连接的方式.
性能由好到差的连接类型为 NULL、system、const(表中只有一行匹配,主键或唯一索引的等值查询)、eq_ref(使用唯一索引进行查找)、ref(使用非唯一索引进行查找)、range(使用索引进行范围扫描)、index(全索引扫描[索引树])、all(全表扫描[磁盘])
possible_keys
:可能应用在这张表上的索引,一个或多个,如果为 NULL,则没有使用索引
Key
:实际使用的索引,如果为 NULL,则没有使用索引
Key_len
:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
rows
:MySQL估计要检索的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的
filtered
:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
Extra
: 表示额外的信息。
一些常见的值有:
Using where
: 表示使用了WHERE子句进行过滤。Using index
: 表示使用了索引覆盖查询,数据直接从索引中获取而无需访问表。Using temporary
: 表示使用了临时表来存储中间结果。Using filesort
: 表示使用了文件排序。索引是帮助 MySQL 高效获取数据的数据结构(有序)。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构之上实现高级查询数据的算法,这种数据结构就是索引。
个人理解: 数据表中的某个/某些字段会被选出来作为B+树节点的索引值,从而快速找到相应的行数据。
? 其中具有多个列的索引称为
联合索引
或复合索引
。
优点:
缺点:
索引结构 | 描述 |
---|---|
B+ Tree | 最常见的索引类型,大部分存储引擎都支持B+树索引 |
Hash | 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 空间索引是 MyISAM 存储引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES |
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+ Tree索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 支持 |
R-Tree索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本后支持 | 支持 | 不支持 |
发展历程:
二叉树【左小右大】:
二叉树的缺点可以用红黑树来解决:
红黑树也存在大数据量情况下,层级较深,检索速度慢的问题。
为了解决上述问题,可以使用 B-Tree 结构。
B-Tree (多路平衡查找树) 以一棵最大度数(max-degree,指一个节点的子节点个数)为5(5阶)的 b-tree 为例(5阶意味着每个节点最多存储4个key,有5个指针,5个子节点)
【无论是叶子节点还是非叶子节点,都会保存数据】
不是每个节点都有数据,数据存储在叶子节点的单向链表中。
与 B Tree 的区别:
MySQL 索引数据结构对经典的 B+Tree 进行了优化。
在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。
特点:
存储引擎支持:
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
普通索引 | 快速定位特定数据 | 可以有多个 | INDEX |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在 InnoDB 存储引擎中,根据索引的存储形式
,又可以分为以下两种:
分类 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index) | 将行数据与索引放一块(在一个B+树) ,索引结构的叶子节点保存了数据表的行数据 | 必须有,而且只有一个,一次即可查找到行数据 |
二级索引(非聚集索引)(Secondary Index) | 将行数据与索引分开存储(在两个B+树) ,索引结构的叶子节点关联的是数据表行数据的主键,不是真正的数据表的行数据,所以还需要一次回表查询(利用聚集索引查询行数据) | 可以存在多个,且需要两次才能查找到行数据! |
主键索引也被称为聚簇索引(clustered index),其余都称呼为非聚集索引也被称为二级索引(secondary index)
上面所讲的聚集索引和二级索引的概念主要是在InnoDB中讨论的!
MyISAM也支持聚集索引,但它将表的数据和索引分开存储,而不像InnoDB那样聚集在一起。在MyISAM中,表的数据存储在一个文件中,而索引存储在另一个文件中。
Memory存储引擎将所有数据存储在内存中,不会在磁盘上保留永久的存储结构。因此,关于聚集索引和二级索引的概念在Memory存储引擎中可能不太适用。
在InnoDB中,只存在一个聚集索引,而聚集索引的选取规则:
演示图:
在如下的演示图中,针对主键id字段建立了聚集索引,针对name字段建立了普通索引(二级索引),
每一个索引在 InnoDB 里面对应一棵B+树
,那么此时就存在着两棵B+树:【一个聚集索引的B+树,一个二级索引的B+树】
举例图示:
上图的流程为:
- 在name索引树上找到名称为Arm的节点 ,主键id为10
- 从id索引树上找到id为10的节点, 获取对应的行数据
- 从行数据中获取相应字段数据的值
在流程中从非主键索引树搜索回到主键索引树搜索的过程称为:回表,在本次查询中因为查询结果只存在主键索引树中,我们必须回表才能查询到结果。
再举例:
以下图为例,假设现在有一个表,存在id、name、age三个字段,其中id为主键,因此id为聚集索引,name建立索引为非聚集索引。关于id和name的索引,有如下的B+树,可以看到,聚集索引的叶子节点存储的是主键和行记录,非聚集索引的叶子节点存储的是主键。
从上面的索引存储结构来看,我们可以看到,在主键索引树上,通过主键就可以一次性查出我们所需要的数据,速度很快。这很直观,因为主键就和行记录存储在一起,定位到了主键就定位到了所要找的包含所有字段的记录。
但是对于非聚集索引,如上面的右图,我们可以看到,需要先根据name所在的索引树找到对应主键,然后再一次通过主键索引树查询到所要的记录(行数据),这个过程叫做回表查询。
所以尽量不要用
select *
,容易出现回表查询,降低效率,除非有联合索引包含了所有字段
上面的回表查询无疑会降低查询的效率,那么有没有办法让它不回表呢?这就是索引覆盖
。
索引覆盖是指一个查询的结果可以完全通过索引来获取,而不需要访问实际的数据行,即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能,这样就避免了回表。
当一个查询所有需要的列都包含在索引中时,可以说这个查询是通过索引覆盖的。
通俗来说,就是数据库引擎不需要去实际的数据表中读取数据行,而是直接通过索引就能够得到查询的结果,从而提高查询的性能
。
举个例子:
假设有一张数据表(id,name,age),其中id为主键(对应了聚集索引),并插入部分数据,此时数据表所含数据如下:
然后我们以name和age两个字段建立联合索引,对应的B+索引树如下:
此时如果执行如下sql:
SELECT age FROM student WHERE name = '小李';
那么此时的流程为:
注意:
1.尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *。
2.如何确定数据库成功使用了覆盖索引呢?
答: 当发起一个索引覆盖查询时,在explain 语句的extra列可以看到using index的信息。
-- 备注:id为主键,name字段创建的有索引
-- 所以id字段对应的是聚集索引,name字段上创建的索引是二级索引
select * from user where id = 10;select * from user where name = 'Arm';
答:第一条语句,因为第二条需要回表查询,相当于两个步骤,所以比第一句慢。
答:假设B+树的每个节点大小为一页且为16KB,数据表的一行数据大小为1kB,一页中可以存储16行这样的数据(即每个B+节点可存储16个行数据)。且假设InnoDB 的指针占用6个字节的空间,主键字段假设为bigint,占用字节数为8.
可得公式:n * 8 + (n + 1) * 6 = 16 * 1024
,其中 8 表示 bigint 占用的字节数,n 表示当前B+树节点存储的主键key的数量,
(n + 1) 表示指针数量(比key多一个)。算出n约为1170
。
如果树的高度为2(最下一层叶子节点的数量为1171),那么他能存储的数据量(数据行数)大概为:1171 * 16 = 18736
;
如果树的高度为3(最下一层叶子节点的数量为1171 *1171),那么他能存储的数据量(数据行数)大概为:1171 * 1171 * 16 = 21939856
。另外,如果有成千上万的数据,那么就要考虑分表(避免树的层级较深,检索速度慢的问题)
,涉及运维篇知识。
select id, username, password from tb_user where username='itcast';
答:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引(索引覆盖)。
单列索引:即一个索引只包含单个列。
创建语法:
CREATE INDEX index_name ON table_name (column_name);
示例: 假设有一个表 employees
包含列 employee_id
、first_name
、last_name
,如果我们为 first_name
列创建单列索引:
CREATE INDEX idx_first_name ON employees (first_name);
这将创建一个名为 idx_first_name
的单列索引,用于加速对 first_name
列的查询。
注意:
- 适用于针对单个列的查询。
- 简单,占用较少的存储空间。
联合索引:即一个索引包含了多个列。
索引的底层是一颗B+树,那么联合索引的底层也是一颗B+树,只不过联合索引的B+树节点中存储的是键值。
且由于构建一棵B+树只能根据一个值来确定索引关系,所以数据库依赖联合索引的最左的字段来构建。
举例:创建一个(a,b)的联合索引,那么它的索引树就是下图的样子。
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。但是我们又可发现a在等值的情况下,b值又是按顺序排列的,但是这种顺序是相对的。这是因为MySQL创建联合索引的规则是首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序。
创建语法:
CREATE INDEX index_name ON table_name (column1, column2, ...);
示例: 假设我们为 employees
表的 first_name
和 last_name
列创建联合索引:
CREATE INDEX idx_firstname_lastname ON employees (first_name, last_name);
这将创建一个名为 idx_firstname_lastname
的联合索引,用于加速对 first_name
和 last_name
列的组合查询。
注意:
适用于涉及多个列的查询,尤其是在涉及这些列的组合查询时。
多列的组合查询可能会更高效。
由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。
ORDER BY子句也遵循此规则。
建立了一个联合索引,比如(col1,col2,col3),则其等价于/相当于建立了如下的几个索引: (col1),(col1,col2),(col1,col2,col3)!!
前缀索引是一种索引类型,它并不包含整个列的值,而只包含列值的前缀部分。
这种索引可以用来减少索引的存储空间,提高查询性能,特别是在对长文本列或大字符串进行索引时。
CREATE INDEX index_name ON table_name (column_name(length));
其中,length
是指定前缀长度的参数。通过指定 length
,你可以选择索引列值的前几个字符,而不是整个列的值。
示例:
假设有一个表 products
包含一个长文本列 description
,我们希望对这个列创建前缀索引:
CREATE INDEX idx_description_prefix ON products (description(100));
在这个例子中,idx_description_prefix
是一个前缀索引,只包含 description
列值的前100个字符。
这样的索引可以提高查询性能,同时占用更少的存储空间。
总的来说,前缀索引是一种用于优化存储和查询性能的技术,特别适用于对大字符串或长文本列进行索引的场景。
1.创建主键索引:
-- 例子中,通过将 PRIMARY KEY 关键字放在列定义上,创建了名为 id 的主键索引。 CREATE TABLE your_table ( id INT PRIMARY KEY, name VARCHAR(50), age INT );
2.创建唯一索引:
-- 例子中,通过在列定义上使用 UNIQUE 关键字,创建了名为 username 的唯一索引。 CREATE TABLE your_table ( username VARCHAR(50) UNIQUE, email VARCHAR(100), age INT );
3.创建普通索引
-- 写法1:通过在列定义后面使用 INDEX 关键字,创建了名为 idx_category 的普通索引。 CREATE TABLE your_table ( category_id INT, product_name VARCHAR(100), price DECIMAL(10, 2), INDEX idx_category (category_id) ); -- 写法2::通过在列定义后面使用 Key 关键字,创建了名为 idx_example 的普通索引。 CREATE TABLE your_table ( column1 INT, column2 VARCHAR(50), KEY idx_example (column1, column2) ); --实际上,这两个关键字在这个语境中是可以互换使用的,都表示创建普通索引。选择使用哪一个关键字通常是个人或团队的偏好问题,对于MySQL来说,它们是等效的。
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
如果不
CREATE
后面不加索引类型参数,则创建的是常规索引(默认)
常规索引(默认): 如果你不指定任何特殊类型,直接创建索引,那么就是常规索引。这种索引允许表中存在相同的索引值,适用于一般的查询需求。
CREATE INDEX index_name ON table_name (index_col_name, ...);
唯一索引: 如果你希望索引列的值在整个表中是唯一的,不允许重复,你可以创建唯一索引。这对于确保数据完整性很有用。
CREATE UNIQUE INDEX unique_index_name ON table_name (index_col_name, ...);
全文索引: 如果你需要在文本数据中进行全文本的搜索和匹配,你可以创建全文索引。
CREATE FULLTEXT INDEX fulltext_index_name ON table_name (index_col_name, ...);
区别:
- 常规索引允许索引列字段出现重复值,唯一索引不允许索引列字段出现重复值。
- 全文索引用于文本数据字段的全文搜索。
SHOW INDEX FROM table_name;
DROP INDEX index_name ON table_name;
案例:
-- name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- phone手机号字段的值非空,且唯一,为该字段创建唯一索引
create unique index idx_user_phone on tb_user (phone);
-- 为profession, age, status创建联合索引(多个字段作为索引)
create index idx_user_pro_age_stat on tb_user(profession, age, status);
-- 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);
-- 删除索引
drop index idx_user_email on tb_user;
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 '%工程';
,前后都有 % 也会失效。在设计查询的sql语句时需要注意一些原则,以充分利用索引来提高查询性能。
首先要知道,最左匹配原则都是针对联合索引来说的,有必要了解一下联合索引的原理。
了解了联合索引,那么为什么会有最左匹配原则这种说法也就理解了。
我们都知道索引的底层是一颗B+树,那么联合索引当然也是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。
而构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引中最左的字段来构建B+树。
例子:
假如创建一个(a,b)的联合索引,那么它的索引树是这样的:
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,而跳过了a,单独一个b是无序的。【这就对应了最左前缀法则的第一部分内容,下面会讲】
同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对有序的,不是绝对有序的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围(不是一个值),在这个范围中b是无序的。但可以用>=或者<=来规避索引失效问题,因为=号会让a出现一个定值,a只要是定值,那么b就是相对有序的了。
【这就对应了最左前缀法则的第二部分内容,下面会讲】
如果创建了联合索引,则在书写sql查询语句时,要遵守最左前缀法则!它包括两部分内容:
第一部分:
sql查询语句的查询条件需从联合索引的最左列开始,并且不跳过联合索引中的列,如果跳跃某一列,联合索引将部分失效(后面的字段索引失效)。换句话说: 即如果查询的时候查询条件精确匹配索引字段从最左边开始的连续一列或几列,则此索引生效。
举例:
假设有一个联合索引 (col1, col2)
。最左前缀法则指的是,如果你想要充分利用这个索引,查询条件必须从最左边的列开始,并且不跳过中间的列。如果跳过了某一列,那么索引就会部分失效(因为此时后面的字段是无序的,只有未跳过之前的列是有序)。
-- 良好的使用方式(可以命中索引)
SELECT * FROM your_table WHERE col1 = 'value1';
-- 良好的使用方式(可以命中索引)
-- 这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 col2= xx and col1 =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。
SELECT * FROM your_table WHERE col1 = 'value1' AND col2 = 'value2';
-- 不良的使用方式(最左前缀法则失效)
-- 。这是因为MySQL创建联合索引的规则是首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序。所以col2=xx这种查询条件没有办法利用索引。
SELECT * FROM your_table WHERE col2 = 'value2';
在第一个例子中,查询条件从 (col1, col2)
的最左列开始,索引能够被充分利用。
而在第二个例子中,查询条件跳过了 col1
,导致 (col1, col2)
联合索引最左前缀法则失效。
注意:
- 查询的时候如果联合索引的多个字段都用上了,但是顺序不同,如 col2= xx and col1 =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。
第二部分:
sql查询语句的查询条件在进行范围查询
时,如果联合索引中的某一列字段出现了 <
>
,between
,like
等,则该列字段右侧所有索引字段失效。【但可以用>=或者<=来规避索引失效问题】
举例:
假设有一个联合索引(a,b,c,d),此时有如下的sql语句:
select * from your_table where a=1 and b=2 and c>3 and d=4;
那么d是用不到索引的,因为c字段是一个范围查询,它之后的字段会停止匹配索引。
尽量不要用
select *
,容易出现回表查询,降低效率,除非有联合索引包含了所有字段
SQL提示是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示从而传递给数据库
来达到优化操作的目的。
如,使用索引:
explain select * from tb_user use index(idx_user_pro) where profession="软件工程";
不使用哪个索引:
explain select * from tb_user ignore index(idx_user_pro) where profession="软件工程";
必须使用哪个索引:
explain select * from tb_user force index(idx_user_pro) where profession="软件工程";
use 是建议,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force就是无论如何都强制使用该索引。
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)
合理选择主键(主键应具有唯一性)
主键列的值应该具有唯一性,确保每一行都能被唯一标识。
CREATE TABLE your_table (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
...
);
避免过长的主键
避免使用过长的主键,因为主键通常会被用作其他表的外键,较长的主键可能导致索引的大小增加,从而影响查询性能。
CREATE TABLE your_table (
user_id INT PRIMARY KEY, -- 避免使用过长的主键
name VARCHAR(50),
...
);
利用自增主键
插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键
自增主键通常可以提高插入性能,并确保新插入的数据总是添加到索引的末尾。
CREATE TABLE your_table (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
...
);
合理使用聚集索引(指定存储引擎为InnoDB)
在InnoDB等支持聚集索引的数据库中,主键通常是聚簇索引,这意味着行数据的物理存储顺序与主键的顺序相同。
这有助于提高范围查询的性能。
CREATE TABLE your_table (
id INT PRIMARY KEY,
name VARCHAR(50),
...
) ENGINE=InnoDB;
业务操作时,避免对主键的修改
主键的值不宜频繁更新,因为更新主键可能涉及到对其他索引的维护,导致性能开销。
-- 不推荐的更新方式
UPDATE your_table SET id = id + 1 WHERE ...;
尽量不要使用 UUID(尽管UUID有全局唯一性的优势) 做主键或者是其他的自然主键,如身份证号.
批量插入:
批量插入是提高插入性能的有效手段。 相较于逐条插入,批量插入能减少事务处理和日志写入的开销,提高整体性能。
-- 逐条插入
INSERT INTO your_table (column1, column2) VALUES (value1, value2);
INSERT INTO your_table (column1, column2) VALUES (value3, value4);
-- 批量插入
INSERT INTO your_table (column1, column2) VALUES
(value1, value2),
(value3, value4),
(value5, value6);
如果一次性需要插入大批量数据,使用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';
禁用索引
在大批量插入数据时,可以考虑在插入过程中禁用索引,然后再启用索引。这有助于减少索引维护的开销。
-- 禁用索引
ALTER TABLE your_table DISABLE KEYS;
-- 执行插入操作
-- 启用索引
ALTER TABLE your_table ENABLE KEYS;
合理设置事务块,手动提交事务
对于事务性要求不是很高的插入操作,可以考虑将插入语句放在更大的事务块中,减少事务的开销,然后手动提交事务。
START TRANSACTION;
-- 执行插入操作
COMMIT;
主键顺序插入数据
按照主键的顺序插入数据,有助于减少数据页的频繁分裂和移动,提高写入性能
-- 主键顺序插入
INSERT INTO your_table (id, column1, column2) VALUES (1, 'value1', 'value2');
INSERT INTO your_table (id, column1, column2) VALUES (2, 'value3', 'value4');
INSERT INTO your_table (id, column1, column2) VALUES (3, 'value5', 'value6');
避免全表更新
避免不带WHERE条件的全表更新,因为这样的更新会涉及到整个表的数据,性能开销较大。
-- 不推荐的全表更新
UPDATE your_table SET column1 = value1;
确保更新操作的WHERE条件涉及的列上有合适的索引,以提高检索效率和避免行锁升级为表锁
。
InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
如以下两条语句:
update student set no = '123' where id = 1;,-- 这句由于id有主键索引,所以只会锁这一行;
update student set no = '123' where name = 'test'; -- 这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引
使用limit限制更新行数
在进行大量更新时,可以使用LIMIT限制每次更新的行数,避免一次性锁定大量数据。
UPDATE your_table
SET column1 = value1
WHERE condition1
LIMIT 1000;
使用索引
确保 GROUP BY 语句中涉及的列上有合适的索引,以提高检索效率。
分组操作时,索引的使用也是满足最左前缀法则的
-- 假设category有合适的索引
SELECT category, AVG(price)
FROM products
WHERE date BETWEEN '2022-01-01' AND '2022-12-31'
GROUP BY category;
避免过多的group by列
尽量减少 GROUP BY 语句中的列数,避免不必要的计算。
-- 不推荐的写法,GROUP BY 列数过多
SELECT category, subcategory, AVG(price)
FROM products
GROUP BY category, subcategory;
-- 推荐的写法,尽量减少 GROUP BY 列数
SELECT category, AVG(price)
FROM products
GROUP BY category;
补充explain结果的extra字段知识:
如果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;
会全部走索引。
总结:
避免使用子查询
在 LIMIT 查询中,尽量避免使用子查询,因为子查询可能导致性能下降。
-- 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit
select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
-- 不推荐的写法,使用子查询
SELECT column1, column2
FROM your_table
WHERE id IN (SELECT id FROM another_table WHERE some_condition)
LIMIT 10;
-- 推荐的写法,使用 JOIN
SELECT t1.column1, t1.column2
FROM your_table t1
JOIN another_table t2 ON t1.id = t2.id
WHERE t2.some_condition
LIMIT 10;
使用覆盖索引
如果可以使用覆盖索引,可以减少对数据表的实际访问,提高性能。
-- 假设有合适的覆盖索引
SELECT column1, column2
FROM your_table
WHERE some_condition
ORDER BY indexed_column
LIMIT 10;
-- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;
避免全表扫描
LIMIT 查询时,尽量避免对整个表进行扫描。通过使用索引和合适的 WHERE 子句,减小查询的数据集。
-- 不推荐的写法,全表扫描
SELECT column1, column2
FROM your_table
LIMIT 10;
-- 推荐的写法,使用索引和 WHERE 子句
SELECT column1, column2
FROM your_table
WHERE some_condition
LIMIT 10;
补充知识:
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where);
而InnoDB 在执行 count(*) 时,则需要把数据一行一行地从引擎里面读出来,然后累计计数,效率慢。
如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值
count的几种用法: count(*)、count(主键)、count(字段)、count(1)
count的几种用法的性能:
count(*)跟count(主键)一样,因为主键不能为空;且两者计数时都不会忽略列值为NULL的情况
count(字段)只计算字段值不为NULL的行
count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟count(*)一样;
这是因为 COUNT 函数需要对指定的列或表达式进行计数,而 COUNT(1) 中的 “1” 不是实际的数据列,而是一个常量。因此,数据库引擎为每一行生成一个 “1”,然后对这些 “1” 进行计数,最终得到行数。
count(null)返回0
按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(*)
总结:
由于考虑效率和性能,尽量使用count(*)
-- 不推荐的写法,COUNT(column)
SELECT COUNT(column)
FROM your_table
WHERE some_condition;
-- 推荐的写法,COUNT(*)
SELECT COUNT(*)
FROM your_table
WHERE some_condition;
避免使用COUNT(DISTINCT column)
在某些情况下,COUNT(DISTINCT column) 可能会导致性能下降,尽量避免使用
-- 不推荐的写法,COUNT(DISTINCT column)
SELECT COUNT(DISTINCT column)
FROM your_table
WHERE some_condition;
-- 推荐的写法,使用其他方式处理
SELECT COUNT(*)
FROM (SELECT DISTINCT column FROM your_table WHERE some_condition) AS subquery;
The End!!创作不易,欢迎点赞/评论!!欢迎关注我的WXGZ号!