Mysql学习

显示字符集编码

image-20220117121933893

mysql架构

image-20220125173346275

逻辑架构

在这里插入图片描述

  • Client :
    提供连接MySQL服务器功能的常用工具集
  • Server :
    MySQL实例,真正提供数据存储和数据处理功能的MySQL服务器进程
  • mysqld:
    MySQL服务器守护程序,在后台运行。它管理着客户端请求。mysqld是一个多线程的进程,允许多个会话连接,端口监听连接,管理MySQL实例
  • MySQL memory allocation:
    MySQL的要求的内存空间是动态的,比如 innodb_buffer_pool_size (from 5.7.5), key_buffer_size。每个会话都有独一无二的执行计划,我们只能共享同一会话域内的数据集。
  • SESSION
    为每个客户端连接分配一个会话,动态分配和回收。用于查询处理,每个会话同时具备一个缓冲区。每个会话是作为一个线程执行的
  • Parser
    检测SQL语句语法,为每条SQL语句生成SQL_ID,用户认证也发生在这个阶段
  • Optimizer
    创造一个有效率的执行计划(根据具体的存储引擎)。它将会重写查询语句。比如:InnoDB有共享缓冲区,所以,优化器会首先从预先缓存的数据中提取。使用 table statistics optimizer将会为SQL查询生成一个执行计划。用户权限检查也发生在这个阶段。
  • Metadata cache
    缓存对象元信息和统计信息
  • Query cache
    共享在内存中的完全一样的查询语句。如果完全相同的查询在缓存命中,MySQL服务器会直接从缓存中去检索结果。缓存是会话间共享的,所以为一个客户生成的结果集也能为另一个客户所用。查询缓存基于SQL_ID。将SELECT语句写入视图就是查询缓存最好的例子。
  • key cache
    缓存表索引。MySQL keys是索引。如果索引数据量小,它将缓存索引结构和叶子节点(存储索引数据)。如果索引很大,它只会缓存索引结构,通常供MyISAM存储引擎使用

数据库引擎:

image-20220125173524623

引擎对比:

image-20220125173746074

image-20220125174645385

SQL性能下降的原因

  • 查询语句写的差。
  • 索引失效:索引建了,但是没有用上。
  • 关联 查询太多join(设计缺陷或者不得已的需求)。
  • 服务器调优以及各个参数的设置(缓冲、线程数等)。

机读顺序

image-20220126104245756

七种JOIN理论

七种JOIN理论

/* 1 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key;

/* 2 */
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key;

/* 3 */
SELECT <select_list> FROM TableA A INNER JOIN TableB B ON A.Key = B.Key;

/* 4 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL;

/* 5 */
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL;

/* 6 */
SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key;
/* MySQL不支持FULL OUTER JOIN这种语法 可以改成 1+2 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key
UNION
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key;

/* 7 */
SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL OR B.Key IS NULL;
/* MySQL不支持FULL OUTER JOIN这种语法 可以改成 4+5 */
SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL;
UNION
SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL;

训练:

image-20220126112014110

笛卡尔积

image-20220126112141713

inner join

image-20220126112509976

left join

image-20220126112649545

right join

image-20220126112731024

返回所有记录

image-20220126113400563

 

索引

索引的本质:索引是排好序的快速查找数据结构。

重点:索引会影响到MySQL查找(WHERE的查询条件)和排序(ORDER BY)两大功能!

除了数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上

