Loading

MySQL 进阶篇 Part 1

😉 本文共8790字,阅读时间约15min

体系结构

结构图

  1. 连接层

    最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

  2. 服务层

    第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的解析器和优化器,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。

  3. 引擎层

    存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。

  4. 存储层

    数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。

存储引擎

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,可以通过创建表的时候来指定存储引擎,而不是基于库的,所以存储引擎也可被称为表引擎

CREATE TABLE 表名(
    字段1 字段1类型 [ COMMENT 字段1注释 ] ,
    字段n 字段n类型 [COMMENT 字段n注释 ]
) ENGINE = INNODB [ COMMENT 表注释 ] ;

InnoDB

  1. 介绍:

    InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的MySQL 存储引擎。

  2. 特点

    1. DML操作遵循ACID模型,支持事务
    2. 行级锁,提高并发访问性能;
    3. 支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
  3. 文件

    xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构、数据和索引。

  4. 逻辑存储结构

    image-20230111200529851

    1. 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个Segment段。
    2. 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
    3. 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页
    4. 页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
    5. 行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。

MyISAM

  1. 介绍

    MyISAM是MySQL早期的默认存储引擎。

  2. 特点

    1. 不支持事务、外键、行级锁
    2. 支持表锁,不支持行锁
    3. 访问速度快

Memory

  1. 介绍

    表数据存储在内存,没有持久化,只能作为临时表或缓存

  2. 特点

    1. 内存存放
    2. hash索引

区别及特点

特点 InnoDB MyISAM Memory
适用场景 订单表(有ACID要求的,有并发性要求) 日志(插入、查询多,更新、删除少,对事务和并发性要求低) 缓存
事务安全 支持 - -
锁机制 行锁 表锁 表锁
支持外键 支持 - -

索引

介绍

  • 索引(index)是帮助MySQL高效查找数据数据结构(有序)。

  • 优缺点:

    • 优点:
      • 提高数据检索效率,降低数据库的IO成本
      • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
    • 缺点:
      • 索引列也是要占用磁盘空间的
      • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

索引结构 描述
B+Tree 最常见的索引类型,大部分引擎都支持B+树索引
Hash 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询

why B+ 树?

  1. 如果是二叉搜索树?不平衡,顺序插入会形成链表 => 层级过深 , 查找效率低

  2. 如果是红黑树?自平衡,只有二叉 => 层级过深 , 查找效率低

  3. 如果是B树?

    1. B树是一种多路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:image
    2. 特点:
      1. 5阶的B树,每一个节点最多存储4个key,对应5个指针。
      2. 一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。
      3. 在B树中,非叶子节点和叶子节点都会存放数据。这也造成树节点一页相同大小,B树的节点相比B+树(非叶节点只放索引)只能放更少的key和指针,会造成层级更深。
  4. 如果是B+树?

    1. B+Tree是B-Tree的变种,我们以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一下其结构示意图:

      image

    2. 非叶节点是索引部分,仅仅起到索引数据的作用,不存储数据。叶节点存放具体数据,在其叶子节点中要存储具体的数据。

    3. 和B树区别:

      1. 所有的数据都会出现在叶子节点
      2. 叶子节点形成一个单向链表。(这是mysql优化的,提高区间访问的性能,利于排序)
      3. 非叶子节点仅仅起到索引数据作用具体的数据都是在叶子节点存放的。

hash索引

  1. 结构

    1. 希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。

    2. 如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

      image

  2. 特点

    1. Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,...)
    2. 无法利用索引完成排序操作
    3. 查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索引
  3. 存储引擎支持

    在MySQL中,支持hash索引的是Memory存储引擎。 而InnoDB中具有自适应hash功能,hash索引是InnoDB存储引擎根据B+Tree索引在指定条件下自动构建的。

思考题: 为什么InnoDB存储引擎选择使用B+tree索引结构?

  1. 相对于二叉和红黑树,支持自平衡、支持多路,层级少
  2. 对于B树,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少指针跟着减少,要同样保存大量数据只能增加树的高度,导致性能降低
  3. 相对Hash索引,B+tree支持范围匹配及排序操作;

思考题:InnoDB主键索引的B+tree高度为多高呢?

如果树的高度为2,那么他能存储的数据量大概为:18736
如果树的高度为3,那么他能存储的数据量大概为:21939856

另外,如果有成千上万的数据,那么就要考虑分表,涉及运维篇知识

索引分类

两种分类

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
常规索引 快速定位特定数据 可以有多个
-- 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;

在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放一块,索引结构的叶子节点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索
    引。

聚集索引和二级索引的具体结构如下:

image

  • 聚集索引的叶子节点下挂的是这一行的数据 。
  • 二级索引的叶子节点下挂的是该字段值对应的主键值。

