mysql2

内容回顾

1、MySQL8默认字符集utf8

2、sql_mode

  • 编写分组语句,select后面出现分组字段和聚合函数

3、MySQL存储引擎

4、练习sql语句

5、MySQL索引

  • 高效获取数据的数据结构

  • 索引缺点:占用空间、维护成本高

5、树

  • 二叉树

  • 二叉搜索树

  • 平衡二叉树

  • 多叉树:树高度和io次数相关的,树高度越低,io次数越少

  • B-Tree 和 B+Tree

6、InnoDB聚簇和非聚簇

7、覆盖索引

今天内容

1、MyISAM中的索引

  • MySQL数据库支持多种存储引擎,默认存储引擎是InnoDB,还有很多其他存储引擎

  • MyISAM引擎使用 B+Tree 作为索引结构叶子节点存放的是数据记录的地址

  • MyISAM索引都是非聚簇,但是和InnoDB里面非聚簇不一样的

  • 执行概述:没有严格区分主键和非主键,根据条件(主键或者非主键)找到对应地址,再根据地址查询其他数据(回表)

2、MyISAM与InnoDB对比

  • InnoDB支持事务、外键、行锁;MyISAM不支持事务、不支持外键、使用表锁

  • 从其他角度比较:

1、InnoDB的数据索引在一个文件中,而MyISAM索引和数据在不同的文件里面:

2、InnoDB中主键索引是聚簇索引,其他索引是非聚簇索引 。MyISAM索引都是非聚簇。

3、InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。

4、MyISAM叶子节点记录的是数据的地址。InnoDB叶子节点存储数据完整的值。

3、索引操作

  • 索引创建语句

  • create index 索引名称 on 表名称(字段1 , 字段2....)

复制代码
# 创建表时候,设置主键,mysql自动为主键创建索引
​
# 创建索引语句
# 单值索引
CREATE INDEX  idx_name ON customer1(customer_name);  
​
# 复合索引
CREATE INDEX  idx_no_name ON customer1(customer_no,customer_name);

4、索引的使用场景(背)

哪些情况适合创建索引:

  • 频繁作为WHERE查询条件的字段

  • 经常GROUP BY 和 ORDER BY的列

  • 字段的值有唯一性的限制

  • DISTINCT去重字段需要创建索引

  • 多表JOIN时,对连接字段创建索引

  • 使用字符串前缀创建索引

  • 使用频繁的列,放到联合索引的左侧 (最佳左前缀法则)

哪些情况不要创建索引:

  • WHERE、GROUP BY 、ORDER BY里用不到的字段不创建索引

  • 表的数据记录太少

  • 避免对经常增删改的表创建索引

  • 不要定义冗余或重复的索引

5、索引优化

性能分析工具EXPLAIN

  • explain:查看SQL执行计划:使用EXPLAIN关键字可以模拟优化器执行SQL查询语句

  • 使用explain分析sql性能如何的

  • 用法:explain + sql语句

复制代码
explain select * from  customer1
  • 返回结果,各个字段表示性能指标,根据这些指标分析处理sql语句性能

id: 语句执行顺序,得到结果查询趟数

**type:**性能类型,ALL表示全表检索,效率最低