Linux下查看磁盘空间命令 df -h 
[root@VM-16-3-centos ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        908M     0  908M   0% /dev
tmpfs           919M   32K  919M   1% /dev/shm
tmpfs           919M  624K  919M   1% /run
tmpfs           919M     0  919M   0% /sys/fs/cgroup
/dev/vda1        59G  7.4G   50G  14% /
tmpfs           184M     0  184M   0% /run/user/0

我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种数据结构的索引之外,还有哈希索引(Hash Index)等。

索引的优势和劣势

优势:

  • 查找:类似大学图书馆的书目索引,提高数据检索的效率,降低数据库的IO成本。
  • 排序:通过索引対数据进行排序,降低数据排序的成本,降低了CPU的消耗。

劣势:

  • 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
  • 虽然索引大大提高了查询速度,但是同时会降低表的更新速度,例如对表频繁的进行INSERTUPDATEDELETE。因为更新表的时候,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加的索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
  • 索引只是提高效率的一个因素,如果MySQL有大数据量的表,就需要花时间研究建立最优秀的索引。

MySQL索引分类

索引分类:

  • 单值索引:一个索引只包含单个列,一个表可以有多个单列索引。
  • 唯一索引:索引列的值必须唯一,但是允许空值。
  • 复合索引:一个索引包含多个字段。

建议:一张表建的索引最好不要超过5个!

/* 基本语法 */

/* 1、创建索引 [UNIQUE]可以省略*/
/* 如果只写一个字段就是单值索引,写多个字段就是复合索引 */
CREATE [UNIQUE] INDEX indexName ON tabName(columnName(length));

/* 2、删除索引 */
DROP INDEX [indexName] ON tabName;

/* 3、查看索引 */
/* 加上\G就可以以列的形式查看了 不加\G就是以表的形式查看 */
SHOW INDEX FROM tabName \G;

使用ALTER命令来为数据表添加索引

/* 1、该语句添加一个主键,这意味着索引值必须是唯一的,并且不能为NULL */
ALTER TABLE tabName ADD PRIMARY KEY(column_list);

/* 2、该语句创建索引的键值必须是唯一的(除了NULL之外,NULL可能会出现多次) */
ALTER TABLE tabName ADD UNIQUE indexName(column_list);

/* 3、该语句创建普通索引,索引值可以出现多次 */
ALTER TABLE tabName ADD INDEX indexName(column_list);

/* 4、该语句指定了索引为FULLTEXT,用于全文检索 */
ALTER TABLE tabName ADD FULLTEXT indexName(column_list);

MySQL索引数据结构

索引数据结构:

  • BTree索引。
  • Hash索引。
  • Full-text全文索引。
  • R-Tree索引。

BTree索引检索原理:

BTree

哪些情况不要建索引

  • 记录太少的表。
  • 经常增删改的表。
  • 频繁更新的字段不适合创建索引。
  • Where条件里用不到的字段不创建索引。
  • 假如一个表有10万行记录,有一个字段A只有true和false两种值,并且每个值的分布概率大约为50%,那么对A字段建索引一般不会提高数据库的查询速度。索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。

性能分析

EXPLAIN简介

EXPLAIN是什么?

EXPLAIN:SQL的执行计划,使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的。

EXPLAIN怎么使用?

语法:explain + SQL

mysql> explain select * from tbl_emp\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_emp
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 8
        Extra: NULL
1 row in set (0.00 sec)

ERROR: 
No query specified

EXPLAIN能干嘛?

可以查看以下信息:

  • id:表的读取顺序。
  • select_type:数据读取操作的操作类型。
  • possible_keys:哪些索引可以使用。
  • key:哪些索引被实际使用。
  • ref:表之间的引用。
  • rows:每张表有多少行被优化器查询。

EXPLAIN字段

id

id:表的读取和加载顺序。

值有以下三种情况:

  • id相同,执行顺序由上至下。
  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。
  • id有相同有不同,同时存在。永远是id大的优先级最高,id相等的时候顺序执行。

select_type

select_type:数据查询的类型,主要是用于区别,普通查询、联合查询、子查询等的复杂查询。

  • SIMPLE:简单的SELECT查询,查询中不包含子查询或者UNION 

  • PRIMARY:查询中如果包含任何复杂的子部分,最外层查询则被标记为PRIMARY

  • SUBQUERY:在SELECT或者WHERE子句中包含了子查询。

  • DERIVED:在FROM子句中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表中。

  • UNION:如果第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED

  • UNION RESULT:从UNION表获取结果的SELECT

    eg:

    image-20220129211613525

type

type:访问类型排列。

从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL。除了ALL没有用到索引,其他级别都用到索引了。

一般来说,得保证查询至少达到range级别,最好达到ref

  • system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计。
  • const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转化为一个常量。
  • eq_ref:唯一性索引扫描,读取本表中和关联表表中的每行组合成的一行,查出来只有一条记录。除 了 system 和 const 类型之外, 这是最好的联接类型。
  • ref:非唯一性索引扫描,返回本表和关联表某个值匹配的所有行,查出来有多条记录。
  • range:只检索给定范围的行,一般就是在WHERE语句中出现了BETWEEN< >in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引树的某一点,而结束于另一点,不用扫描全部索引。
  • indexFull Index Scan,全索引扫描,indexALL的区别为index类型只遍历索引树。也就是说虽然ALLindex都是读全表,但是index是从索引中读的,ALL是从磁盘中读取的。
  • ALLFull Table Scan,没有用到索引,全表扫描。

possible_keys 和 key

possible_keys:显示可能应用在这张表中的索引,一个或者多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

key:实际使用的索引。如果为NULL,则没有使用索引。查询中如果使用了覆盖索引,则该索引仅仅出现在key列表中。

key_len

key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。在不损失精度的情况下,长度越短越好。

key_len计算规则:https://blog.csdn.net/qq_34930488/article/details/102931490

mysql> desc category;
+---------------+------------+------+-----+---------+----------------+
| Field         | Type       | Null | Key | Default | Extra          |
+---------------+------------+------+-----+---------+----------------+
| cat_id        | bigint(20) | NO   | PRI | NULL    | auto_increment |
| name          | char(50)   | YES  |     | NULL    |                |
| parent_cid    | bigint(20) | YES  |     | NULL    |                |
| cat_level     | int(11)    | YES  |     | NULL    |                |
| show_status   | tinyint(4) | YES  |     | NULL    |                |
| sort          | int(11)    | YES  |     | NULL    |                |
| icon          | char(255)  | YES  |     | NULL    |                |
| product_unit  | char(50)   | YES  |     | NULL    |                |
| product_count | int(11)    | YES  |     | NULL    |                |
+---------------+------------+------+-----+---------+----------------+
9 rows in set (0.00 sec)


mysql> explain select cat_id from category where cat_id between 10 and 20 \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: category
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY  # 用到了主键索引,通过查看表结构知道,cat_id是bigint类型,占用8个字节
      key_len: 8        # 这里只用到了cat_id主键索引,所以长度就是8!
          ref: NULL
         rows: 11
     filtered: 100.00
        Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)