回表问题与覆盖索引

接下来,我们来分析一下,当我们执行如下的SQL语句时,具体的查找过程是什么样子的。

image

具体过程如下:

  1. 由于是根据name字段进行查询,所以先根据name='Arm'到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
  2. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。
  3. 最终拿到这一行的数据,直接返回即可。

回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。

以下两条SQL语句,那个执行效率高? 为什么?

A. select * from user where id = 10 ;

B. select * from user where name = 'Arm' ;

备注: id为主键,name字段创建的有索引;

A性能高于B,B还要回表

面试题:一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id, username, password from tb_user where username='itc';

解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引

Extra 含义
Using where; Using Index 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
Using index condition 查找使用了索引,但是需要回表查询数据

索引使用

单列索引&联合索引

  1. 介绍

    单列索引:即一个索引只包含单个列

    联合索引:即一个索引包含了多个列

  2. 使用原则:

    1. 优先建立联合索引
      1. 在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
      2. 减少开销:多个单列索引底层会创建多个B+索引树,索引建立得越多就越占磁盘空间,在更新数据的时候速度会越慢,故如果只有多条件联合查询时最好建联合索引。相当于创建了(col1)、(col1,col2)、(col1,col2,col3)3个索引
      3. 覆盖索引:假如查询SELECT col1, col2, col3 FROM 表名,由于查询的字段存在索引页中,那么可以从索引中直接获取,而不需要回表查询
      4. 效率高:对col1、col2、col3三列分别创建索引,MySQL只会选择辨识度高的一列作为索引。假设有100w的数据,一个索引筛选出10%的数据,那么可以筛选出10w的数据;对于组合索引而言,可以筛选出100w10%10%*10%=1000条数据。
    2. 区分度效果最好的字段位置越靠前越好
  3. mysql索引选择:

    多个单列索引在多条件查询时优化器会优先选择最高效的索引策略,可能只用一个索引,也可能将多个索引全用上。

  4. 最左前缀法则联合索引,查询从索引的最左列开始,并且不跳过索引中的列,针对查询条件中存在的列,顺序无所谓

    1. 当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。(k1,k3)也可以,不过只有k1生效。
    2. 部分失效场景:如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
    3. 查询条件中存在的列必须是符合最左前缀法则的,顺序无所谓,不要求这些列一定按联合索引顺序排列(优化器会优化),如where中。(k2,k3,k1)也会用到索引。
  5. 例子

    -- 创建联合索引
    ALTER TABLE employee ADD INDEX idx_name_salary (name,salary)
    -- 有效
    SELECT * FROM employee WHERE NAME='哪吒编程' 
    SELECT * FROM employee WHERE NAME='哪吒编程' AND salary=5000
    SELECT * FROM employee WHERE salary=5000 AND NAME='哪吒编程'(虽然违背了最左特性,但MySQL执行SQL时会进行优化,底层进行颠倒优化)
    -- 失效
    SELECT * FROM employee WHERE salary=5000
    

索引失效场景

  1. 索引列运算或函数操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';

  2. 字符串不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号

    对于优化器来说,如果等号两边的数据类型
    例如,explain select * from evt_sms where phone = 13020733815;这条SQL语句就会变为explain select * from evt_sms where cast(phone as signed int) = 13020733815;不一致,则会发生隐式转换。
    由于对索引列进行了函数操作,从而导致索引失效。
    
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。

  4. 如果 MySQL 评估使用索引比全表更慢,则不使用索引。当Mysql发现通过索引扫描的行记录数超过全表的10%-30%时,优化器可能会放弃走索引,自动变成全表扫描。

    1. or分割的条件(可能)
    	select * from user where userid = 1 or age = 18;
    a.使用or可能会使索引失效,从而全表扫描。
    对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程:全表扫描+索引扫描+合并。
    如果它一开始就走全表扫描,直接一遍扫描就完事。mysql是有优化器的,处于效率与成本考虑,遇到or条件,索引可能失效,看起来也合情合理。
    b.如果都有索引,可能会都走,index_merge优化
    
    2. 范围查询(可能)
    如出现范围查询(<, >),范围查询右侧的列索引失效。有些情况可以用>=或者<=来不会有索引失效问题。
    

索引使用规则

什么时候建立索引?

  1. 针对于数据量较大,且查询比较频繁的表建立索引。
  2. 常作为查询条件(where)、排序(order by)、分组(group by)的字段建立索引。
  3. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率

索引优化

  1. 联合索引要符合最左前缀法则

  2. 尽量使用覆盖索引,避免回表查询

  3. 避免索引失效情况

  4. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高

  5. 使用区分度高的前缀索引,降低索引大小。如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引。否则查询时,浪费大量的磁盘IO,影响查询效率。但前缀索引会增加回表次数,同时使得覆盖索引无效。

  6. 使用SQL提示,人为优化,但对mysql来讲只是建议罢了。use indexignore indexforce index

  7. 索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

