数据库
一、关系型数据库
1.1 优缺点
-
优点:
-
二维表相较于网状,层次模型容易理解
-
使用方便,通用的SQL语法
-
易于维护,丰富的完整性约束,降低了数据冗余和数据不一致的概率
-
数据存储结构规范:关系型数据库是结构化存储,数据存储时具有良好的结构
-
-
瓶颈:
-
难以高并发,在web2.0社交网站的高并发读写需求,关系型数据库很难满足
-
难以海量数据,难以满足海量数据实时读写
-
难以满足高扩展及可用性要求,很难横向扩展
-
事务一致性难以实现,分布式事务中,实现事务一致性很困难
-
多表关联查询等SQL语句比较复杂,很难编写,而且执行效率不高
-
1.2 SQL语言组成
-
DDL,数据库定义语言,用于定义数据库,表等,包含Create,Alter,Drop语句。
-
DML,数据库操作语言,用于数据库的添加,修改,删除操作,包括insert,update,delete
-
DQL,数据库查询语言,select
-
DCL,数据库控制语言,主要用于控制用户的访问权限,包括,Grant,revoke,commit,rollback语句。grant用于给用户添加权限,revoke用于收回用户权限,commit用于提交事务,rollback用于回滚事务
1.3 数据库实例
1.3.1 Oracle
-
Oracle的特有语法:
-
特有函数
-
空值函数:
NVL(arg1, arg2)
:如果参数1不为null,则返回参数1,否则返回参数2 -
空值函数:
NVL2(arg1, arg2, arg3)
:如果参数1不为null,返回参数2,否则返回参数3 -
DECODE()函数:
DECODE(expr, search1, result1, search2, result2, ..., default)
expr是表达式或者函数,如果expr = search1,则返回result1的值,如果expr = search2,则返回search2的值,否则返回 default,注,这种语句可以使用CASE代替。 -
NULLIF(),如果字段符合条件的值,就用空代替,比如:
select nullif(name, '0') from student
如果name是’0’,则用空来进行代替。 -
coalesce():用来处理Null值的,
COALESCE(expr1, expr2, ..., exprn)
,取出第一个不为null值的值,如果全是null,就返回null -
greatest():找出里边最大的值
-
least():找出里边最小的值
-
-
分析函数与开窗函数
-
概念:分析函数和开窗函数是同一个概念。就是将查询建立在不同的数据区,分别进行统计。
-
模型:
select employeeId, salary, row_number() over (partition by ... order by ...) as ... from orders
-
实例:
INSERT INTO sales (region, sales_date, amount) VALUES ('North', '2023-01-01', 100), ('South', '2023-01-01', 150), ('North', '2023-01-02', 200), ('South', '2023-01-02', 50), ('North', '2023-01-03', 300), ('South', '2023-01-03', 100); SELECT region, sales_date, amount, -- 计算每个地区每天的销售总额,然后进行加和 SUM(amount) OVER (PARTITION BY region ORDER BY sales_date) AS running_total FROM sales; +--------+------------+--------+--------------+ | region | sales_date | amount | running_total| +--------+------------+--------+--------------+ | North | 2023-01-01 | 100 | 100 | | North | 2023-01-02 | 200 | 300 | | North | 2023-01-03 | 300 | 600 | | South | 2023-01-01 | 150 | 150 | | South | 2023-01-02 | 50 | 200 | | South | 2023-01-03 | 100 | 300 | +--------+------------+--------+--------------+转成md格式
注:row_number()可以换成rank(), dense_rank(),sum(), leg(), lead()等等。
-
-
递归查询
语句:
select * from tablename start with 开始条件 connect by prior 递归条件
SELECT employee_id, employee_name, manager_id FROM employees START WITH manager_id IS NULL -- 根节点条件,可以根据实际情况修改 CONNECT BY PRIOR employee_id = manager_id;
-
insert
-
-
分页语句
-
无order by的排序分页写法
使用案例,通过嵌套子查询,进行两次筛选
select * from (select rownum as rownum, t.* from userinfo t where t.birthday between TO_DATE('19810101', 'yyyymmdd') and TO_DATE('20210301', 'yyyymmdd') and rownum < 20*2 ) table_alias where table_alias.rownum > 20*(2-1)
-
有order by 的排序分页写法(相当于加了一层嵌套,在这个嵌套里边先进行排序,之后的再进行取值)
select * from (select rownum as rownum, r.* from (select * from userinfo t where t.shengri between to_date('19810101', 'yyyymmdd') and to_date('20210301', 'yyyymmdd') order by t.shengri desc) r where rownum <= 20 * 2) table_alias where table_alias.rowno > 20*(2-1);
-
1.3.2 Mysql
1.3.2.1 Mysql架构
-
分层
-
网络连接层(应用层)
-
Mysql核心服务层
-
存储引擎层
-
系统文件层
-
-
分层详解
-
网络连接层:也叫做应用层,主要负责与客户端进行通信
作用
-
连接处理:MysqlServer对客户端发来的请求,使用线程池进行对接,且以后该客户端的请求,都由该线程进行对接。
-
用户鉴权:对客户端发来的连接请求进行鉴权处理,主要根据用户名,客户端主机地址,用户密码进行鉴权
-
安全管理:根据用户的权限,判断可以执行哪些操作。
-
-
核心服务层
包含五个模块
-
Mysql Management Server & utilities(系统管理)
-
功能:
-
数据库备份和恢复
-
数据库安全管理,用户及安全管理
-
数据库复制管理
-
数据库集群管理
-
数据库分区,分库,分表管理
-
数据库元数据管理
-
-
-
SQL Interface(SQL接口)
主要用来接收用户的SQL命令,并且对其进行处理,得到用户所需要的结果,具体处理下面的几种语句。
-
DML(增删改查)
-
DDL(create/drop/alter等定义数据库和表的操作)
-
存储过程
-
视图
-
触发器
-
-
SQL Parser(SQL 解析器)
主要用来解析select查询语句,生成最终的语法树。
sql解析器首先对查询语句进行分析,如果发现select语句上有语法错误,则返回相应的错误信息。
语法审查通过之后,会先从缓存中进行查询,如果有就直接返回。
-
Optimizer(查询优化器)
主要是对查询语句进行优化,选择合适的索引,确定数据读取方式等。
-
Cache & buffers(缓存)
缓存包括全局缓存和特定引擎的缓存,如果命中缓存,就直接从缓存读取,无须再通过解析和执行,缓存机制是由一系列的小缓存组成的,表缓存,记录缓存,key缓存,权限缓存。
-
-
存储引擎层
Mysql提供热拔插的存储引擎,供用户调用。存储引擎是mysql与具体文件打交道的子系统,是Mysql区别于别的数据库的重要特点。
常见存储引擎有:Myisam,innodb,memory等。
-
系统文件层
这是Mysql的最底层。
Mysql运行时所需要的一些系统文件,日志记录文件。例如:redolog/undolog/binlog/errorlog/querylog/showlog/data/index
注:这一层和日常的sql开发联系不大,可以通过修改或者查看某些日志,实现特定功能,或者为数据库提供优化。
-
1.3.2.2 select语句的执行步骤
-
客户端发送一条select查询语句给Mysql服务器
-
查缓存
-
描述:mysql服务器先从查询缓存中查询结果,如果缓存中存在指定的查询结果,则直接从缓存中返回命令的结果,如果缓存中不存在,则进入下一步
-
引入原因:
由于查询在CRUD中最耗时,所以为了提高效率,提供了缓存机制,是mysql进行优化的重要一环。
-
原理:
缓存系统其实是由
哈希引用表
进行维护的,这个哈希值中包含了查询本身,当前要查询的数据库,客户端协议版本等一些可能影响返回结果的信息。这个缓存系统会跟踪查询中涉及的每个表,如果表发生了变化,那么和这个表相关的所有的查询缓存都会失效。 -
缓存失效条件:
-
任何字符的不同,都会影响缓存的命中
-
查询语句中包含一些不确定的数据时,结果不会被缓存,
例如函数:now(), current_date()等系统自带的函数,用户自定义的函数,或者存储函数,用户变量,临时表,mysql系统表,或者包含任何级别权限的表,都不会被缓存。注:虽然不会命中查询结果,但还是会查询缓存,但是不存。
-
-
mysql中配置缓存
-
query_cache_type:查询缓存类型,是否打开查询缓存,off/on/demand,demand表示只有在查询语句中明确写明sql_cache的语句才会进行查询缓存。
-
query_cache_size:查询缓存使用的总内存空间
-
query_cache_min_res_unit:查询缓存中分配内存块时的最小单元,如果这个值太小,会导致频繁内存块操作,但是可以减少分的过大,导致的内存浪费。
-
query_cache_limit:Mysql能查询到的最大查询结果,如果查询结果大于这个值,则不会进行缓存。
-
-
-
解析及预处理:
-
描述:服务器中的
SQL解析器
,会对sql进行解析,预处理,生成一个解析树
-
执行方式:使用Mysql语法和关键字,对sql语句进行解析及验证,生成相应的解析树。根据Mysql规则,检查解析树是否合法,解析名字和别名看是否有歧义,检查表和列是否存在。
-
-
查询优化器生成生成执行计划
-
描述:核心服务层的
查询优化器
,会对解析树进行优化,生成对应的执行计划
-
原理:由于生成的解析树,有多重执行方式,所以查询优化器会根据
基于成本的查询优化器(CBO)
,基于统计和代价模型,选择成本最小的方法。
-
-
查询执行引擎执行sql查询
Mysql根据
执行计划
,调用一个合适的存储引擎API进行查询。注:执行计划是一个数据结构,不是别的关系型数据库中常见的字节码。 -
存缓存
- 描述:mysql会看下查询结果是否可以缓存,如果可以就在本阶段对结果进行缓存;
-
将结果返回给客户端
- 描述:将结果集以
增量,逐步返回
的方式,推到客户端,换句话说,在查询生成第一条结果的时候,mysql就开始向客户端返回结果了。
- 描述:将结果集以
1.3.2.3 mysql查重
-
distinct关键字
select distinct expression from tables where...
-
distinct查重的方式
-
distinct对完全相同的记录查重
select distinct username from user
注:distinct对null是不进行过滤的,返回的结果中包含null值,如果有多个null,也只会返回一个null
-
distinct对有唯一id且部分字段相同的记录查重
如果想要实现类似于
select distinct id, username from user
,这种sql语句,包含一个id和username,id是唯一键,这种去重无法去掉,可以根据用户名进行分组,使用聚合函数选出来一个某种条件的数据,然后得到的内容就是对某个字段去重的剩余的数据。select * from user where id in (select max(id) from user group by username)
-
distinct对没有唯一id且部分字段相同的记录查重
select address, group_concat(distinct username) as 姓名 from user group by username;
INSERT INTO orders (order_id, user_id, order_number, order_date) VALUES (1, 1, 'A123', '2023-01-01'), (2, 1, 'B456', '2023-02-01'), (3, 2, 'C789', '2023-01-15'), (4, 2, 'D101', '2023-03-01'), (5, 1, 'E202', '2023-04-01'); -- 执行 SELECT user_id, GROUP_CONCAT(order_number ORDER BY order_date SEPARATOR ', ') AS ordered_items FROM orders GROUP BY user_id; --结果是:
user_id ordered_items 1 B456, A123, E202 2 C789, D101
-
-
通过分组+having短语实现查重
除了使用distinct关键字,也可以使用group + having进行数据查重
select * from user group by username having count(username) > 1
;
1.3.2.4 Mysql去重
如果想要去除表中的重复数据,可以使用delete结合select
delete from user
where username in (
select a.username from (
select username from user group by username having count(username) > 1
) as a
)
and id not in (
select b.bid from (
select min(id) as bid from user group by username having count(username) > 1
) as b
);
找出usename大于1的,同时又不能是分组中最小id的,将其删去,如果不限制非最小id,就会将username大于1的全部删除。
二、SQL关键字
2.1 SQL关键字及执行顺序
- from
- on 注:有说join和on同时进行的
- join
- where
- group by
- 聚合函数 avg/sum/max/min/count…
- with rollup或者cube
- having
- select
- distinct
- union
- order by desc|asc
- limit
2.2 关键字具体用法
-
from:
-
可以在from中同时指定多个表名,例如from t1, t2, t3,表示将前两个表,执行笛卡尔积(交叉连接),从而可以得到来自不同数据源的数据集,最后将这个结果集作为VT1。
-
from 子句中的多个表的执行顺序,是从后往前,从右往左,所以,from子句中写在最后的表会被最先处理。当from子句中包含多个表,为了性能达到最优,一般将数据量最少的表,作为驱动表,遵循小表驱动大表。
-
-
on:
- on后边一般都是逻辑表达式,sql根据on后的逻辑表达式,对虚拟表vt1中的各个行进行筛选,筛选出条件为真的行,插入到生成的vt2中
-
join
-
用于连接多个表,添加外部行,配合outer来使用,分为三种情况,
left outer join; right outer join; full outer join;
A left join B
会将A放在左边,进行保留,匹配不到的数据被记成NULL
-
-
where
-
将条件为假的数据全部过滤掉。
-
由于此时还没有进行分组,所以不能在where过滤器中使用聚合函数对分组进行过滤
-
由于还没有进行列的选取操作,所以不能使用select中列的别名
-
-
group by
-
按照group by子句中指定的列,将虚拟表中的数据进行去重,将唯一的结果值,合并成为一个分组
-
如果使用了group by,那么后续所有的步骤都只能使用处理过的虚拟表了
-
在这一步,就可以使用聚合函数,以及使用select中列的别名了
-
group by 的执行顺序是从左往右分组
-
为了提高效率,可以将不需要的记录,在group by 之前的where语句中过滤掉,避免在group by 之后通过having进行过滤
-
-
聚合函数
-
用于对从列中获取的值进行计算,返回一个单一的值。
-
常用的聚合函数:
-
AVG:返回平均值
-
COUNT:返回行数
-
FIRST:返回第一个记录的值
-
LAST:返回最后一个记录的值
-
MAX:返回最大值
-
MIN:返回最小值
-
SUM:返回总和
-
-
-
WITH ROLLUP 或 CUBE
-
cube生成的数据,显示了所选列中的值的所有组合的聚合
SELECT Region, Product, SUM(Sales) AS TotalSales FROM Orders GROUP BY CUBE(Region, Product);
-
ROLLUP 生成的结果集,显示了所选并列中的值的某一层次的聚合
SELECT Region, Product, SUM(Sales) AS TotalSales FROM Orders GROUP BY Region, Product WITH ROLLUP;
-
-
Having
- having和where的作用是相同的,having是对聚合值进行过滤,where关键字无法与聚合函数一起使用,having短语是在检索出所有记录之后才会对结果集进行过滤,这个处理需要进行排序,总计等操作,所以效率很低,如果能在group by之前的where 先对数据进行过滤,就能减少having的开销。
-
select
-
select 中尽量避免使用 *,因为sql解析的过程中,会将
*
依次转换为所有的列名,是通过查询数据字典完成的。 -
尽量将所有的字段全部变为大写,因为sql解析的时候,会先进行转换成大写
-
-
distinct
- 去除重复行
-
UNION
UNION用于对select的结果进行合并,默认去掉重复的记录
-
order by
将虚拟表中的数据,按照当前指定的列进行排序,生成游标。这里和之前不一样的地方在于,这里生成的不是虚拟表,而是游标,所以在order by 子句中不能使用表达式,而且order by 子句的执行顺序是从左往右,很消耗资源。
-
Limit
limit关键字用于从游标中,从指定位置选出指定行的数据,最终生成虚拟表。当数据量非常大的时候,limit非常低效,因为都是从头开始扫描,先定位到某一行,再开始读取。所以使用应用层缓存十分有必要。
2.3 关键字on, where, having的对比
-
执行次序:on最先执行,where次之,having最后
-
执行速度:
-
on先将不符合条件的记录进行过滤掉,再进行统计,所以可以减少中间运算需要处理的数据。最快;
-
若为单表查询,过滤的字段不需要进行字段计算,where可以使用rushmore技术,所以where优于having;
-
2.4 in与exist
in
-
简介
用来确定条件中给定的值,是否与子查询或者列表中的值相互匹配。可以分为单表和多表,
-
单表:
select * from user where id in (1,2,3);
等效于:
select * from user where id = 1 or id = 2 or id = 3;
-
多表:
select * from A where id in (select id from B);
进行子查询的时候,首先查询B表,然后将B与A进行笛卡尔积操作,最后按照条件筛选出符合要求的结果。简单说,就是先将B中查到的所有的id进行缓存,再检查A表中的id与B表中缓存的id是否相等,相等的放到结果集,直到遍历完成。类似于Java的双重循环
in操作适用于A表数据多,B表数据少的场景。原因:
-
缓存效果。当在小表上执行查询的时候,数据库引擎会更容易将小表缓存在内存中,小表的数据量比较小,在执行主查询的时候,可以更快地在内存中查到匹配的数据,不必频繁访问磁盘。
-
索引效果。如果子查询上有索引,那么在小表上使用索引的效率更高,索引可以大大减少,在子查询中查找匹配值的时间。
-
减少对大表的处理。主查询的in子句,会将小表中的值,用于在大表中筛选匹配的行,由于小表的数据量比较小,这个筛选过程相对来说更加高效,因为数据库引擎不需要在大表上进行全表扫描。
-
-
-
not in 简介
select * from A a where a.id not in (select b.id from B b)
not in 的操作,本质上等于 != and != ***,由于!=会使索引失效,所以not in 的操作会进行全表扫描,而不使用索引,效率比较低。
-
exists简介
-
简介:exists通常和子查询一起操作,用于检查这个子查询是否至少返回了一行数据。exists不会返回数据,只是返回true或者false,当子查询返回为真时,外层查询语句会进行查询,当子查询返回为假时,外层查询语句将不进行查询或者查询不出任何记录。
内表循环与外表进行对比
,内表小,外表大,是常见的方式,也是对优化友好的,如果内表大,建索引就比较大。最好使用join来进行处理。 -
基本用法:
select * from user where exists (select * from user where id = 1)
select * from user
是外表,select * from user where id = 1
是内表 -
执行逻辑:
上面的sql,基本执行逻辑是,先对外表进行循环遍历,找到结果之后,查看外表中的记录和内表中的记录是否一致,如果匹配,就将结果放到入到结果集中。
注:与in的区别是,exists先对外表进行查询,而且exists()不会对结果进行缓存,因为exists查询只会返回一个布尔值,只关心exists的查询是否有记录,与具体的结果无关。
exists的本质,是对外表进行Loop循环,每次loop时都会对内表进行查询,当外表数据多,内表数据少,适合用exists,同样是小表驱动大表。
注:exists适合用于内表数据量大,外表数据量小的查询,主要是基于查询的执行逻辑和性能考虑:
-
停止条件早。当外表数据量小的时候,对外表的每一行都能很快处理完成,在这种情况下,exists子句可能会更快地找到满足条件的记录并且停止查询。
-
内表数据量大也无所谓,只要找到一个满足的条件,那么就停止查询了。
-
当外表数据量比较小的时候,可以避免对大量不相关的数据进行处理,如果外表数据量很大,即使内表有索引,也可能需要在大量记录上执行exists子查询,降效率。
-
-
-
not exists简介
select * from A where not exists (select * from B b where b.id = a.id)
not exists内部是可以使用使用索引的,所以not exists的操作效率高于 not in
-
in与exists的区别
-
使用场景不同
in适合于内表数据比较少,外表数据比较多的时候,内表做驱动表,外表做被驱动表。
exists适用于外表数据量比较少,内表数据量比较多的时候。
-
子查询关联不同
in一般是非关联子查询,非关联子查询必须先完成内层的查询,再进行外层的查询,
exists一般是关联子查询,关联子查询必须先执行外层的查询,然后对所有满足过滤条件的记录,执行内层查询,外层查询会将数据传递给内层,所以外层查询会和内层查询相互依赖。
-
效率不同
内表小,外表大,内表驱动外表的时候,in操作只会执行一次,尤其是外表查询存在索引时,in的速度较快
内表大,外表小,外表驱动内表,exists会对外表进行loop循环,每次循环时,会对内表进行查询,此时即使外表没有索引,exists的效率也比较高。
-
2.5 group by 和 Having
group by 比 having执行早
-
group by 简介
- 用于分组操作,sql查询会按照group by子句中指定的列,将虚拟表中符合条件的数据进行去重,然后将唯一的结果值,组合成一个分组,最后生成另一个虚拟表。
-
having简介
- where子句是无法和聚合函数一起使用的
-
group by 和having的区别
-
作用不同
having用于对where和group by查询出来的分组进行二次过滤,查询满足条件的分组结果,是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作。
group by 对select查出来的结果集,按照某个字段或者表达式进行分组,获得一组结果,然后从每组中取出一个指定的字段或者表达式的值。
-
执行时机
group by是在where 过滤部分数据之后,再对剩下满足条件的数据进行分组,group by 比having的执行条件要早。
having是在group by 分组后,再找到特定的分组,通常会和聚合函数一起使用。
-
作用范围:
group by 要紧跟在where的最后一个条件之后,不能被放在where的限制条件之间。
having必须跟在group by 之后,使用having之前必须使用了group by,但使用group by不一定接着使用having
-
执行效率,group by 的执行顺序是从左往右的,效率一般,可以再group by之前先利用where 进行过滤,将不需要的记录提前过滤掉,再利用group by进行二次过滤。having短语是在检索出所有记录之后,才对结果集进行过滤,这个处理过程需要进行排序等操作,效率低。
-
2.6 date/time/datetime/timestamp的区别
sql语法中提供了几个与时间相关的关键字,不同数据库的关键字可能不太一样,以Mysql举例,常见的有上边几种。
-
time & date
YYYY-MM-DD HH:MM:SS 分别对应了date和time,如果数据库设置的格式是date,但是同时存储了time,会丢掉精度,同时抛出warning。
-
dateTime
-
存储范围:1000-01-01 00:00:00.000000至9999-12-31 23:59:59.999999,占用的存储空间8字节
-
类型可以精确到毫秒,也可以格式化成为YYYY-MM-DD HH:mm:ss
-
允许使用null作为默认值,也可以自定义,但是系统不会自动更新数据值
-
在不允许存空值时,需要手动制定datetime字段的数据值来插入数据,也可以使用now变量来自动插入系统的当前时间。
-
dateTime类型的值不会自动发生变化,适合记录数据的原始创建时间。
-
mysql5.6之后,current_timestamp()函数支持使用datetime类型
-
-
timestamp简介
-
存储的时间范围1970-01-01 00:00:01.000000到2038-01-19 03:14:07.000000,存储空间四个字节
-
精度到毫秒,可以格式化成YYYY-MM-DD HH:mm:ss
-
默认值是current_timestamp(),即当前系统的时间。再mysql5.6之前,current_timestamp()使用的是timestamp类型。
-
该类型允许有空值,但不可以自定义值,所以为空值时没有任何意义。
-
该类型适合记录最后修改时间,只要修改了表中的其他字段,timestamp字段就会被数据库自动更新成为当前的系统时间
-
在有索引的时候,timestamp更轻量级,比datetime执行速度更快。
-
timestamp会随着市区进行变化,适合用于跨时区的场景
-
2.7 select的基本语法
-
sql语句
select name as '商品名称', AVG(good_number) as '平均数量' from weshop_goods where is_on_sale = 1 group by name having 平均数量 > 40 order by goods_number desc limit 100;
-
执行顺序
- 先执行from,从weshop_goods中拿到数据,
- 执行where,筛选出商品表中的所有is_on_sale为1的商品,
- 执行group by 子句,按name进行分组
- 使用聚合函数,进行平均
- 执行having,筛选出平均数量大于40的商品
- 执行select,设置别名
- 执行order by
- 返回前100数据
三、mysql分页
3.1 limit语法进行分页
-
Limit的语法
可以使用Limit来限制查询结果返回的数量,进而实现分页的效果
select * from table limit [offset,] rows
注:offset表示返回记录行的偏移量,默认从0开始,当offset=0时可以进行省略,Limit rows等同于Limit 0,rows
-
用法
select * from table limit 30, 10; --表示从第30行开始取10行
只需要动态地修改offset和rows两个值,即可实现分页功能。一般从前端向后台传递两个参数,currentPage和pageSize,分别对应offset和rows
前端传递过来的currentPage一般是1开头,所以在sql中进行调整如下:
select * from table limit (currentPage - 1) * pageSize, pageSize
-
limit分页原理
limit 20,10 一般是查询出前30条数据,然后舍弃掉前20条。
当表中记录数比较多的时候,分页查询的效率会变慢。
-
优化
select * from table order by id desc limit 100000, 2000;
该条语句可以优化为:
select * from table where id <= 102000 order by id asc limit 2000;
原因:之前是将所有的数据全部倒序排列,取出102000条数据,舍弃100000条,现在是,只拿102000条数据,倒序,只取前2000条。其实这样做有问题,因为id不一定是正好在102000之间的。如果有102001,那优化后的数据,id<102000就扫描不到数据。 这个思想是对的,先不使用Limit进行切分,直接拿出来那个范围内的数据。从头开始,而不是从某个位置进行切分。这种方式,
如果使用这种优化方式,拿下一页可以使用:
select * from table where id > 102000 order by id asc limit 2000;
不管翻多少页,每次只扫描2000行,效率会提高,但是只能提供上一页,下一页的操作。
进一步优化:指明查询列
select 列1, 列2, 列3... from table where id > 102000 order by id asc limit 2000
进一步优化,使用主键或者唯一索引进行分页,假设主键或者唯一索引为Id,客户端传来的currentPage为10001,pagesize=1000
select 列名1, 列2, 列3... from mytable where id > (currentpage-1) * pagesize limit pageSize;
进一步优化,
select 列名1, 列2, 列3... from table where id > (currentpage -1) * pagesize order by id limit pagesize
这种情况会根据id进行排列,适用于数据量很多的情况,order by 后面的列是主键或者唯一索引,这样使order by 操作能利用索引,查询的速度就会快很多
进一步优化,
表中的id是连续递增的,可以根据查询的页数和查询记录,算出要查询的范围,可以使用between和and进行查询
select 列名1, 列2, 列3... from table where id between 100000 and 1000200
limit 200;这种方式能极大加快查询方式,但是只能用于能推算出来数据范围的情况
3.2 PageHelper进行分页
支持12种数据库,支持常见的rowBounds,RowBounds 是 MyBatis 中用于控制查询结果行数的对象。遵循queryInterceptor规范。
3.2.1 使用方式
-
pom导入依赖
-
配置拦截器插件
新版的拦截器插件使用的是com.github.pagehelper.PageInterceptor,旧版使用的是com.github.pagehelper.PageHelper。现在是一个特殊的dialect实现类,是分页插件的默认实现类,提供了和以前相同的用法。
<plugins> <plugins interceptor="com.github.pagehelper.PageInterceptor"> <property name = "param1" value = "value1"> </plugin> </plugins>
-
使用方式
-
直接使用RowBounds方式
List<Country> list = sqlSession.selectList("x.y.slelectIf", null, new RowBounds(0, 10));
-
使用Mapper接口方式调用,这种方式一定要在查询开始的时候,将pagehelper放在前面
-
startPage()
PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectIf(1);
-
offsetPage()
PageHelper.offsetPager(1, 10); List<Country> list = countryMapper.selectIf(1);
-
-
3.2.2 PageHelper源码及原理分析
-
Mybatis对插件的处理过程
Mybatis是通过拦截器进行处理的,内部真正执行sql语句的有四个插件对象:
- executor:负责拦截update,query,getTransaction,commit,rollback,close等方法
- statementHandler:处理prepare,parameterize,batch,update,query等方法
- parameterHandler:处理setParameters,getParameterObject等方法
- ResultSetHandler:处理handlerResultSets,handleOutputParameters
-
自定义插件
要实现自定义插件,必须要实现Interceptor接口,实现接口中的三个方法,否则无法被拦截。
有了拦截器,对上述四大对象进行拦截。
//args 属性是一个 Class[] 数组,用于指定被拦截方法的参数类型。在你的例子中,args = {MappedStatement.class, RowBounds.class, ResultHandler.class} 表示被拦截的 query 方法的参数类型分别是 MappedStatement、RowBounds 和 ResultHandler。 @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, RowBounds.class, ResultHandler.class})}) public class MyPlugins implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("成功拦截了Executor的query方法,在这里可以进行业务操作。"); Object result = invocation.proceed(); System.out.println("After executing query..."); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { System.out.println("自定义属性:name->" + properties.getProperty("name")); } }
注:@Interceptor注解用来声明当前类是一个拦截器,后面的@Signature用于标识需要拦截的方法签名,通过以下三个参数来确定
-
type:被拦截的类名
-
method:被拦截的方法名
-
args:标注方法的参数类型
然后在Mybatis-config中对上述插件进行配置即可。
<plugins> <plugin interceptor = "com.yuanheng.mybatis.plugin.myPlugin"> <property name = "name" value = "一一哥"> </plugin> </plugins>
-
-
pagehelper实现的拦截器
pageHelper插件用的拦截器是:PageInterceptor,
@Override public Object intercept(Invocation invocation) throws Throwable { // 在Mybatis的拦截器中,invocation表示对被拦截方法的一次调用, // invocation内部包含三个属性 // Object target, 被拦截的目标对象,即实际执行方法的对象 // Method method, 被拦截的目标方法,即,要执行的方法 // Object[] args,方法的参数数组 Object[] args = invocation.getArgs(); }
invocation.getArgs()
可以用来获取参数,里面的几个参数分别是:-
MappedStatement ms = (MappedStatement) args[0];
- MappedStatement 包含了执行 SQL 语句所需的所有信息,包括 SQL 语句本身、参数映射信息、结果映射信息等。
-
ID标识符:每个映射语句都有唯一一个id,用于在mybatis中唯一标识一个映射语句
-
sql语句:包含映射语句的实际sql语句,可以使增删改查等操作
-
参数映射信息:包含了参数的类型,参数的属性映射关系等信息,用于将传递给映射语句的参数和sql语句的占位符进行映射
-
结果映射信息:包括了查询结果的映射信息,用于将数据库查询结果集映射为java对象
-
缓存配置:包括了关于缓存的配置信息,如缓存的类型,缓存的大小
-
语句类型:标识映射语句的类型,包括select/insert/update/delete
-
Object parameter = args[1];
-
RowBounds rowBounds = (RowBounds) args[2];
-
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget(); //executor是获取执行实际方法的对象
-
-
大致流程
-
pagehelper首先将前端传来的参数保存到page对象中,接着将Page对象放到ThreadLocal对象中
-
pagehelper在使用拦截器的时候,会查询threadLocal中有没有分页数据
-
执行查询语句,获取当前线程中的分页参数
-
通过拦截器在sql语句中添加分页参数,拼接出完整的分页sql语句,实现分页查询
-
查询结束之后,在finally中清除threadLocal中的分页查询参数
-
四、主键
4.1 引入
很多表设计之初,都会给表分配一个id主键字段,主键的名字不一定叫id,可能是别的名字。主键一般是作为数据库表格里的第一个字段,主键的生成策略和自增长比较重要。分布式主键的生成策略也比较重要。
主键一般是一列或者多列的组合,该值能唯一地标识表中的每一行,保证了实体的完整性。而且可以和别的表的外键相关联,也可以在主键上创建索引以提高查询效率。
主键不是必须的。但是最好设置一个主键,不管是单主键还是复合主键,主键的存在,代表表的结构完整性。
4.2 Mysql主键
-
主键的作用
-
保证实体的完整性
-
加快数据库的操作速度
-
在添加数据时,DMS可以防止新纪录与已有数据主键重复
-
数据库管理系统会自动按主键值的顺序来显示表中的记录
-
-
主键的特点
-
一个表只能有一个主键
-
唯一性原则,主键值,必须可以唯一地标识出表中的每一行,且不能为Null。表中不会存在逐渐相同的两行数据。
-
一个字段只能在联合主键中出现一次。
-
联合主键要满足最小化原则,联合主键中不能包含不必要的字段。即,如果将联合主键的某一字段删除后,剩下的字段如果构成主键,仍满足唯一性原则,那么这个联合主键就是不正确的。
-
主键的内容不能被更新。
-
主键不应该包含动态变化的数据,例如,时间戳,创建时间列,修改时间列。
-
-
主键的生成策略
基于Mysql,说明几种主键的生成策略
-
auto_increment
-
描述
常见,对于一般的表,可以使用此种策略。一般适用于int,short,long等整型类型的id。
常用于单线程,向同一张表中插入数据时使用。在并发环境下避免使用本策略。
create table user ( id int(4) primary key not null auto_increment, username varchar(16) not null, password varchar(16) not null );
-
主键用完的处理机制
如果采用int类型作为自增主键,取值范围是0~43亿。如果不够,就是用bigInt类型。
如果已经上线了,也可以直接在线上将id的类型改成bigInt。如果使用的是mysql5.6版本以上的,可以直接使用在线修改功能
alter table tableName change oldFieldName newFieldName fieldType
也可以采用第三方工具,在线修改表结构,例如pt-osc(pt-online-schema-change),ghost
不过一般在单表数据超过5000w之后,就考虑分库分表了,否则数据查询效率很低
-
-
联合主键
create table student_course( name varchar(25), stuId int(11), cid int(11), primary key(stuId, cid) )
-
uuid
mysql支持使用uuid作为唯一主键,特点如下
-
优点
- 全局唯一,安全,可移植
- 能够保证独立性,程序可以在不同的数据库之间迁移,效果不受影响
- uuid不仅独立于表,也独立于库,在切分数据的时候,这种特性尤其重要
-
缺点
- uuid无序性,造成性能不如自增id,字符串存储空间大,查询效率低
- uuid无序性,对于InnoDb引擎,会增加I/O压力
- uuid过长,影响内存的索引命中率,索引查询的时候性能很差
drop table if t_user; create table t_user ( id varchar(50) not null, name varchar(50) not null, primary key(id) ); -- 插入数据需要手动指定主键的值,数据库不能自己生成 insert into t_user(id, name) values (select UUID(), 'zhangsan');
-
-
自定义序列表
自定义序列表,是指,在数据库中创建一张
用于生成序列的表
来存储序列信息,该序列的生成策略,是通过代码来实现的。create table 't_user' ( 'name' varchar(50) not null, 'id' bigint(20) unsigned not null default '0', primary key ('name') ) engine=InnoDB;
id不是主键,使用该表的时候,先向里边插入一些初始数据
insert into t_user(name) values ('zhangsan', 'lisi', 'wangwu');
之后,通过执行下面的sql,获得新的用户id,
update t_user set id = LAST_INSERT_ID(id + 1) where name = 'zhangsan'; select LAST_INSERT_ID();
id的序列生成会脱离数据库层面,由应用层负责,可以使用spring框架实现,但会增加开发的复杂度。
-
获取自动生成的主键
-
jdbc2.0提供的insertRow()方式
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_UPDATABLE); // 创建Statement stmt.executeUpdate("DROP TABLE IF EXISTS autoIncTutorial"); stmt.executeUpdate("CREATE TABLE autoIncTutorial (priKey INT NOT NULL AUTO_INCREMENT,dataField VARCHAR(64), PRIMARY KEY (priKey))"); rs = stmt.executeQuery("SELECT priKey, dataField FROM autoIncTutorial"); rs.moveToInsertRow(); rs.updateString("dataField", "AUTO INCREMENT here?"); rs.insertRow(); rs.last(); int autoIncKeyFormRs = rs.getInt("priKey") } finally { rs.close(); rs = null; }
-
jdbc3.0提供的getGenerateKeys()来实现
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_UPDATABLE); // ... stmt.executeUpdate( "INSERT INTO autoIncTutorial (dataField) " + "values ('Can I Get the Auto Increment Field?')", Statement.RETURN_GENERATED_KEYS); // 向驱动指明需要自动获取generatedKeys! int autoIncKeyFromApi = -1; rs = stmt.getGeneratedKeys(); // 获取自增主键! if (rs.next()) { autoIncKeyFromApi = rs.getInt(1); } else { // throw an exception from here } rs.close(); rs = null; System.out.println("Key returned from getGeneratedKeys():" + autoIncKeyFromApi); } finally { ... }
-
通过sql的select LAST_INSERT_ID()函数来实现
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(); // ... // 省略建表 // ... stmt.executeUpdate( "INSERT INTO autoIncTutorial (dataField) " + "values ('Can I Get the Auto Increment Field?')"); int autoIncKeyFromFunc = -1; rs = stmt.executeQuery("SELECT LAST_INSERT_ID()"); // 通过额外查询获取generatedKey if (rs.next()) { autoIncKeyFromFunc = rs.getInt(1); } else { // throw an exception from here } rs.close(); System.out.println("Key returned from " + "'SELECT LAST_INSERT_ID()': " + autoIncKeyFromFunc); } finally {...}
-
通过SQL @@INDENTITY 变量来实现
INSERT INTO t_user VALUES(NULL, 'yyg'), (NULL, 'syc'); SELECT @@identity;
-
通过MYBATIS框架来实现
<insert id="id" parameterType="User" useGeneratedKeys="true" keyProperty="id"> </insert>
五、存储引擎
存储引擎的作用
-
对数据进行操作
数据库实现增删改查的各种操作,本质上就是利用存储引擎来实现的。
-
提供特色功能:使用不同的存储引擎,mysql的功能就不同,mysql的能力取决于存储引擎。
-
存储机制:规定了数据库如何进行存储
-
索引:如何为表构建索引,如何查询数据等
-
锁定水平:数据库的锁定水平是指,数据库管理系统对数据进行并发控制的一种机制
-
读未提交
-
读已提交
-
可重复度
-
串行化
-
-
存储引擎的分类
不同版本的mysql对存储引擎种类支持不同,可以使用show engines
查看数据库中包含的存储引擎。
innoDB
-
特性
-
支持事务(innodb的核心特性)
-
支持行级锁
描述:粒度小,行级锁只锁定单独的行,别的事务可以同时访问同一表的不同行,从而减少了锁定的冲突。但是会造成资源消耗,引入更多的锁定管理开销(需要引入更多的锁),当多个事务同时请求多个行的锁,可能会造成死锁。
-
支持自动增长列auto_increment
描述:innodb表的自动增长列不能为空,可以进行手动插入
但是若手动插入的值,是空或者0,实际插入的值仍然是自动增长后的值。
-
自动增长的列必须是索引,如果是组合索引,也必须是组合索引的第一列。
-
对主键的处理:如果没有显式地在表中指定主键,InnoDB会为每一行自动生成一个6B的ROWID作为默认的主键。
-
InnoDB索引的底层数据结构基于B+Tree
-
支持外键:只有innodb支持外键。在创建外键时。父表必须有对应的索引,子表在创建外键时,也会自动创建对应的索引。
CREATE TABLE 表1 ( 列1 数据类型 PRIMARY KEY, 列2 数据类型, FOREIGN KEY (列2) REFERENCES 表2(被引用列) ); --注:列2是表1的外键,它引用了表2中的被引用列,表1中的列2的值,必须在表2的被引用列中存在。
-
自动灾难恢复:与其他存储引擎不同,innodb表能够自动从灾难中恢复。
-
innoDB存储引擎中的数据库文件类型包括.frm/ibdata1/ibd格式
-
实现了缓冲管理:不仅能缓冲索引,也能缓冲数据,且能自动创建散列索引以加快数据的获取
散列索引:使用哈希函数将索引列映射为一个固定大小的哈希值,查找速度非常快,常用来做等值查询时的索引,但是不适用于范围查询和排序,因为hash值的顺序,和索引列的顺序并不一致。而且散列索引不支持部分匹配,比如说组合索引,匹配到一部分就不能命中索引,如果使用b-tree,根据最左前缀原则,能匹配到几个算几个。
-
读写处理的效率较差,会更多占用磁盘空间以保留数据和索引。
-
-
基本使用
-
代码
create table t_test ( name varchar(20) not null default 'zhangsan', sex varchar(2) default '1', primary key (name) ) engine = InnoDB default chatset=utf-8 row_format=dynamic;
-
-
使用场景
Innodb的优点是提供了良好的事务处理,崩溃修复和并发控制能力,缺点是读写效率较差,占用的数据空间相对较大。所以,若需要支持事务,外键,自增的主键等功能,想要更高的并发读取效率,innodb可以考虑。
myisam
-
特性
-
不支持事务
-
不支持外键
-
使用表级锁,并发性差
-
同样使用B+Tree,但在具体实现上,与innoDB不同
-
主机宕机后,myisam表容易损坏,灾难恢复性不佳
-
只缓存索引,数据的缓存是利用操作系统缓冲区来实现的
-
每个Myisam在磁盘上存储成3个文件,其中文件名和表名都相同,但是扩展名分别为:frm(存储表定义),myd(mydata,存储数据),myi(myindex,存储索引)
-
myisam的表支持3种不同的存储方式,
-
静态表(固定长度)ROW_FORMAT=FIXED
-
特点:在静态存储方式下,MyISAM 表的行格式是固定长度的,不支持变长的 CHAR、VARCHAR、BLOB 和 TEXT 等数据类型。每个列的长度都是固定的,不会根据实际数据长度而变化。
-
描述:这种存储方式的优势是更加紧凑,因为每一行的存储空间是固定的,适用于表的结构相对简单且列的长度大致相同的情况。
-
-
动态表 Dynamic
-
特点:支持变长的 CHAR、VARCHAR、BLOB 和 TEXT 等数据类型。
-
描述:动态存储方式可能导致随机访问时的性能损失。
-
-
压缩表 Compressed
-
特点:MyISAM 表的行格式是变长的,并且通过压缩算法对数据进行压缩。这种存储方式可以显著减小表的物理存储空间,降低磁盘 I/O 操作。
-
描述:压缩存储方式在节省存储空间方面有优势,但在进行写入和查询时可能会引入一些性能开销,因为需要进行压缩和解压缩操作。
-
create table table1 ( column1 int, column2 varchar(25), ) engine=MyISAM ROW_FORMAT=FIXED;
-
-
-
基本使用
create table t_test( id int(10) unsigned not null auto_increment, name varchar(10) character set utf8, age int(10), primary key(id) ) engine=MYISAM;
-
使用场景
-
优点:占用空间小,处理速度快,
-
支持高速存储,检索,支持全文搜索:在筛选大量数据的时候,非常迅速,适合
选择密集型
表 -
并发插入特性,允许同时选择和插入数据:适合管理邮件或者web服务器等日志插入密集型的数据表
-
-
缺点:不支持事务的完整性和并发性
如果对事务的完整性没有要求,或者以select/insert为主,基本上都可以使用这个引擎创建表。
-
memory
-
特性
-
数据在内存中
使用内存中的数据创建表,而且所有的数据都存储在内存中。
-
访问非常快
-
默认使用
hash索引
,速度比b+tree
更快 -
使用表级锁
-
只支持固定大小的行
注:varchar类型的字段,会存储为固定长度的char类型
-
不支持text, blob字段
-
服务器重启后,表中的数据会消失,但表结构还在
-
每个memory表实际对应一个磁盘文件,格式是.frm
-
-
基本使用
create table t_test engine=memory
-
使用场景
-
存储引擎使用的是hash索引,对等值查找十分高效:
适用于查找邮编和地区对应的关系表。
-
易失性
可以用于存储,数据分析时建立的临时表
-
Archive
适合存储大量独立的,作为历史记录的数据。区别于innodb和myisam,archive提供了压缩功能,拥有十分高效的插入速度,但是不支持索引,所以查询性能较差。
Merge
merge存储引擎是一组myisam表的集合,这些myisam表结构必须完全相同,merge表中没有数据,对merge类型的表可以查询,更新,删除的操作,这些操作实际上是对内部的myisam表进行操作。
常用引擎的区别
innoDB 支持事务,支持外键,支持树索引,不支持哈希索引,不支持全文索引,存储限制为64tb,支持数据缓存,支持行级锁,添加修改删除更快,自动灾难恢复较好。
myisam 不支持事务,不支持外键,支持树索引,不支持哈希索引,支持全文索引,存储限制为256tb,不支持数据缓存,支持表级锁,查询速度更快,对自动灾难的恢复一般。
选择引擎主要考虑一下的内容:是否支持事务,是否要使用热备,是否要能够自动灾难恢复,是否需要支持外键。
-
为什么innodb添加删除更快,而myisam查询速度快:
-
锁定粒度不一样:innoDB支持行级锁,而myISAM支持表级锁,行级锁允许多个事务同时在表中不同部分进行插入和删除,表级锁,整个表被锁定,阻塞其他写操作,造成高并发写入时的性能瓶颈。所以innoDB适合读写,而myISAM适合查询。
-
事务支持:因为innoDB支持事务,所以在高并发,事务要求严格的场景中,innoDB的添加删除更为优越
-
聚簇索引:myISAM引擎使用非聚簇索引,使得在特定情况下(例如范围查询),减少了磁盘IO,更快执行某些查询。
注:只是某些情况更快,也有可能更慢。
innoDB采用聚簇索引,数据和索引放在了一起。
-
索引查找方式不同:innoDB如果建了一个索引,会根据这个索引先找主键key,再通过主键索引去找真实物理地址,而myISAM能通过索引直接找到真实地址
-
六、mysql索引
-
作用
-
加快查询速度:
索引相当于数据库的目录,可以减少服务器需要扫描的数据量,快速查找出数据库里存储的数据。
-
帮助服务器避免排序和创建临时表:
-
如果查询中的列上有合适的索引,数据库系统可以使用索引提供的有序性,避免对数据进行排序。
-
索引的使用也有助于避免创建临时表,因为索引提供了一种更有效的方式来满足查询的需求。
-
-
将随机IO变成顺序IO
索引的有序性使得在磁盘上的访问更加连续,减少了寻道的开销,提高了查询性能。
注:表中的数据量越大,索引的作用越明显。
-
-
缺点
使用索引会增加一些额外的开销成本,比如:
-
空间成本:创建索引会产生索引文件,占用一定的磁盘空间。
-
时间成本:查询索引本身需要一些时间,索引越大,查询索引本身花费的时间就越多。
-
维护成本:索引文件是一个二叉树类型的文件,进行DML操作的时候,会对索引文件进行修改,当数据大量更新时,需要对索引进行维护,DML操作后的索引性能会下降。
-
-
使用场景:
-
适合
-
频繁查询的字段
给频繁作为查询条件的字段,添加索引
-
唯一性强的字段
如果索引的值是唯一的,通过该索引可以快速地确定某条记录。
唯一性太差(区分度低)的字段不适合单独创建索引,即使该字段频繁被作为查询条件。
-
长度较小的字段
尽量为长度较短的列创建索引,索引的字段要尽量小
对一个char(100)的列进行全文检索需要的时间,比char(10)要多
-
频繁排序的字段
为需要进行排序的列,或者经常order by的列,创建索引,比如经常需要对某列使用
<|<=|=|>|>=|between|in
等。索引是已经排过序的,可以利用索引的排序,加快排序的查询时间。 -
作为关联的列
与别的表进行关联的字段,即外键,可以建立索引
-
高并发场景
高并发场景下,如果要使用索引,面临单列或者组合索引,尽量使用组合索引。原因如下:
-
组合索引可以形成覆盖索引
组合索引可以更好地满足覆盖索引的需求。覆盖索引是指索引包含了查询中涉及的所有列,从而避免了对数据表的实际行的查找。当查询中的所有列都在组合索引中时,数据库可以直接从索引中获取所需的数据,而无需再次访问数据表,这可以显著提高查询性能。
-
可以减少索引数量
组合索引可以用更少的索引来满足多个列的查询需求,相较于维护多个单列索引,这可以减少索引的维护成本。在高并发环境中,减少索引数量有助于减少锁的竞争,提高系统的并发性。
-
减少查询时间
在高并发场景下,查询的响应时间至关重要。组合索引可以减少查询时间,因为它允许数据库系统直接在索引中定位满足查询条件的数据,而不必浪费时间在多个单列索引之间进行切换。
-
支持多列查询
组合索引能够支持涉及多个列的查询。在高并发环境中,可能需要执行复杂的查询,涉及多个条件的组合。组合索引可以更好地支持这样的查询,提高整体系统性能。
-
-
统计与分组
如果查询中经常作为统计与分组的字段,可以建立索引
-
-
不适合
-
频繁更新的字段
频繁更新的字段不适合创建索引,因为维护索引也是一笔开支
-
数据量小的表
没必要创建索引。如果数据量比较小,本身进行查询的时间就不多,不用创建索引。创建索引,遍历索引的时间可能比直接查询用的时间还多。
-
用不到的字段
where中用不到字段,不用创建索引。
-
-
-
注意事项
-
创建索引的时候尽量满足最左前缀原则,where子句中使用最频繁的一列,一定要在组合索引中放到最左边。
-
尽量扩展索引,而不是重新建索引
-
索引的列不要参与运算,比如说有个主键索引ID,执行
where id+1 > 10
,在每次检查索引的时候,都会将索引计算一次,然后再进行比较,这样的成本会很高。例如:使用了
SELECT * FROM your_table WHERE id + 1 > 10;
,这个查询对 id 列进行了运算(加法操作)。在进行查询优化时,数据库系统可能无法直接利用 id 索引来加速查询,因为在索引中存储的是原始的 id 值,而不是 id + 1 的值。因此,在执行这个查询时,数据库可能需要对每一行数据进行运算(计算 id + 1 的值),然后再进行比较。这样的操作会导致数据库无法有效使用索引,因为它不能直接利用索引中的有序性来执行查询,而是需要对每行数据进行计算,这会增加查询的成本。 -
删除不再使用或者很少使用的索引
-
限制索引的数量,创建的索引越多,占用的磁盘空间越大,在更新表之后,索引也需要同步维护,甚至还需要重构索引,mysql生成执行计划的时候,也需要考虑索引。
-
-
索引能加速查询的原理
-
描述:类似于字典的目录,通过不断缩小查询的数据范围,筛选出最终需要的结果。
-
原理:
-
能将随机事件变成顺序的事件
-
索引提供了一种固定的查找方式来锁定数据
-
拥有特定的数据结构:
如果索引结构较为复杂,那么使用索引本身,就会占用一定的系统性能,针对这种情况,设计出几种数据结构存储索引。
例如fulltext,hash,btree,rtree。
innodb就采用了b+tree数据结构,b+tree会根据索引的物理结构将索引分为聚簇索引,非聚簇索引。
-
-
-
原理详述
-
为什么创建完索引查询速度会变快
传统查询方法:按照表的顺序遍历,不论查找几条数据,mysql都会将表的数据从头到尾遍历一遍。
索引查询方式:mysql通过b+tree算法生成一个索引文件,查询数据库时,按照索引文件进行遍历(折半查找可以大幅提高查询效率),找到响应的键,从而找到数据。
-
索引的数据结构
几乎没有使用二叉查询树,或者二叉查找树的升级版——红黑树进行查询的。
大部分都采用B树或者B+树
-
B树:
-
概念:
-
顺序:是一种自平衡的树,能够保持数据有序。
-
时间复杂度:能够让查找数据,访问数据,插入数据,删除数据都在对数时间内完成。
-
-
满足的数据结构:
-
子节点个数:由于B树是一个一般化的二叉查找树,每个子节点都可以拥有多于两个的子节点
-
节点的数据结构:B-Tree的每个节点都是
指针+键值对
组成,key和指针相互间隔,节点的两端是指针 -
每个非叶子节点由n-1个key和n个指针组成,每个叶子节点最少包含一个Key和两个指针
-
每个指针要么是Null,要么指向另外的节点,每个叶子节点的指针都是Null
-
一个节点中的key从左向右递减排列
-
指针指向的节点,所有Key大于指针左边的key,小于指针右边的key
-
所有的叶子节点具有相同的深度,等于树高(树的层数)
-
-
B树中按key检索数据的算法
首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点,递归查找,直到找到节点或者找到null指针,返回null就代表查找失败。
一个度为d的B树,假设其索引是n个key,则其树高的上线为
logd((n+1)/2)
,检索一个key,其查找节点个数的渐进复杂度为O(logdN),可以看出B树是一个非常有效率的索引数据结构。度:用来约束一个节点Key和指针的个数,每个非叶子结点由n-1个key和n个指针组成,其中d<=n<=2d,由于key的个数至少为1,所以d>=2
-
-
B+树
-
使用场景:B+树是B树的最常见变种,Mysql普遍使用B+树实现索引结构
-
与B树的对比:
-
B+树的每个非叶子节点都是由
Key+指针
组成的,B树是由键值对+指针
组成的; -
B+树的每个非叶子结点的指针数量等于key的数量,B树指针数量等于键值对数量+1
-
B+树的叶子节点只包含
键值对
,B树的叶子节点同样是键值对+指针
; -
B+树的叶子节点包含所有的key
-
-
在数据库和文件系统中的优化
增加顺序访问指针,每个叶子节点增加一个指向相邻叶子节点的指针,形成了带有顺序访问指针的B+树,为了提高区间访问的性能。
-
-
-
innoDB & myISAM & b+树
-
innoDB创建索引的方式:
innoDB根据主键ID先创建一个主键ID索引树(b+树),其叶子节点存储的是主键的ID和对应的指针(指向了数据文件的数据)。
-
innoDB创建索引的时机:
在建表的时候,innoDB就会自动建立好主键ID的索引树。
因为这个原因,使用innoDB引擎的mysql要求在建表的时候一定要有主键,没有的话就自动给生成主键。
-
手动给字段添加索引会发生什么:
如果给username字段添加索引,那么MYSQL就会给username添加索引树。
存的内容是:key为username,value存的是主键key。先在username索引树中找到主键key,再去主键索引树中找到数据的真实地址。
注:为什么不直接在username索引树中存储真实数据的地址,是因为如果建立了多个索引,每个都保存数据的真实地址,会产生大量冗余数据。
-
查询性能对比:
myisam查询性能更好,
原因是,Myisam直接找到物理地址后,就可以定位数据,而innoDB查到叶子节点后,还需要再查询一次主键索引树,才能定位到具体的数据。
-
-
-
索引的分类
-
根据逻辑-功能分类:
-
主键索引:一张表只能有一个主键索引,不允许重复,不允许有null
-
唯一索引:索引列的值,必须唯一,不允许有重复,但是允许有null值,一张表允许有多个唯一索引,如果是组合索引,那么列值的组合必须唯一。
-
普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许null值
-
全文索引:可以查找文本中的关键词,主要用于全文索引
-- 设置全文索引的最小和最大单词长度 set global innodb_ft_min_token_size = 3; set global innodb_ft_max_token_size = 3; create FULLTEXT INDEX idx_fulltext on table1 (column1); -- table1是要进行全文检索的表,column是包含想要搜索文字的列,search_query是具体要搜索的值 select * from table1 where match(column) against ("search_query");
-
-
根据逻辑-列数分类
-
单列索引:一个索引只包含一列,一个表可以有多个单列索引
-
组合索引:一个组合索引可以有两个或者两个以上的列,查询时遵循“最左前缀”原则,即,在where语句的组合中,要按照创建索引时字段的排列顺序,索引才能生效。
注:最左前缀匹配原则:如果创建了一个索引
create index idx_1 on table1 (column1, column2)
,在查询的时候,如果用的语句是下面这两条,那么是能够命中索引的where column1 = 'someString'
,WHERE column1 = 'some_value' AND column2 = 'another_value';
如果是
column2 = 'someString'
,就命中不到。只能从最左边开始命中。
如果是
WHERE column1 = 'some_value' AND column3 = 'another_value' AND column2 = 'another_value';
这种中间隔了个column3的情况,就不一定,执行计划不一定会充分利用索引。
-
-
根据物理分类:不是单独的索引类型,而是一种数据存储方式
-
聚簇索引:innodb的主键索引就是聚簇索引。可以理解为将数据与索引放在了一起,找到了索引也就找到了数据。将索引和数据放在同一个B+树中。
-
非聚簇索引:数据和索引是分开的,B+树的叶子节点存放的不是数据表的行记录。innoDB中的辅助索引,以及myISAM使用的都是非聚簇索引,每张表最多
注:每张表最多只能有一个聚簇索引。
-
查询速度对比:
-
聚簇索引更快的情况:
-
范围查询和排序:当查询涉及到范围查询或者排序,并且这些操作基于聚簇索引的列,由于相邻的数据行,在磁盘上是相邻存储的,可以减少IO操作,从而提高速度。
-
覆盖索引:如果聚簇索引包含了查询中所需的所有列,也可以避免操作数据库的数据,直接使用聚簇索引,拿到数据就行了,更快。
-
-
聚簇索引更慢的情况:
-
插入和更新操作:因为新的数据行需要按照聚簇索引的顺序插入,如果聚簇索引不稳定,频繁插入和删除,可能会导致数据页的分裂和合并,影响性能。
-
随机插入:聚簇索引对于随机插入的性能比较差,因为数据行需要根据索引的顺序,插入到磁盘中,而不是直接插入到表的末尾。
-
删除操作:删除操作可能导致聚簇索引产生碎片,从而影响性能。
-
-
-
-
-
索引失效
-
like %xxx
模糊查询使用关键字
like
,在后边的字符串采用%
开头,存储引擎会直接进行全表扫描 -
*
通配符使索引失效使用
*
属于全表扫描,不会使用索引 -
负向条件查询
not | not in | not like | != | <>
会使索引失效虽然不能使用
<>
,但是能够使用单独的<
或者>
!=
负向条件查询导致索引失效,对主键索引,不起作用。对主键索引使用负向查询,仍然能命中缓存。 -
索引列参与运算,会让索引失效,
应该尽量避免在where子句中对条件字段使用表达式操作,存储引擎会放弃使用索引,转而使用全表扫描。
-
对字段进行null值判断会使索引失效
尽量避免在where子句中进行null判断,
select id, title from article where title is null
-
使用
or
会让索引失效 -
违反最左前缀,会让索引失效
-
数据类型不一致,索引失效
select id, title form article where title = 4
,如果title本身是字符串类型的,传进来的是int类型的4,那么就会命中不了索引 -
<|>
不等号查询不当,会导致索引失效在查询的时候,mysql会一直向右匹配,直到索引搜索键,遇到
<|>
操作符,就会停止匹配,如果权重最高的索引搜索键使用了范围查询<|>
,那么别的<|>
都无法再次命中索引,也就是说,最多只有一个<|>
指定的范围列可以命中索引,如果查询条件中有两个以上的<|>
,无法命中全部的索引。select id, title from article where id > 1 and title < '4'
-
order by 使用不当
order by
如果是对主键进行排序,一定会命中主键索引,如果对别的列进行排序,不一定会命中索引,取决于多个条件,比如说索引有没有覆盖当前排序的列,不同的索引类型也会影响能否使用索引进行排序。
-
-
创建索引的方式简介
-
create index方式
create index idx_xxx on table1 (id,title)
-
alter table 方式
alter table table1 add idx_xxx (id, title)
如果想通过一条语句完成多个操作,可以使用这种方式。
-
-
禁用索引
-
引入:对于使用索引的表,在插入数据的时候,会对新插入的数据创建索引记录`,如果插入了大量的数据,建立索引的工作就会影响插入数据的速度。可以在批量插入之前先禁用索引,在插入完成之后再将索引开启。之后数据库会自动为这些数据构建索引。
-
操作语句:
Alter table table1 disable keys; alter table table1 enable keys;
-
-
索引的创建与删除
-
创建
-
主键索引:
-
建表时创建索引
create table table1 ( 'id' int(11) not null auto_increment, 'name' varchar(32), primary key('id') );
-
修改表结构时创建索引
alter table table1 add constrant t1_pk primary key (id);
注:如果是字符串字段,还可以制定索引的长度,在列命令后边加上索引的长度就可以了
create table table1 ( column1 varchar(255), index idx_column (column(10)) )
-
-
唯一索引:
-
建表时创建
create table table1 ( name varchar(32), unique INDEX index_unique_table1_name(name) );
-
建表后创建
create unique index index_table1_name on table1(name);
-
修改表结构时创建
alter table table1 add unique index index_table1_name(name);
-
-
普通索引
-
建表时创建
create table table1 ( name varchar(32), index index_table_name (name) );
-
建表后创建
create index index_table1_name on table1(name);
-
修改表结构时创建索引
alter table table1 add index index_table1_name(name);
-
-
全文索引
-
注:全文索引主要用来查找文本中的关键字,而不是直接与索引中的值进行比较,fulltext索引与别的索引不太一样,更像是一个搜索引擎,而不是简单的where语句的参数匹配。 fulltext索引配合match against使用,而不是使用where like,可以在create table, alter table, create index时使用,不过目前只有char, varchar, text列上可以创建使用全文索引
-
建表时创建
create table 'article' ( 'id' int(11) not null auto_increment, 'title' char(250) not null, 'content' text null, 'create_at' int(10) null default null, primary key ('id'), fulltext(contents) );
-
建表后创建
create fulltext index index_article_content on article()
-
修改表结构时进行创建
alter table article add fulltext index index_article_contents(contents);
-
-
组合索引
-
组合索引是多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用,使用组合索引时遵循最左前缀原则
-
在创建表的时候
create table table1( 'id' int(11), 'name' varchar(32), index idx_table1_id_name('id', 'name') );
-
在建表后
create index idx_table1_id_name on table1 (id, name);
-
修改表结构时
alter table table1 add index index_table1_id_name (id, name);
-
-
-
删除
drop index idx_table1_column1 on table1 alter table table1 drop index idx_table1_column1;
-
七、速度优化
limit速度优化
-
加缓存
通过添加缓存,减少对数据库的直接操作,可以通过redis,也可以通过elasticSearch
-
使用延迟关联+子查询
即,先利用limit得到数据的索引字段,然后再通过原表和索引字段关联,得到需要的数据。
select a.* from tableA a, (select id from tableB limit 100000, 20) b where a.id = b.id;
原理:
-
减少了数据传输:只有子查询的20行数据会被传输到主查询中,而不是整个表B,这可以减少数据传输的开销,尤其是当表B包含了大量的数据。
-
减少了比较操作:主查询中的条件a.id = b.id,只需要在子查询选择的20行数据上执行,而不是在整个表B上执行,这减少了比较的操作数量,有助于提高查询性能
-
减少索引扫描:如果表B有索引,子查询只需在索引的一小部分上执行,而不是在整个索引上执行,可以减少索引扫描的开销。
-
-
记录上次查询的最大id
如果id是连续的,我们可以把每次查询出的最大id记录下来,比如下面的sql语句,因为知道了上次查询的最大值是100020,所以直接从100020开始查。
select id, name from users where id > 100020 order by iod asc limit 20;
-
使用join进行查询
可以将sql语句改变一下,使用join进行查询,这个执行效率也比直接查询快得多,
select * from tableName as t1 join (select id from tableName order by id desc limit 20000, 10) as t2 on t1.id <= t2.id order by t1.id desc limit 10
注:使用join看似是使用笛卡尔积,但其实不是的,在实际执行计划中,数据库优化器会尽量避免生成完整的笛卡尔积,而是通过索引和连接条件,以更有效的方式进行连接操作。
-
从业务上实现
不做过多的分页,只分前100页,后面的不允许再分页查询了
-
不使用limit N,M
可以使用Limit N,将offset转化为where条件
sql查询日志
-
慢SQL:
就是查询很慢的sql,mysql作为一种数据库系统,本身提供了一种慢查询的日志记录,用来记录Mysql中超过特定阈值的sql语句,只要某个select语句的执行时间超过了这个日志中
long_query_time
规定的值,就可以将select看做是慢sql,该语句就会被记录进慢查询日志中。默认情况下,long_query_time是10秒钟,也就是说,只要一条sql执行时间超过10s,就是慢sql。当然也可以根据自己的项目进行更改。 -
查看慢sql阈值
mysql中查询慢sql的阈值
show variables like 'long%'
-
更改慢sql阈值
默认请款修改,Mysql是没有开启慢查询查询日志,这个功能。如果想检测慢sql,需要先进行开启。
show variables like 'slow%'
变量名slow_query_log,如果是off,就代表没有打开慢日志查询。
set global slow_query_log = 1
就可以进行打开了通过这个命令,只是打开当前开启的数据库,重启之后就失效了,如果需要永久生效,需要修改mysql数据库的配置文件my.cnf
[mysqld] # 开启全局的查询日志功能 slow_query_log=1 slow_query_log_file=E:\mysql-5.7.26-winx64\data\zahngsanfeng39C2-slow.log # 设置全局的日志查询阈值时间 long_query_time=3 log_output=FILE
设置之后重启下mysql服务器生效
开启了之后,就可以进行查看,有多少条sql语句被加入到了慢sql查询日志中
show GLOBAL STATUS LIKE '%Slow_queries%'
-
更改慢查询时间阈值
set global long_query_time=3
当然也能在my.config文件中进行全局修改配置
-
开启未使用索引的查询语句日志记录功能
想提高查询效率,很多情况下都要使用索引,为了知道有没有命中索引,Mysql提供了一个功能,将未使用索引的查询语句记录到日志中,可以开启这个语句对sql进行调优。
慢sql
-
产生的原因
-
sql语句问题
-
数据库及表锁定
-
服务器硬件配置低
-
mysql本身有故障
-
其他原因
-
-
程序员着重需要解决的原因
-
数据量过大,需要的I/O次数过多(因为数据量大的原因)
-
数据读取的方式有问题,缓存有没有,索引有没有,是否直接从磁盘进行读取的
-
数据加工方式不完美,数据有没有进行排序,有没有进行子查询
-
-
解决思路
-
将数据适当进行缓存
-
适当合并I/O:如果分别执行
select c1 from t1
与select c2 from t1
,与select c1, c2 from t1
相比,后者的开销更小。 -
合理进行分布式架构:在处理海量数据的时候,考虑将数据和I/O分散到多台主机上进行
-
慢sql预防
-
避免索引失效
- like %会使索引失效
-
- 通配符会使索引失效
- not | != | <> | not in | not like 等负向条件会使普通索引失效
- 索引列参与计算(±*/)会使索引失效
- 对字段进行Null值判断,会使索引失效
- 使用or连接会让索引失效
- 违反最左前缀原则会让组合索引失效
- 数据类型不一致会让索引失效
> <
范围查询不当会让索引失效- order by 使用不当会让索引失效
-
优化表结构设计
- 在满足需求的前提下,尽量使用小的数据类型
- 尽量使用tinyint, smallint, mediumint作为整数类型,而非int
- 尽可能使用not null定义字段,因为null会占用4个字节
- 数字类型默认为0,字符串类型默认为""
- 尽量少用text类型,非用不可时,最好独立出一张表
- 尽量使用timeStamp,而非dateTime
- 单表不要有太多字段,建议在20个字段之内
-
别的注意事项
-
用exists代替in,比如select num from tablea where exists (select y from tableb where num = a.num);
-
避免频繁创建和删除临时表,以减少系统表资源的消耗
-
用join代替in,join的效率高于in,因为join不需要创建临时表
-
尽量避免大事务操作,提高系统并发能力
-
优化分页查询
-
-
定位解决慢sql
-
治理优先级
如果存在多个慢sql,时间精力有限,需要有所侧重,应该遵循如下的治理优先级原则
-
如果存在主从分离,则先解决master主库,再解决slave从库
-
执行次数多的sql优先治理
-
如果某个sql语句会高并发地访问某一张表,应该优先治理
-
-
查看慢查询日志
首先要确认到底是哪里产生了慢sql,然后对症下药,
show global status like '%slow_queries%';
如果没有产生慢sql,那么value字段的值是0,如果有,value就是对应的条数
打开之前配置的mysql-slow.log日志文件,可以看到相关的数据。
除了使用日志来查看慢sql还能使用别的框架查看
-
ActivePerl
mysql自带了一个perl脚本文件,可以通过avtivePerl这个工具进行使用,安装了这个perl工具之后,就能在命令行中使用perl脚本了,可以当成命令来使用。
# 查询得到记录集最多的10个sql # 该命令后边跟着自己之前配置的慢sql文件位置即可 mysqldumpslow.pl -s r -t 10 ..\data\zhangsanfeng39c2=slow.log
mysqldumpslow命令,常用的命令:
- –help:输出帮助信息
- -v 输出详细信息
- -d 调试
- -s 按照什么排序,默认是at,显示顺序为倒序
- al 平均锁表时间
- ar 平均结果行数
- at 平均查询时间
- c 次数
- l 锁表时间
- r 总结果行数
- t 总查询时间
- -r 正序排序 即从小到大排序
- -t num限制显示的条数
- -a 显示出数字和字符串 默认数字为N 字符串为’S’
- -g pattern过滤字符串 ,后接正则表达式 如’10$'以10为结尾的条件
# 得到访问次数最多的10个sql mysqldumpslow.pl -s c -t 10 slow.log # 得到按照时间排序的前10条里面含有左连接的查询语句 mysqldumpslow.pl -s t -t 10 -g "left join" slow.log
通过这样的工具执行,就能知道现在有多少条和那几条慢sql了
-
show profile
还可以使用另一个工具,show profile,这是mysql提供的用来分析某个sql语句执行所消耗资源情况的命令,可以为sql语句的调优提供量化指标,但是该命令默认是关闭的,需要手动开启该功能。可以查询sql语句在服务器中的执行细节和生命周期。
-
查看功能是否开启
show variables like 'profiling'
如果显示value为off,就代表是关闭的 -
开启
set profiling=ON
-
查看sql执行历史记录
show profiles
,这个命令能将之前执行过的所有的sql都记录下来,显示结果包含query_id, duration, query,分别代表查询的id, 执行的时间,执行的语句。根据上边的id,执行
show profile cpu, block io for query 123
:123是上边的id。可选查看参数:
- ALL:所有开销
- block io:显示io相关开销
- context switches:显示上下文切换相关开销
- cpu:显示cpu相关开销
- ipc:显示发送接收相关开销
- memory:显示内存相关开销
- page faults:显示页面错误相关开销
- source:显示和source_function, source_file, source_line相关开销
- swaps:显示交换次数相关开销
如果执行上述命令,如果遇到如下几种情况就要考虑进行优化:
- converting heap to myisam:查询结果太大,内存不够用
- create tmp table:创建临时表
- copy to tmp table on disk:将内存中的临时表复制到磁盘
- locked:有锁定
-
explain分析慢sql语句
通过上边的一系列步骤,就可以知道有多少条慢sql,以及那些慢sql,接下来可以使用explain命令进一步分析,可以得到如下信息:
-
表的读取顺序
-
表的读取操作的操作类型
-
哪些索引可以被使用
-
哪些索引被实际使用
-
表之间的引用
-
每张表有多少行被优化器查询
-
-
mysql服务器参数调优
如果经过上述措施之后,sql的执行效果仍然不理想,比如order by和group by 无法使用索引,可以增大mysql服务器的max_length_for_short_data和sort_buffer_size等参数的设置
-
-
explain
-
作用
- 查看表的读取顺序
- 查看数据库读取操作的类型
- 查看有可能用到那些索引
- 查看真正用到那些索引
- 查看表之间的引用关系
- 查看表中有多少行记录被优化器查询
-
用法
-
语句:explain + sql语句
-
包含的内容
-
id:选择标识符
id是按照sql语法解析后分层的序号,代表着select查询的序列号,每个select查询都会自动分配一个唯一的标识符,包含一组数字,表示查询中执行select子句或者操作表的顺序,该值可能会重复,这个id值有以下三种情况:
- id值完全相同,会按从上到下的执行顺序。
- id值完全不同,如果是子查询,id的序号会递增,id值越大则优先级越高,也会优先被执行
- id值有相同也有不同的,值相同的部分,会按从上到下的顺序执行,值不同的部分,id值越大,优先级越高,也就越先被执行
-
select_type:表示查询的类型
主要是用来区分普通查询,联合查询,子查询等查询方式,有以下几种情况:
-
simple:代表简单select 查询,不使用union及子查询
-
primary:代表最外层的select查询
-
union:代表union中的第二个,或者随后的select查询,不依赖于外部查询的结果集
-
dependent union:代表union中的第二个或者随后的select查询,依赖于外部查询的结果集
-
subquery:代表子查询中的第一个select查询,不依赖于外部查询的结果集
-
dependent subquery:代表子查询中的第一个select查询,依赖于外部查询的结果集
-
derived:用于from 子句里有子查询的情况,mysql会递归执行这些子查询,将结果放到临时表中
-
uncacheable subquery:结果集不能被缓存的子查询,必须重新为外层查询的每一行进行评估
-
uncacheable union:union中的第二个或者随后的select查询,属于不可缓存的子查询
在开发中,最常用的是以下6种
-
simple
简单的select查询,查询中不包含子查询或者union等
--这个语句查出来的select_type就是simple explain select * from pet where name = 'Bowser'
-
primary
查询中若包含任何复杂的子部分,比如两表做union或者存在子查询,则外层的表操作类型为primary,内层的操作为union。
--结果包含两条,分别是primary和subquery explain select article, dealer, price from shop where price = (select max(price) from shop);
-
subquery
表示在select 或者where 列表中包含了子查询
explain select article, dealer, price from shop where price = (select max(price) from shop);
-
derived
在from 列表中包含的子查询被标记为derived(衍生),mysql会递归执行这些子查询,然后将结果放到临时表中
explain select s1.article, dealer, s1.price from shop s1 join ( select article, max(price) as price from shop group by article ) as s2 on s1.article = s2.article and s1.price = s2.price
-
union
若第二个select 出现在union之后,则被标记为union,若union包含在from的子查询中,外层的select被标记为derived。
-
union result
从union表获取结果的select
explain select owner from pet where name = 'Fluffy' union select type from event where name = 'fluffy'
-
-
table:输出结果集的表
table 表示我们查询的是那张表,如果sql中表有别名,这里出现的就是别名
-
partitions:匹配的分区
匹配的分区
-
type:表示表的连接类型
显示查询使用的是那种访问类型
- system:从系统表中读取一行,这是const连接类型的一个特例
- const:表示最多有一个匹配行,它将在查询开始时被读取,因为仅有一行,该行的列值,可被优化器认为是常数。const表很快,因为他们只读一次。
- eq_ref:表示查询条件是“等于”
- ref:表示查询条件是“不等于”
- ref_of_null:同ref,包含null值的行
- index_merge:代表索引联合查询
- unique_subquery:表示利用唯一索引进行子查询
- index_subquery:表示用非唯一索引进行子查询
- range:表示按照索引范围进行扫描
- index:表示按照索引扫描
- all:表示进行全表扫描
这几种类型的性能从最好到差依次为:
system > const > eq_ref > ref > range > index > all
一般来说,要保证查询至少到range级别,最好能达到ref级别
-
system:表中只有一行记录(等于系统表),这是const的特例,平时不会出现
-
const:const一般用于比较primary key或者unique索引,只匹配一行数据,索引性能比较高,比如我们根据主键作为查询条件进行查询,mysql优化器会将该查询转换为一个const常量,这表示通过索引一次就找到了结果。
-
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与其陪陪,常见于主键或者唯一索引扫描,也就是说当我们使用了主键或者唯一性索引进行查找的时候,明确知道查找的结果只有一个,这个时候type的值就是eq_ref,如下图中的这种连接类型,每次都进行精确查询,无须过多的扫描,因此查询效率更高,当然列的唯一性是需要根据实际情况决定的
explain select * from t1, t2 where t1.id = t2.id
-
ref:当查找的内容用到了索引,但不是主键索引好unique索引,即索引列的值不是唯一的,会有重复,会返回匹配某个单独值的所有行,当使用索引快速查找到了第一条数据,它仍然不会停止,会进行目标值附近的小范围扫描,这样的好处是他不需要进行全表扫描,因为索引是有序的,即使有重复的值,也是在非常小的范围内进行扫描,本质上是一种索引访问,可能会找到多个符合条件的行。
-
range:当我们在查询中使用了between, and,
>
,<
,in,or时,就会变成range类型,这是一种带有范围的索引扫描,表示只检索指定范围内的行,相对于index的全部索引扫描,它有范围限制,有扫描的起始点,因此性能要强于index -
index:full index scan,全索引扫描,这是另一种形式的全表扫描。index与ALL都是扫描全表,但index与All的区别是,index只扫描索引树,是先从索引树中读取索引记录,然后再回表取数据;ALL是从硬盘中读取数据。官方文档说是比ALL查询快,因为一般索引文件比数据文件小。
explain select * from employee order by rec_id;
-
all:这是Full Table Scan,即“全表扫描”,会通过遍历全表找到匹配的行。出现ALL通常意味着我们的SQL语句处于一种最原生的状态,有很大的优化空间,ALL是一种非常暴力和原始的查找方法,非常的耗时而且低效。
-
possible_keys:表示查询时,可能使用的索引。比如某个查询字段,可能关联一个或多个索引,那么这些索引都将被列出,但不一定被查询时实际使用。
-
key:表示实际使用的索引,若该值为null,则表示没有使用到索引,产生该结果有两种可能性:
-
没有创建索引
-
索引失效
若查询中使用饿了覆盖索引,则该索引仅仅出现在key列表中
-
-
key_len:索引字段的长度
表示索引中使用的关键字长度,可通过该列计算出查询时使用的索引的长度。在不损失精确性的情况下,该值越短越好,key_len显示的是索引字段的最大可能长度,而非实际使用的长度。key_len是根据定义计算得到的,而不是通过表内检索得到的,当我们查询时的条件用的越多,结果就会越准确,那么key_len就会越长,能查到相同结果的前提情况下,条件越少越好。
-
ref:列与索引的比较
指的是表之间的引用,一般是指 = 后面的内容,代表使用哪个列或者常数,与key一起从表中选择行。通俗的说,就是ref可以显示索引中哪一列或者const常量,在查询中被使用了,用于查找索引列对应的值,常与key一起被使用。
-
rows:扫描出的行数(估算的行数)
显示本次查询中,mysql执行查询时大概会扫描多少行,这是一个估计值,该值越小越好,如果值很大,说明索引没有很好发挥作用。
-
filtered:按表条件过滤的行百分比
表示该次查询中,复核某个条件的记录数所占的百分比
-
extra:执行情况的描述和说明
该列包含mysql解决查询的额外详细信息,一般表示当前使用多个索引类型,该列的值有如下:
-
distinct:表示去重,返回第一个满足条件的值
-
not exists:表示使用not exists查询
-
range checked for each record:表示有索引,但索引的选择率很低
-
using filesort:代表有序索引
-
using index:代表使用了索引扫描
-
using index condition:代表了索引查询
-
usingtemporary:代表进行临时表检索
-
Using where:代表使用where 条件查询
-
using index & using where :代表使用了索引和条件查询
-
NULL:代表查询的列未被索引覆盖,并且where 筛选条件是索引的前导列
-
Using sort_union:代表进行有序合并查询
-
using union:代表进行了合并查询
-
using intersect:代表索引交叉合并
-
impossible where noticed after reading const tables:代表读取const tables 查询结果为空
-
no tables used:表示没有使用表
-
using join buffer(Block nested Loop):代表使用join buffer(BNL)算法
-
Using MRR(Multi-Range Read) :表示使用辅助索引进行多范围读取
extra 常用的值有using index, using where, using index condition, using index & using where, null等
extra详解:
-
using index:当查询的列完全被索引覆盖,并且where中的筛选条件是索引前导列,会出现该值。
-
using where; using index:有两种情况会出现这种:
-
当查询的列被索引覆盖,且where中的查询条件是索引列之一,但不是前导列。这也意味着无法直接通过索引查找来查询到符合条件的数据。
-
当查询的列被索引覆盖,并且where中的查询条件表示索引前导列的一个范围,同样意味着无法通过索引查找,来查询到符合条件的数据。
-
-
using where:两种情况会出现这个
-
当查询的列没有被索引覆盖,并且where中的筛选条件是非索引前导列,extra中为using where
-
当查找的列没有被索引覆盖,且where中的筛选条件是非索引列,extra中为using where
注:using where表示通过索引或者表扫描的方式进行where条件的过滤,也就是说,没有可用的索引查找,这里的type都是all,说明mysql认为全表扫描是一种性能比较低的方式。
-
-
using index condition
-
查询的列不全在索引中,where条件是一个前导列的范围
-
查询列没有被完全的索引覆盖,查询条件完全可以使用到索引。
注,当出现当前using index condition时,意味着在多个查询列中,有些列无法直接使用索引,会根据能用索引的条件先搜索一遍,然后再匹配无法使用索引的条件。
-
-
null
查询的列未被索引覆盖,并且where筛选条件是索引的前导列。extra出现Null,说明还是用到了索引,但是部分字段没有被索引覆盖,必须通过“回表”实现。没有纯粹地用到索引,也不是完全没用到索引。
-
using filesort
mysql中无法利用索引完成的排序被称为“文件排序”,这说明mysql查询时对数据的使用,是一个外部的索引排序,而不是按照索引的默认排序进行的读取。一般在使用order by 关键字的时候,如果待排序的类,不能由该排序列所使用的索引直接完成排序的情况,那么mysql就会使用文件排序。
出现using filesort时,mysql会将数据在内存中进行排序,排序时使用的内存区域,时通过sort_buffer_size系统变量设置的排序区,这个排序区是每个thread独享的,所以有可能在同一时刻,mysql存在多个sort buffer内存区域。
mysql中filesort排序算法实际上有两种:
双路排序:首先根据相应的条件,去除相应的排序字段和可以直接定位行数据的行指针信息,然后在sort buffer中进行排序。
单路排序,是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序。
这种文件排序效率比较低,出现这种情况可以进行优化:
-
修改代码逻辑,不在Mysql中使用order by,而是在自己的代码中进行排序
-
使用Mysql索引,为待排序的列创建索引,直接利用索引的排序
-
-
using temporary
mysql在对查询结果排序时,会使用临时表,用于保存中间结果,常见于order by 和group by分组查询。
-
-
-