ref

ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

rows

rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录需要读取的行数。

Extra

Extra:包含不适合在其他列中显示但十分重要的额外信息。

  • Using filesort:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作成为”文件内排序”。
# 排序没有使用索引
mysql> explain select name from category where name='Tangs' order by cat_level \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: category
   partitions: NULL
         type: ref
possible_keys: idx_name_parentCid_catLevel
          key: idx_name_parentCid_catLevel
      key_len: 201
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where; Using index; Using filesort
1 row in set, 1 warning (0.00 sec)

#~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
# 排序使用到了索引

mysql> explain select name from category where name='zz' order by parent_cid,cat_level\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: category
   partitions: NULL
         type: ref
possible_keys: idx_name_parentCid_catLevel
          key: idx_name_parentCid_catLevel
      key_len: 201
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
  • Using temporary:使用了临时表保存中间结果,MySQL在対查询结果排序时使用了临时表。常见于排序order by和分组查询group by临时表対系统性能损耗很大。
  • Using index:表示相应的SELECT操作中使用了覆盖索引,避免访问了表的数据行,效率不错!如果同时出现Using where,表示索引被用来执行索引键值的查找;如果没有同时出现Using where,表明索引用来读取数据而非执行查找动作。
# 覆盖索引
# 就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
# 注意:如果要使用覆盖索引,一定不能写SELECT *,要写出具体的字段。
mysql> explain select cat_id from category \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: category
   partitions: NULL
         type: index
possible_keys: NULL       
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 1425
     filtered: 100.00
        Extra: Using index   # select的数据列只用从索引中就能够取得,不必从数据表中读取   
1 row in set, 1 warning (0.00 sec)
  • Using where:表明使用了WHERE过滤。
  • Using join buffer:使用了连接缓存。
  • impossible whereWHERE子句的值总是false,不能用来获取任何元组。
mysql> explain select name from staffs where name = 'zz' and name = 'cc'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
   partitions: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: Impossible WHERE   # 不可能字段同时查到两个名字
1 row in set, 1 warning (0.00 sec)

MySQL中Explain的Extra字段值Using index和Using where;Using index和Using where以及Using index condition的区别