SQL性能分析

增删改查执行频率

查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次:
SHOW GLOBAL STATUS LIKE 'Com_______'; 或者 SHOW SESSION STATUS LIKE 'Com_______';

判断是读多写少、频繁更新等等类型。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有
SQL语句的日志。通过慢查询日志,就可以定位出执行效率比较低的SQL,从而有针对性的进行优化。

profile详情

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。

查看每一条SQL的耗时情况:

mysql> show profiles;
+----------+------------+---------------------------------------------+
| Query_ID | Duration   | Query                                       |
+----------+------------+---------------------------------------------+
|        1 | 0.00074075 | select count(*) from tb_sku                 |
|        2 | 0.00047950 | select * from tb_user                       |
|        3 | 0.00028925 | select * from tb_user where id = 1          |
|        4 | 0.00047600 | select * from tb_user where name = '白起'   |
+----------+------------+---------------------------------------------+
4 rows in set, 1 warning (0.00 sec)

查看指定SQL各个阶段的耗时情况 :

mysql> show profile for query 4;
+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000089 |
| Executing hook on transaction  | 0.000006 |
| starting                       | 0.000007 |
| checking permissions           | 0.000005 |
| Opening tables                 | 0.000032 |
| init                           | 0.000004 |
| System lock                    | 0.000007 |
| optimizing                     | 0.000061 |
| statistics                     | 0.000182 |
| preparing                      | 0.000015 |
| executing                      | 0.000030 |
| end                            | 0.000003 |
| query end                      | 0.000003 |
| waiting for handler commit     | 0.000006 |
| closing tables                 | 0.000005 |
| freeing items                  | 0.000010 |
| cleaning up                    | 0.000011 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.00 sec)

explain

explain非常有用,常用来看判断是否走了索引

EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

image-20230111220248978

字段 含义
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 。
possible_key 显示可能应用在这张表上的索引,一个或多个。
key 实际使用的索引,如果为NULL,则没有使用索引。
key_len 表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好 。
rows MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。
filtered 表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好。
extra 有时可以根据这个判断有没有回表

| ALL | 全表扫描

| index | 索引全扫描

| range | 索引范围扫描,常用语<,<=,>=,between等操作

| ref | 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中

| eq_ref | 类似ref,区别在于使用的是唯一索引,使用主键的关联查询

| const/system | 单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询

| null | MySQL不访问任何表或索引,直接返回结果

SQL优化

插入优化

插入优化原则

普通插入:

  1. 采用批量插入(一次插入的数据不建议超过1000条)
  2. 手动提交事务
  3. 主键顺序插入(避免B+树发生页分裂,严重影响性能)

大批量插入:
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。

批量插入为什么快?

MySQL默认开启自动事务,也即每条SQL都会被当成一个事务,当插入6万多数据时,要开启6万次事务,事务的开销相比插入一条数据而言比例是非常高的,资源实际利用率是非常低的,而使用批量插入,只需开启一次事务,事务占用的开销比插入操作而言比例很小,资源实际利用率是很高的。

批量插入为什么快?

  1. 逐条插入每次都会新建一个事务,批量插入只会使用一个事务。
  2. 逐条插入每次都会插入binlog事务日志,也会影响效率。
  3. 从网络传输方面来说,批量插入多条数据,更省空间。(想想TCP报文头)

主键优化

顺序插入性能高

数据库主键是自增好还是UUID好,分布式环境下如何保证主键的唯一性

可以使用twitter的snowflake来生成主键,snowflake生成的主键就是介于自增长和UUID之间的一种主键(存储空间小、速度快、分布式、时间序列)

索引设计原则

  1. 满足业务需求的情况下,尽量降低主键的长度。
  2. 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
  3. 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
  4. 业务操作时,避免对主键的修改

::: tip 知识小贴士:

MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。

:::

order by 优化

两种排序

MySQL的排序,有两种方式:

Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。

对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index

-- age、phone都无索引
explain select id,age,phone from tb_user order by age;
explain select id,age,phone from tb_user order by age, phone;
-- type 会是ALL,Extra显示的是Using filesort

-- 创建索引
create index idx_user_age_phone_aa on tb_user(age,phone);
explain select id,age,phone from tb_user order by age;
-- type 会是index,Extra显示的是Using Index, 性能较高

-- 根据age, phone进行降序排序
explain select id,age,phone from tb_user order by age desc , phone desc;
-- Extra显示的是Backward index scan; Using index, 代表反向扫描索引,因为在MySQL中我们创建的索引,默认索引的叶子节点是从小到大排序的