system > const > eq_ref > ref > range > index > ALL`

SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 const级别。(阿里巴巴开发手册要求)

possible_keys 和 key : possible_keys 可能使用的索引,key实际使用的索引

**key_len:**查看索引使用充分程度

  1. 先看索引上字段的类型+长度。比如:int=4 ; varchar(20) =20 ; char(20) =20

  2. 如果是varchar或者char这种字符串字段,视字符集要乘不同的值,比如utf8要乘 3(MySQL5.7),如果是utf8mb4要乘4,GBK要乘2

  3. varchar这种动态字符串要加2个字节

  4. 允许为空的字段要加1个字节

-- 创建索引 (4 + 4 + 20*3+2) + 3 = 73 CREATE INDEX idx_age_deptid_name ON emp(age,deptid,name);

rows: 查询出结果检索多少行数据,肯定越少越好

**filtered:**命中率

**Extra:**额外,有些信息在前面字段无法显示,在这个字段显示

准备工作-添加几十万数据(了解)

复制代码
CREATE TABLE `dept` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `deptName` VARCHAR(30) DEFAULT NULL,
    `address` VARCHAR(40) DEFAULT NULL,
    ceo INT NULL ,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
​
CREATE TABLE `emp` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `empno` INT NOT NULL ,
    `name` VARCHAR(20) DEFAULT NULL,
    `age` INT(3) DEFAULT NULL,
    `deptId` INT(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;

使用存储过程+函数添加(了解)

复制代码
# 创建表
CREATE TABLE `dept` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `deptName` VARCHAR(30) DEFAULT NULL,
    `address` VARCHAR(40) DEFAULT NULL,
    ceo INT NULL ,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
​
CREATE TABLE `emp` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `empno` INT NOT NULL ,
    `name` VARCHAR(20) DEFAULT NULL,
    `age` INT(3) DEFAULT NULL,
    `deptId` INT(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
​
# 创建函数
-- 查看mysql是否允许创建函数:
SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
-- 命令开启:允许创建函数设置:(global-所有session都生效)
SET GLOBAL log_bin_trust_function_creators=1; 
​
-- 随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN    
    DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    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 $$
​
-- 用于随机产生区间数字
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN   
 DECLARE i INT DEFAULT 0;  
 SET i = FLOOR(from_num +RAND()*(to_num -from_num+1));
RETURN i;  
END$$
​
​
# 创建存储过程
-- 插入员工数据
DELIMITER $$
CREATE PROCEDURE  insert_emp(START INT, max_num INT)
BEGIN  
    DECLARE i INT DEFAULT 0;   
    #set autocommit =0 把autocommit设置成0  
    SET autocommit = 0;    
    REPEAT  
        SET i = i + 1;  
        INSERT INTO emp (empno, NAME, age, deptid ) VALUES ((START+i) ,rand_string(6), rand_num(30,50), rand_num(1,10000));  
        UNTIL i = max_num  
    END REPEAT;  
    COMMIT;  
END$$
​
-- 插入部门数据
DELIMITER $$
CREATE PROCEDURE insert_dept(max_num INT)
BEGIN  
    DECLARE i INT DEFAULT 0;   
    SET autocommit = 0;    
    REPEAT  
        SET i = i + 1;  
        INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));  
        UNTIL i = max_num  
    END REPEAT;  
    COMMIT;  
END$$
​
-- 批量删除某个表上的所有索引 建议使用可视化工具执行
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE ct INT DEFAULT 0;
    DECLARE _index VARCHAR(200) DEFAULT '';
    DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY'  ;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=2 ;      
    OPEN _cur;
        FETCH _cur INTO _index;
        WHILE  _index<>'' DO 
            SET @str = CONCAT("drop index ",_index," on ",tablename ); 
            PREPARE sql_str FROM @str ;
            EXECUTE sql_str;
            DEALLOCATE PREPARE sql_str;
            SET _index=''; 
            FETCH _cur INTO _index; 
        END WHILE;
    CLOSE _cur;
END$$
​
# 调用存储过程
-- 执行存储过程,往dept表添加1万条数据
CALL insert_dept(10000); 
​
-- 执行存储过程,往emp表添加50万条数据,编号从100000开始
CALL insert_emp(100000,500000); 

单表查询优化(背)

复制代码
-- 创建索引
CREATE INDEX idx_name ON emp(`name`);
​
EXPLAIN SELECT * FROM emp WHERE emp.age=12 and name='lucy';
​
EXPLAIN SELECT * FROM emp WHERE emp.name=123;
​
#### 单表查询优化
# 计算或者函数导致索引失效
EXPLAIN SELECT * FROM emp WHERE LEFT(emp.name,3) = 'abc';
​
# LIKE以%开头索引失效
EXPLAIN SELECT * FROM emp WHERE emp.name like '%abc%';
​
# 不等于(!= 或者<>)索引失效
EXPLAIN SELECT * FROM emp WHERE emp.name <> 'abc' ;
​
# is not null索引失效
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL;
​
# 类型转换导致索引失效
EXPLAIN SELECT * FROM emp WHERE name= 123;
​
# 最佳左前缀法则
CALL proc_drop_index("atguigudb","emp");
​
-- 创建索引                              (4  +  4   +    20*3+2) + 3 = 73
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,`name`);
-- 1. 先看索引上字段的类型+长度。比如:int=4 ; varchar(20) =20 ; char(20) =20 
-- 2. 如果是varchar或者char这种字符串字段,视字符集要乘不同的值,
--  比如utf8mb3要乘 3(MySQL5.7),如果是utf8mb4要乘4,GBK要乘2
-- 3. varchar这种动态字符串要加2个字节
-- 4. 允许为空的字段要加1个字节 
​
EXPLAIN SELECT * FROM emp WHERE emp.deptid=1 
                        AND emp.name = 'abcd' 
                        AND emp.age = 30;
-- EXPLAIN结果:
-- 索引查找的顺序为 age、deptid、name,匹配所有索引字段
​
​
​
EXPLAIN SELECT * FROM emp WHERE emp.age = 30 
                              AND emp.deptid=1 
                              AND emp.name = 'abcd';
-- EXPLAIN结果:
-- 索引查找的顺序为 age、deptid、name,匹配所有索引字段
​
​
​
EXPLAIN SELECT * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd';
-- EXPLAIN结果:
-- type: ALL, 执行了全表扫描
-- key_len: NULL, 索引失效
-- 索引查找的顺序为 age、deptid、name,查询条件中不包含age,无法使用整个索引
​
​
EXPLAIN SELECT * FROM emp WHERE  emp.name = 'abcd' and  emp.age=30
-- EXPLAIN结果:
-- key_len:5 只使用了age索引
-- 索引查找的顺序为 age、deptid、name,查询条件中不包含deptid,无法使用deptid和name索引
​
​
SHOW VARIABLES LIKE '%char%';
​
​
# 全值匹配,条件有几个字段,为这些字段创建索引,复合索引优于单值索引
​
# 索引中范围条件右边的列失效 
CALL proc_drop_index("atguigudb","emp");
​
create index idx_name on emp(age,deptId,`name`)
-- 把范围字段放到索引最右边
create index idx_name1 on emp(age,`name`,deptId)
​
EXPLAIN SELECT * FROM emp WHERE emp.age=30 
                AND emp.deptId>1000 
                AND emp.name = 'abc'; 

多表查询优化(背)

1、数据准备

复制代码
-- 分类
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
-- 图书
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
 
-- 插入16条记录
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
 
-- 插入20条记录
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

2、左外连接优化

复制代码
-- 创建索引
CREATE INDEX idx_class_card ON class(card);
-- 创建索引
CREATE INDEX idx_book_card ON book(card);
​
EXPLAIN SELECT * FROM class 
                 LEFT JOIN 
                      book 
                 ON class.card = book.card;
                 
# 左外连接创建索引规则
## 左外连接时候,左表是驱动表,右表被驱动表  
--   左表class:驱动表、右表book:被驱动表
--   驱动表会全表检索,被驱动只是关联数据
​
## 在被驱动表创建索引
​
## 把数据量少的表作为驱动表(小表作为驱动表)

3、内连接优化

  • 内连接查询,MySQL自动优化,优先把数据量少的表作为驱动表,这个过程自动完成的

其他优化

  • 子查询优化

因为子查询会有2趟操作,效率很低,不推荐使用子查询,使用关联查询替代子查询

6、慢查询日志

  • **作用:**一种日志记录,查看哪些SQL超出了我们的最大忍耐时间值。

比如设置sql语句最大运行时间5s,某些sql语句运行时间超过了5s,把超过5s语句记录在日志里面

  • **场景:**经常使用在线上环境中,排查接口中sql语句运行慢的问题

  • 使用过程:

第一步,MySQL慢查询日志默认没有开启的,如果使用需要开启慢查询日志

第二步,设置sql语句可以接受运行时间,比如3s

第三步,开始测试,执行程序(语句),当某些sql语句运行时间超过3s,这些语句记录到慢查询日志里面

第四步,如果找到运行慢的sql语句,可以使用explain进行分析,进行调优

7、View视图

  • 将一段查询sql封装为一个虚拟的表。

  • 这个虚拟表只保存了sql逻辑,不会保存任何查询结果。

  • 语法 CREATE VIEW 视图名称 AS 查询sql语句

复制代码
SELECT emp.name, ceo.name AS ceoname 
FROM t_emp emp
LEFT JOIN t_dept dept ON emp.deptid = dept.id 
LEFT JOIN t_emp ceo ON dept.ceo = ceo.id;
​
-- 使用视图
select * from v_ceo

8、读写分离和分库分表理论

数据库优化方案

问题:

哪些方法可以进行数据库调优?

解决方案:

1、创建索引,按照规则进行创建

  • 频繁作为WHERE查询条件的字段

  • 经常GROUP BY 和 ORDER BY的列

  • 字段的值有唯一性的限制

  • DISTINCT去重字段需要创建索引

  • 多表JOIN时,对连接字段创建索引

  • 使用字符串前缀创建索引

  • 使用频繁的列,放到联合索引的左侧 (最佳左前缀法则)

2、对sql语句进行优化,包含单表和多表优化

  • 单表
  • 多表

-- 左外连接,左表驱动表,右表被驱动表,在被驱动表创建索引,把小表作为驱动表

-- 内连接,MySQL自动选择小表作为驱动表

-- 使用关联查询替代子查询

3、搭建数据库集群,采用主从复制+读写分离机制进行优化

4、如果数据过多,考虑进行分库分表(数据库分片)

-- 按照阿里巴巴分库分表原则:单表500W行 或者 单表容量达到2GB

读写分离(主从复制)

概述

  • 搭建数据库主从环境,需要有且只有一台主机,可以有多台从机,比如一主两从

  • 读写分离:主机里面做写操作,从机里面做读操作

  • 要添加数据,在主机里面进行添加,主机添加完成之后,同步到多个从机里面

  • 主从复制有缺陷:数据延迟问题

-- 在主机里面添加完成之后,马上到从机里面去读取数据,可能读取不到,因为主机数据可能还没有同步过去

-- 解决方案:

第一种,在主机里面写完之后,等一会再去读

第二种,主机里面写马上去读,在主机写完成之后就在主机进行读操作

数据库分片

  • 数据库分片称为分库分表

垂直分片

  • 在设计表时候,设计有几个数据库有几个表

垂直分库

垂直分表

  • 把一张表里面一部分字段创建一张表,另一部分字段创建另外一张表

  • 比如用户基本表和用户详情表,商品基本表和商品详情表

水平分片

水平分表:针对数据存放地方

  • 比如有一张表有1w条记录,进行水平拆分,可以创建两张表,每张放其中5000条记录

  • 水平分表规则:取模(求余)

-- 根据id % 表数量

-- 假如2张表,任何数字对2取模,值 0 1,0放到第一张表,1 放到第二张表

阿里巴巴Java开发手册:

【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表

  • 实现读写分离和分库分表可以有两种方式实现:

第一种,编写程序代码实现

第二种,使用数据库中间件实现,比如ShardingSphere、MyCat

  • ShardingSphere主要包含两个产品
  • 程序代码封装:ShardingSphere-JDBC

  • 中间件封装:ShardingSphere-Proxy

相关推荐
木易 士心2 小时前
会见SDK文档
android
Co_Hui2 小时前
Android:多线程
android
赏金术士2 小时前
Kotlin 协程面试题大全(Android 高频版)
android·开发语言·kotlin
y小花3 小时前
DRM-Direct Rendering Manager
android·drm
特立独行的猫a3 小时前
鸿蒙 PC 命令行工具迁移实战 · 直播PPT
android·华为·harmonyos·vcpkg·三方库移植·鸿蒙pc
Riu_Peter3 小时前
【技术】Docker 部署 MySQL
mysql·adb·docker
Co_Hui3 小时前
Android:通知 基本使用
android
古怪今人3 小时前
Android Studio的安装及配置 创建项目编译、运行、调试、打包安装包
android·ide·android studio
程序员陆业聪5 小时前
DNS优化实战:从运营商DNS到HttpDNS的进化之路
android