在分别介绍以上四个值之前,我们需要知道,MySQL的架构分成了server层和存储引擎层(storage engine),server层通过调用存储引擎层来返回数据。

其中Using index表示查询的列被索引覆盖,因而无需再回表查询,因而效率较高。例如:select id from test where id = 5;其中id为主键。

​ Using where;Using index表示查询的列被索引覆盖,且where筛选条件是索引列前导列的一个范围,或者是索引列的非前导列,例如:select id from test where id > 5;。很明显,效率也很高。

​ Using where表示查询的列未被索引覆盖,且where筛选条件是索引列前导列的一个范围,或者是索引列的非前导列,或者是非索引列,例如:select * from test where id > 30; 。因为未被索引覆盖,所以需要回表,因而性能比前两者差。

​ Extra为null表示查询的列未被索引覆盖,且where筛选条件是索引的前导列,这意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,因而性能也比前两者差。

​ Using index condition是MySQL 5.6中引入的一种新特性,叫做索引下推,是一种在存储引擎层使用索引过滤数据的一种优化方式。优化了二级索引,减少回表次数,将判断条件传递给引擎,引擎判断索引是否符合条件,符合才会返回数据给服务器。

image-20220129210757192

索引分析

单表索引分析

数据准备

案例:查询category_id为1且comments大于1的情况下,views最多的article_id

# 1、sql语句
SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;

# 2、sql执行计划
mysql> EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: article
   partitions: NULL
         type: ALL  #最坏的情况
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
     filtered: 20.00
        Extra: Using where; Using filesort  # 产生了文件内排序,需要优化SQL
1 row in set, 1 warning (0.00 sec)

2、创建索引idx_article_ccv

CREATE INDEX idx_article_ccv ON article(category_id,comments,views);

3、查看当前索引。

image-20220129213500083

4、查看现在SQL语句的执行计划。

image-20220129213519241

发现创建符合索引idx_article_ccv之后,虽然解决了全表扫描的问题,但是在order by排序的时候没有用到索引,MySQL居然还是用的**Using filesort**

5、我们试试把SQL修改为SELECT id,author_id FROM article WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;看看SQL的执行计划。

image-20220129214319416

推论:当comments > 1的时候order by排序views字段索引就用不上,但是当comments = 1的时候order by排序views字段索引就可以用上!!!

所以,范围值使索引失效。

6、我们现在知道范围之后的索引会失效,原来的索引idx_article_ccv最后一个字段views会失效,那么我们如果删除这个索引,创建idx_article_cv索引呢????

/* 创建索引 idx_article_cv */
CREATE INDEX idx_article_cv ON article(category_id,views);

查看当前的索引

image-20220129214725701

7、当前索引是idx_article_cv,来看一下SQL执行计划。

image-20220129214743296

成功了

两表索引分析

数据准备

image-20220129215115964

两表连接查询的SQL执行计划

1、不创建索引的情况下,SQL的执行计划。

image-20220129215318710

bookclass两张表都是没有使用索引,全表扫描,那么如果进行优化,索引是创建在book表还是创建在class

2、左表(book表)创建索引。

创建索引idx_book_card

/* 在book表创建索引 */
CREATE INDEX idx_book_card ON book(card);

book表中有idx_book_card索引的情况下,查看SQL执行计划

image-20220129215436042

3、删除book表的索引,右表(class表)创建索引。

创建索引idx_class_card

/* 在class表创建索引 */
CREATE INDEX idx_class_card ON class(card);

class表中有idx_class_card索引的情况下,查看SQL执行计划

image-20220129215703969

由此可见,左连接将索引创建在右表上更合适,右连接将索引创建在左表上更合适。

三张表索引分析

数据准备

DROP TABLE IF EXISTS `phone`;