-- 根据phone,age进行升序排序,phone在前,age在后。
explain select id,age,phone from tb_user order by phone , age;
-- 排序时,也需要满足最左前缀法则,否则也会出现 `filesort`。因为在创建索引的时候, age是第一个字段,phone是第二个字段,所以排序时,也就该按照这个顺序来,否则就会出现 `Usingfilesort`。

-- 一个升序、一个降序均为Using filesort
-- 创建联合索引(age 升序排序,phone 倒序排序)
create index idx_phone_age_ad on tb_user(age asc,phone desc);

优化原则

  1. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  2. 尽量使用覆盖索引。
  3. 多字段排序, 一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  4. 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)

group by 优化

  1. 在分组操作时,可以通过索引来提高效率。
  2. 分组操作时,索引的使用也是满足最左前缀法则的。

limit优化

深度分页效率低

mysql> select * from tb_user limit 900000,10;
10 rows in set (0.28 sec)
-- 当在进行分页查询时,如果执行 limit 2000000,10 ,此时需要MySQL排序前2000010 记录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大。
-- type是all,默认走全表扫描

覆盖索引加子查询优化

优化思路: 一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。加入order by field, field必须有索引

-- 此语句耗时很长
select * from tb_sku limit 9000000, 10;
-- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;


-- 子查询优化
select * from table_name 
inner join 
( select id from table_name where (user = xxx) limit 10000,10) b 
using (id)
select * from table_name where user = xxx limit 10000,10

-- 相比较结果是(500w条数据):第一条花费平均耗时约为第二条的 1/3 左右。同样是较大的 offset,第一条的查询更为复杂,为什么性能反而得到了提升?这涉及到 mysql 主索引的数据结构 b+Tree ,这里不展开,基本原理就是:
-- 子查询只用到了索引列,没有取实际的数据,所以不涉及到磁盘IO,所以即使是比较大的 offset 查询速度也不会太差。利用子查询的方式,把原来的基于 user 的搜索转化为基于主键(id)的搜索,主查询因为已经获得了准确的索引值,所以查询过程也相对较快。

-- 记录上次的最大id,用id做范围查询
https://blog.csdn.net/czx2018/article/details/107911967

-- 数据的时效性,限制分页数目

count优化

 select count(*) from tb_user;

在之前的测试中,我们发现,如果数据量很大,在执行count操作时,是非常耗时的。

  • MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高; 但是如果是带条件的count,MyISAM也慢。
  • InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

如果说要大幅度提升InnoDB表的count效率,主要的优化思路:自己计数(可以借助于redis这样的数据库进行,但是如果是带条件的count又比较麻烦了)。

count用法

count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是NULL,累计值就加 1,否则不加,最后返回累计值。

用法:count(*)count(主键)count(字段)count(数字)

count 用法 含义
count(主键) InnoDB 引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)
count(字段) 没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
count(数字) InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。
count(*) InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)。

update优化

我们主要需要注意一下update语句执行时的注意事项。

update course set name = 'javaEE' where id = 1 ;

当我们在执行删除的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放。

但是当我们在执行如下SQL时。

update course set name = 'SpringBoot' where name = 'PHP' ;

当我们开启多个事务,在执行上述的SQL时,我们发现行锁升级为了表锁。 导致该update语句的性能大大降低。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁升级为表锁 。也就是说我这边事务没有提交的话,其他关于这个表的update都不会执行成功,导致该update语句的性能大大降低。

视图/存储过程/触发器

视图

  1. 介绍

    视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。

    例子: create or replace view stu_wll as select id,name from student where id<=10;

    查看视图数据:SELECT*FROM 视图名称;

  2. 作用

    1. 经常使用的查询可以被定义为视图
    2. 数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。

存储过程

存储过程是事先经过编译并存储在数据库中的一段SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。 存储过程思想上很简单,就是数据库SQL 语言层面的代码封装与重用。

特点

  1. 封装
  2. 复用
  3. 可以接收参数,也可以返回数据减少网络交互,效率提升
-- 创建
CREATE PROCEDURE 存储过程名称( [参数列表] ) 

BEGIN

	 SQL 语句 

END;
-- 调用
CALL 名称 ( [参数])

触发器

  1. 介绍
    1. 触发器是与表有关的数据库对象,指在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
    2. 使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发(比如说 一条语句影响了 5 行 则会被触发 5 次),不支持语句级触发(比如说 一条语句影响了 5 行 则会被触发 1 次)。
触发器类型 NEW 和 OLD
INSERT NEW 表示将要或者已经新增的数据
UPDATE OLD表示修改之前的数据,NEW表示将要或已经修改后的数据
DELETE OLD表示将要或者已经删除的数据
posted @ 2023-01-12 16:35  iterationjia  阅读(153)  评论(0)    收藏  举报