CREATE TABLE IF NOT EXISTS `phone`(
`phone_id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`card` INT(10) UNSIGNED NOT NULL COMMENT '分类' 
) COMMENT '手机';

三表连接查询SQL优化

1、不加任何索引,查看SQL执行计划。

image-20220130195042549

2、根据两表查询优化的经验,左连接需要在右表上添加索引,所以尝试在book表和phone表上添加索引。

/* 在book表创建索引 */
CREATE INDEX idx_book_card ON book(card);

/* 在phone表上创建索引 */
CREATE INDEX idx_phone_card ON phone(card);

再次执行SQL的执行计划

image-20220130195317676

结论

JOIN语句的优化:

  • 尽可能减少JOIN语句中的NestedLoop(嵌套循环)的总次数:永远都是小的结果集驱动大的结果集
  • 优先优化NestedLoop的内层循环。
  • 保证JOIN语句中被驱动表上JOIN条件字段已经被索引。
  • 当无法保证被驱动表的JOIN条件字段被索引且内存资源充足的前提下,不要太吝惜Join Buffer 的设置。

索引失效

建表:

image-20220130202619301

建立索引

ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);

查看效果

三条查询语句

image-20220130203429755

只有长度在变化,其他都是好的

索引失效(应该避免)

从左开始且不跳列才不会失效

  • 最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列

image-20220130203719338

image-20220130203842786

image-20220130203749391

key_len不对,索引失效,不符合最佳左前缀

索引列上不做额外操作才不会失效

不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。

image-20220130205238614

少用>,<,between…and等结构

  • 存储引擎不能使用索引中范围条件右边的列。(就是>,<,between…and)
  • 范围条件右边的索引失效

image-20220130222810515

减少select *

  • 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致))
    区别在于extra,索引的不同,速度不一样

image-20220130223926414

不用 is null, is not null

  • is null, is not null 也无法使用索引。

image-20220130224127150

模糊查询

  • like以通配符开头(’%abc…’),mysql索引失效会变成全表扫描的操作。
  • image-20220130224520085

image-20220130224447994

只有xx%前缀查询才不会失效

解决方法:

把*替换为索引的字段值(id可以不加索引)

image-20220130224928257

类型要正确

即使类型不正确也可以查询,但是底层会帮你转换类型,在判断,但会浪费时间,索引直接失效,变成了全表查询

  • 字符串不加单引号索引失效。

  • image-20220130225213417

    把*替换为字段值会好些

image-20220130225125971

不用or关键字就不会失效

  • 少用or,用它来连接时会索引失效。

image-20220130225352233

面试常考

建表

image-20220201190057941

建立索引

image-20220201190132165

explain

正常顺序

image-20220201190200564

image-20220201190228297

image-20220201190243309

image-20220201190317209

乱序

image-20220201190613078

打乱顺序mysql的最左前缀原则仍符合,因为mysql有优化器会帮你查询是否匹配

范围

image-20220201195817005

如果开始限定范围
第一条会用到3个索引,前两个用来查找,c3用来排序
第二条用到了4个索引,前3个用来查找,c4用来排序

因为mysql引擎会优化第二个sql语句在底层已经变成

explain select * from test03 where c1=’a1’ and c2=’a2’ and c3=’a3’ and c4>’a4’;

使用order by
下面那个例子都是一样的
前两个都是在查找,第三个只是在排序,到了这里就已经断了,所以c4可有可无

image-20220201200304494

如果使用c4排序,会出现Using filesort。,因为优化器会给你文件排序(因为中间跳了一个)

image-20220204121522497

这个也会出现Using filesort,因为顺序颠倒了

 explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;

这个就不会出现filesort,因为c2的值已经确认了

image-20220204122519176

groupby之前必排序,规则和orderby差不多

模糊查询

%在左边会导致索引失效,在右边可以不失效

image-20220204123846471

总结

对于单键索引,尽量选择针对当前query过滤性更好的索引。
在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引。
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的。

口诀:

全值匹配我最爱, 最左前缀要遵守;

带头大哥不能死, 中间兄弟不能断;

索引列上少计算, 范围之后全失效;

LIKE 百分写最右, 覆盖索引不写 \*

不等空值还有 OR, 索引影响要注意;

VAR 引号不可丢, SQL 优化有诀窍。

查询截取分析

SQL调优过程:

观察,至少跑1天,看看生产的慢SQL情况。
开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
explain + 慢SQL分析。
show profile。
运维经理 or DBA,进行SQL数据库服务器的参数调优。

总结:

慢查询的开启并捕获
explain + 慢SQL分析
show profile查询SQL在Mysql服务器里面的执行细节和生命周期情况
SQL数据库服务器的参数调优。

in和exists的区别

小表驱动大表

RBO原理:

当B表的数据集必须小于A表的数据集时,用in优于exists。

select * from A where id in (select id from B)
等价于:
for select id from B
for select * from A where A.id = B.id
当A表的数据集系小于B表的数据集时,用exists优于in

select * from A where exists (select 1 from B where B.id = A.id)
等价于:
for select * from A
for select * from B where B.id = A.id

关于exists的关键字

该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。

提示

EXSTS(subquey)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以是 SELECT 1 或select ‘X’,官方说法是实际执行时会忽略SELECT清单,因此没有区别。
EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
EXISTS子查询往往也可以用条件表达式,其他子查询或者JOIN来替代,何种最优需要具体问题具体分析
3.2 OrderBy优化
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

建立一张表以及一个索引

image-20220204195232493

如果索引的时候也是按照顺序,两种情况都是一样的

但是索引的时候不按顺序
就会出现这样的情况

MySQL支持二种方式的排序:FileSort和lIndex

Index效率高,它指MySQL扫描索引本身完成排序,FileSort方式效率较低。

ORDER BY满足两情况,会使用Index方式排序:

1.ORDER BY语句使用索引最左前列。
2.使用where子句与Order BY子句条件列组合满足索引最左前列。
如果不在索引列上,mysql的filesort有两种算法:双路排序、单路排序

双路排序:MySQL4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和OrderBy列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读对应的数据输出。
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序压的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
但是用单路有问题

在sort_buffer中,单路排序比双路排序要多占用很多空间,因为单路排序是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

优化策略

1.增大sort_buffer_size参数的设置
2.增大max_length_for_sort_data参数的设置
3.为什么设置sort_buffer_size、max_length_for_sort_data参数能优化排序?

主要是因为可以提高Order By的速度

具体原因如下:

Order by时select * 是一个Query需要的字段,这点非常重要。在这里的影响是;

当Query的字段大小总和小于max_length_for_sort_data而且排序字段不是TEXT|BLOB类型时,会用改进后的算法——单路排序,否则用老算法——多路排序。
两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer__size。
尝试提高sort_buffer_size,不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。
尝试提高max_length_for_sort_data,提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。

小结

为排序使用索引

MySql两种排序方式∶文件排序 或 扫描有序索引排序

MySql能为 排序 与 查询 使用相同的索引

创建复合索引 a_b_c (a, b, c)

order by能使用索引最左前缀

ORDER BY a
ORDER BY a, b
ORDER BY a, b, c
ORDER BY a DESC, b DESC, c DESC

如果WHERE使用素引的最左前缀定义为常量,则order by能使用索引

WHERE a = const ORDER BY b,c
WHERE a = const AND b = const ORDER BY c
WHERE a = const ORDER BY b, c
WHERE a = const AND b > const ORDER BY b, c

不能使用索引进行排序的情况

ORDER BY a ASC, b DESC, c DESC //排序不—致
WHERE g = const ORDER BY b, c //产丢失a索引
WHERE a = const ORDER BY c //产丢失b索引
WHERE a = const ORDER BY a, d //d不是素引的一部分
WHERE a in (…) ORDER BY b, c //对于排序来说,多个相等条件也是范围查询

GroupBy优化

GroupBy优化(和order by差不多)

group by实质是先排序后进行分组,遵照索引建的最佳左前缀。
当无法使用索引列,增大max_length_for_sort_data参数的设置 + 增大sort_buffer_size参数的设置。
where高于having,能写在where限定的条件就不要去having限定了。

慢查询日志

MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析
默认情况下,MySQL数据库没有开启慢查询日速,需要我们手动来设置这个参数。

一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。

通过如下命令进行操作

查看日志是否开启

SHOW VARIABLES LIKE '%slow_query_log%';

image-20220204210832085

开启 set global slow_query_log=1,只对当前数据库生效,默认关闭的
结果如下

永久生效
修改配置文件my.cnf
[mysqld]下增加或修改参数slow_query_log和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件

slow_query_log =1
slow_query_log_file=/var/lib/manongyanjiuseng-slow.log

关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)

什么样的数据会放到慢查询日志

需要设置一个多长的时间段就会放到日志中
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒

命令:

SHOW VARIABLES LIKE 'long_query_time%';

运行时间正好等于long_query_time的情况,并不会被记录下来,需大于

设置阈值时间后还需要重启才可以生效

set global long_query_time=3;

查询当前系统中有多少条慢查询记录

show global status like '%Slow_queries%';

如果在配置文件中设置阈值
具体配置如下

[mysqld]下配置:

slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE;

==结合工具进行分析mysqldumpslow ==

在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysql dump slow。

查看mysqldumpslow的帮助信息,mysqldumpslow –help。

工作常用参考

得到返回记录集最多的10个SQL ,
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
得到访问次数最多的10个SQL,
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句,
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
另外建议在使用这些命令时结合│和more 使用,否则有可能出现爆屏情况,
mysqldumpslow -s r-t 10 /ar/lib/mysql/atguigu-slow.log | more

3.5 批量插入数据脚本
为了更好的展示
先建立一张表

create database bigData;
use bigData;

CREATE TABLE dept(
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
    dname VARCHAR(20)NOT NULL DEFAULT "",
    loc VARCHAR(13) NOT NULL DEFAULT ""
)ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE emp(
    id int unsigned primary key auto_increment,
    empno mediumint unsigned not null default 0,
    ename varchar(20) not null default "",
    job varchar(9) not null default "",
    mgr mediumint unsigned not null default 0,
    hiredate date not null,
    sal decimal(7,2) not null,
    comm decimal(7,2) not null,
    deptno mediumint unsigned not null default 0
)ENGINE=INNODB DEFAULT CHARSET=utf8;

开启了可以创建存储函数的权限,主要是这个功能

设置参数log_bin_trust_function_creators

show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;

这是在终端上输入的
但是重启后会失效

如果要配置永久的,需要在配置文件上配置

windows下my.ini[mysqld]加上

log_bin_trust_function_creators=1

linux下/etc/my.cnf 下my.cnf[mysqld]加上

log_bin_trust_function_creators=1

创建函数,保证每条数据都不同

随机产生字符串

delimiter $$ # 两个 $$ 表示结束
create function rand_string(n int) returns varchar(255)
begin
    declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyz';
    declare return_str varchar(255) default '';
    declare i int default 0;
    while i < n do
        set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
        set i=i+1;
    end while;
    return return_str;
end $$

执行完语句后,在终端上输入select rand_string(2)$$;,要以$$结尾

随机产生部门编号

delimiter $$
create function rand_num() returns int(5)
begin
    declare i int default 0;
    set i=floor(100+rand()*10);
    return i;
end $$

执行完语句后,在终端上输入select rand_num()$$,要以$$结尾

创建存储过程,创建往emp表中插入数据的存储过程

delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
    declare i int default 0;
    set autocommit = 0;
    repeat
        set i = i+1;
        insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6),'salesman',0001,curdate(),2000,400,rand_num());
        until i=max_num
        end repeat;
    commit;
end $$

创建往dept表中插入数据的存储过程

delimiter $$
create procedure insert_dept(in start int(10),in max_num int(10))
begin
    declare i int default 0;
    set autocommit = 0;
    repeat
        set i = i+1;
        insert into dept(deptno,dname,loc) values((start+i),rand_string(10),rand_string(8));
        until i=max_num
        end repeat;
    commit;
end $$

调用存储过程
往dept表中插入数据

DELIMITER ;
CALL insert_dept(100, 10);

往emp表中插入50万数据

DELIMITER ;
CALL insert_emp(100001, 500000);

Show Profile

Show Profile是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量

默认情况下,参数处于关闭状态,并保存最近15次的运行结果。

查看当前状态是否开启了,

show variables like 'profiling';

开启 ,

set profiling=on;

image-20220206120852097

通过执行show profiles;

  • 诊断SQL,show profile cpu,block io for query 上一步前面的问题SQL数字号码;
    例如

    show profile cpu,block io for query 3;

    image-20220206120949041

具体的参数介绍

ALL:显示所有的开销信息。

BLOCK IO:显示块lO相关开销。
CONTEXT SWITCHES :上下文切换相关开销。
CPU:显示CPU相关开销信息。
IPC:显示发送和接收相关开销信息。
MEMORY:显示内存相关开销信息。
PAGE FAULTS:显示页面错误相关开销信息。
SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
SWAPS:显示交换次数相关开销的信息。
在查询该文件的时候如果出现了这些一定要特别注意

不理想字段

converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
Creating tmp table 创建临时表,拷贝数据到临时表,用完再删除
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!
locked

全局查询日志

General log默认不开启的原因有两个:

日志将会非常大,对磁盘是一个很大的压力。因为所有的操作都会被记录下来。
对MySQL数据的性能有一定的影响。

不要在生产环境开启这个功能。

  • 开启

    set global general_log=1

    #记录日志文件的路径

    general_log_file=/path/logfile

    #输出格式

    log_output=FILE

查看状态是否开启,show variables like ‘general_log’;

set global general_log=1;
set global log_output='TABLE';

log_output=‘FILE‘表示将日志存入文件,默认值是‘FILE‘

log_output=‘TABLE‘表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中.

所编写的sql语句,将会记录到mysql库里的geneial_log表,可以用下面的命令查看:

select * from mysql.general_log;

查出来会有输出语句

image-20220206211546434

锁机制

从读写来分

读锁:可共同读

写锁:不可共同写,在任务完成前,会阻断其他读锁和写锁

表锁:

image-20220206223240669

image-20220206224230948

image-20220206224728944

innoDB索引失效行锁会切换为表锁

间隙锁的危害

什么是间隙锁?

当我们用范围条件而不是相等条件检索数据,并请求共享或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范文内但并不存在的记录,叫做”间隙(GAP)”。

InnoDB也会对这个”间隙”加锁,这种锁的机制就是所谓的”间隙锁”。

间隙锁的危害

因为Query执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值不存在。

间隙锁有一个比较致命的缺点,就是当锁定一个范围的键值后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会対性能造成很大的危害。

image-20220207212800265

行锁

SELECT .....FOR UPDATE`在锁定某一行后,其他写操作会被阻塞,直到锁定的行被`COMMIT

行锁分析

mysql> SHOW STATUS LIKE 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 11131 |
| Innodb_row_lock_time_avg      | 11131 |
| Innodb_row_lock_time_max      | 11131 |
| Innodb_row_lock_waits         | 1     |
+-------------------------------+-------+
5 rows in set (0.00 sec)
  • Innodb_row_lock_current_waits:当前正在等待锁定的数量。
  • Innodb_row_lock_time:从系统启动到现在锁定总时间长度(重要)。
  • Innodb_row_lock_time_avg:每次等待所花的平均时间(重要)。
  • Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间。
  • Innodb_row_lock_waits:系统启动后到现在总共等待的次数(重要)。

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化策略。

主从复制

https://www.cnblogs.com/phpstudy2015-6/p/6485819.html

八股

1657030880792

MySQL采用页存储数据和索引,索引有三层就要有3次io。

查询数据库时,不论读一行,还是读多行,都是将这些行所在的整页数据加载,然后在内存中匹配过滤出最终结果。

buffer pool

存储空白页的链表——-free链表

从磁盘加载一页数据到buffer pool——–》内部组成为成对的控制块和缓存页———–》为了记录哪些缓存页为空———–》取对应的控制块组成free链表———》当需要加载到内存数据时,就从free链表中取一个空闲的缓存页,并且把相应的控制块从free链表中移除

存储脏页(修改过数据)的链表——–flush链表

缓存不够—————LRU链表

预读机制导致读进来不用,或者全表扫描导致只读一次(劣币驱逐良币)——————-》lru分区(young old)

1657119906580

​ 针对预读机制: 规定当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时,该缓存页对应的控制块会被放到old区域的头部

​ 针对全表扫描:对old区的缓存页进行第一次访问时就记录访问时间到控制块,如果后续访问时间与第一次访问时间的时间间隔在一个规定范围内,该数据页就不会移动到young区,否则就移动到young区的头部。

我们可以通过指定 innodb_buffer_pool_instances 来控制 Buffer Pool 实例的个数,每个 Buffer Pool 实
例中都有各自独立的链表,互不干扰。

自 MySQL 5.7.5 版本之后,可以在服务器运行过程中调整 Buffer Pool 大小。每个 Buffer Pool 实例由若
干个 chunk 组成,每个 chunk 的大小可以在服务器启动时通过启动参数调整。

日志

redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗

posted @ 2022-07-19 11:43  山野村夫01  阅读(108)  评论(0编辑  收藏  举报