MySQL 进阶

1 存储引擎

mysql 体系结构

image-20220415085036522

  • 连接层
    • 最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
  • 服务层
    • 第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
  • 引擎层
    • 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。索引是在引擎层实现的。
  • 存储层
    • 主要是将数据存储在文件系统之上,并完成与存储引擎的交互。

存储引擎简介

use test_06_engine;
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `money` int(11) DEFAULT NULL COMMENT '余额',
  PRIMARY KEY (`id`)
) COMMENT='账户表';

insert into account (name, money)
VALUES ('Alice', '1000'),
       ('Bob', '2000'),
       ('Cindy', '3000'),
       ('Kite', '6000');

show create table account;

# CREATE TABLE `account` (
#   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
#   `name` varchar(10) DEFAULT NULL COMMENT '姓名',
#   `money` int(11) DEFAULT NULL COMMENT '余额',
#   PRIMARY KEY (`id`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='账户表'

mysql 默认引擎是 InnoDB

show engines;

关注 InnoDB 和 MyISAM、Memory

image-20220415090903350

创建表指定存储引擎

create table xxx (...) engine = [ InnoDB | MyISAM | Memory ]

# 创建 my_isam 指定 引擎为 MyISAM
create table my_isam
(
    id   int(3) primary key auto_increment comment 'id',
    name varchar(20) comment '姓名'
) engine = MyISAM ;


# 创建 my_isam 指定 引擎为 MyISAM
create table my_memory
(
    id   int(3) primary key auto_increment comment 'id',
    name varchar(20) comment '姓名'
) engine = Memory ;

查看可以选中表 --> goto DDL

存储引擎特点

InnoDB

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

  • 特点

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

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

    在mysql安装目录/data/数据库名 文件夹下有innodb引擎的数据表都有同名文件.ibd (例:account.ibd), 在该目录下打开cmd, ibd2sdi account.ibd

image-20220415093259146

ibd 即为表空间

MyISAM

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

  • 特点
    • 不支持事务,不支持外键
    • 支持表锁,不支持行锁
    • 访问速度快
  • 文件
    • xxx.sdi:存储表结构信息
    • XXX.MYD:存储数据
    • Xxx.MYI:存储索引

Memory

Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用。

  • 特点
    • 内存存放
    • hash索引(默认)
  • 文件
    • xxx.sdi:存储表结构信息

对比

特点 InnoDB MyISAM Memory
存储限制 64TB
事务安全 支持 - -
锁机制 行锁 表锁 表锁
B+tree索引 支持 支持 支持
Hash索引 - - 支持
全文索引 支持(5.6版本之后) 支持 -
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持 - -

存储引擎选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB
    • 是Mysql的默认存储引。支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致
      性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
  • MyISAM
    • 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那
      么选择这个存储引擎是非常合适的。(日志、足迹
    • 被nosql类型数据库 MongoDB取代
  • MEMORY
    • 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表
      无法缓存在内存中,而且无法保障数据的安全性。
    • 被nosql类型数据库 redis 取代
  • INNODB:存储业务系统中对于事务、数据完整性要求较高的核心数据。
  • MyISAM:存储业务系统的非核心事务。

2 索引

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

索引搜索演示(以二叉树为例)

image-20220415150139245

优势 劣势
提高数据检索的效率,降低数据库的IO成本 索引列也是要占用空间的
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:

索引结构 描述
B+Tree索引 最常见的索引类型,大部分引擎都支持B+树索引
Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R-tree(空间索引) 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES
索引 InnoDB MYISAM Memory
B+tree索引 支持 支持 支持
Hash 索引 不支持 不支持 支持
R-tree 索引 不支持 支持 不支持
Full-text 5.6版本之后支持 支持 不支持

二叉树缺点:顺序插入时,会形成一个链表,查询性能大大降低。大数据量情况下,层级较深,检索速度慢。

红黑树:大数据量情况下,层级较深,检索速度慢。

B+Tree

以一颗最大度数(max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):

image-20220415152626344

B+tree

image-20220415153333904

MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能

image-20220415153631464

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

  • 相对于二叉树,层级更少,搜索效率高;
  • 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
  • 相对Hash索引,B+tree支持范围匹配及排序操作;

Hash

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

  • Hash索引特点
    • Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,…)
    • 无法利用索引完成排序操作
    • 查询效率高,通常只需要一次检索就可以了,效率通常要高于B+tree索引
  • 在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。

索引分类

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
常规索引 快速定位特定数据 可以有多个
全文索引 全文索引查找的是文本中的关键词,而不是比较索引中的值 可以有多个 FULLTEXT
分类 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

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

image-20220415155705345

回表查询

image-20220415155950147

思考:

select * from user where id = 10;select * from user where name ='Arm' ;

哪个查询效率更高?备注:id为主键,name字段创建的有索引

select * from user where id = 10;

注意: 都是 B+Tree 结构

InnoDB主键索引的B+tree高度为多高呢?

image-20220415165026620

索引语法

  • 创建索引
    • CREATE [UNIQUE|FULLTEXT] INDEX index_name ON table_name (index_col_name,...);
  • 查看索引
    • SHOW INDEX FROM table_name ;
  • 删除索引
    • DROP INDEX index_name ON table_name ;
 use test_07_index;
 

 insert into tb_user (id, name, phone, email, profession, age, gender, status, createtime)
 values 
 (1, '吕布', '17799990000', '17799990000@123.com', '软件工程', '23', '1', '6', '2000-02-02 00:00:00'),
 (2, '曹操', '17799990001', '17799990001@123.com', '通讯工程', '33', '1', '0', '2001-02-02 00:00:00'),
 (3, '赵云', '17799990002', '17799990002@123.com', '英语', '34', '1', '2', '2002-02-02 00:00:00'),
 (4, '孙悟空', '17799990003', '17799990003@123.com', '工程造价', '54', '1', '0', '2003-02-02 00:00:00'),
 (5, '花木兰', '17799990004', '17799990004@123.com', '软件工程', '23', '2', '1', '2004-02-02 00:00:00'),
 (6, '大乔', '17799990005', '17799990005@123.com', '舞蹈', '22', '2', '0', '2005-02-02 00:00:00'),
 (7, '露娜', '17799990006', '17799990006@123.com', '应用数学', '24', '2', '0', '2006-02-02 00:00:00'),
 (8, '程咬金', '17799990007', '17799990007@123.com', '化工', '38', '1', '5', '2007-02-02 00:00:00'),
 (9, '项羽', '17799990008', '17799990008@123.com', '金属材料', '43', '1', '0', '2008-02-02 00:00:00'),
 (10, '白起', '17799990009', '17799990009@123.com', '机械工程及其自动化', '27', '1', '2', '2009-02-02 00:00:00'),
 (11, '韩信', '17799990010', '17799990010@123.com', '无机非金属材料工程', '27', '1', '0', '2010-02-02 00:00:00'),
 (12, '荆轲', '17799990011', '17799990011@123.com', '会计', '29', '1', '0', '2011-02-02 00:00:00'),
 (13, '兰陵王', '17799990012', '17799990012@123.com', '工程造价', '44', '1', '1', '2012-02-02 00:00:00'),
 (14, '狂铁', '17799990013', '17799990013@123.com', '应用数学', '43', '1', '2', '2013-02-02 00:00:00'),
 (15, '貂蝉', '17799990014', '17799990014@123.com', '软件工程', '40', '2', '3', '2014-02-02 00:00:00'),
 (16, '妲己', '17799990015', '17799990015@123.com', '软件工程', '31', '2', '0', '2015-02-02 00:00:00'),
 (17, '芈月', '17799990016', '17799990016@123.com', '工业经济', '35', '2', '0', '2016-02-02 00:00:00'),
 (18, '嬴政', '17799990017', '17799990017@123.com', '化工', '38', '1', '1', '2017-02-02 00:00:00'),
 (19, '狄仁杰', '17799990018', '17799990018@123.com', '国际贸易', '30', '1', '0', '2018-02-02 00:00:00'),
 (20, '安琪拉', '17799990019', '17799990019@123.com', '城市规划', '51', '2', '0', '2019-02-02 00:00:00'),
 (21, '典韦', '17799990020', '17799990020@123.com', '城市规划', '52', '1', '2', '2020-02-02 00:00:00'),
 (22, '廉颇', '17799990021', '17799990021@123.com', '土木工程', '19', '1', '3', '2021-02-02 00:00:00'),
 (23, '后羿', '17799990022', '17799990022@123.com', '城市园林', '20', '1', '0', '2022-02-02 00:00:00'),
 (24, '姜子牙', '17799990023', '17799990023@123.com', '工程造价', '29', '1', '4', '2023-02-02 00:00:00');
 
 show index from tb_user;
 # show index from tb_user\G;    # linux 可以使用这一指令将一行数据转化为一列
 
 
 # 创建 普通索引 index
 # 1.name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
 create index id_user_name on tb_user (name);
 
 show index from tb_user;
 
 # 创建 unique 索引
 # 2.phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引
 create unique index idx_user_phone on tb_user (phone);
 
 # 创建 联合索引 profession age status
 # 3. 为profession、age、status创建联合索引
 create index idx_pro_age_sta on tb_user (profession, age, status);
 
 # 4.为email建立合适的索引来提升查询效率
 create index idx_user_email on tb_user (email);
 
 show index from tb_user;
 # 删除索引
 drop index id_user_name on tb_user;

SQL 性能分析

SQL 执行频率

MySQL客户端连接成功后,通过showsession statusshow global status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:

show global status like 'Com_______';

image-20220415191422980

慢日志查询

慢日志记录了所有执行时间超过指定参数(long_query_time, 单位:秒, 默认 10 秒) 的所有SQL 语句的日志

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

# 开启MySQL慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2s

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,

查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log

profile

show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持
profile操作

# 查看是否支持 profile 操作
select @@have_profiling;

# 查看profiling状态
select @@profiling;
set profiling = 0;      # 默认值(关闭)
set profiling = 1;
# set session profiling = 1;
# set global profiling = 1;


select * from tb_user where name = '吕布';
select * from tb_user where id = 3;

#查看开启profiling后上述每一条SQL的耗时基本情况
show profiles;

#查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query 90;

#查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query 90;

image-20220415195152990

image-20220415195043003

image-20220415194926230

explain 执行计划

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

#直接在select语句之前加上关键字explain/ desc
EXPLAN SELECT 字段列表 FROM 表名 WHERE 条件;

image-20220415200632061

id

select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。(如下:先执行s 在执行 sc 最后执行 c)

image-20220415202016706

image-20220321210429778

# 子查询 查询选了 mysql 课程的学生信息
select * from student s where s.id in (select student_id from student_course sc where sc.course_id = (select id from course c where c.name = 'MySQL'));

explain select * from student s where s.id in (select student_id from student_course sc where sc.course_id = (select id from course c where c.name = 'MySQL'));

image-20220415203741739

select_type

表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等

type

表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all

possible_key

显示可能应用在这张表上的索引,一个或多个

Кеу

实际使用的索引,如果为NULL,则没有使用索引

Key_len

表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好

rows

MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的

filtered

表示返回结果的行数占需读取行数的百分比,filtered的值越大越好

索引使用

验证索引效率

# 创建百万数据
CREATE TABLE million_test (
    id bigint   primary key auto_increment,
    name        VARCHAR(50)              DEFAULT '' COMMENT '用户昵称',
    email       VARCHAR(50)         NOT NULL COMMENT '用户邮箱',
    phone       VARCHAR(20)              DEFAULT '' COMMENT '手机号',
    gender      TINYINT(4) UNSIGNED      DEFAULT '0' COMMENT '性别(0:男:1:女)',
    password    VARCHAR(100)        NOT NULL COMMENT '密码',
    age         TINYINT(4)               DEFAULT '0' COMMENT '年龄',
    createtime  DATETIME                 DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

DELIMITER $$
CREATE FUNCTION mock_data()

returns  int
deterministic
begin
    declare num int default 1000000;
    declare i int default 0;
    while i < num do
        insert into million_test (name, email, phone, gender, password, age)
        VALUES
               (concat('用户',i),'1232321@163.com',
                concat('18',floor((rand()*999999999))),
                floor(rand()*2),UUID(),FLOOR(Rand()*100));
        set i = i + 1;
    end while;
    return i;
end;

select mock_data();

在未建立索引之前,select * from million_test where phone = '18100001319'; 耗时 0.48s

image-20220415215113883

针对字段创建索引create index idx_million_phone on million_test (phone) ;

然后再次执行相同的SQL语句,耗时 0.0007s

image-20220415215428202

扩展:尝试通过id 直接定位 select * from million_test where id = 256244; 耗时 0.0003s

image-20220415215205754

最左前缀法则

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

# 联合索引 idx_pro_age_sta

# 1
explain select * from tb_user where profession = '软件工程' and age = '31' and status = '0';
# explain select * from tb_user where age = '31' and status = '0' and profession = '软件工程';  # 结果同上
# 2
explain select * from tb_user where profession = '软件工程' /*and age = '31' and status = '0'*/;
# 3
explain select * from tb_user where profession = '软件工程' and age = '31' /*and status = '0'*/;
# 4 
explain select * from tb_user where profession = '软件工程' /*and age = '31' */and status = '0';
# 5
explain select * from tb_user where /*profession = '软件工程' and*/ age = '31' and status = '0';
possible_keys key key_len ref filtered
1 idx_pro_age_sta idx_pro_age_sta 92 const,const,const 100
2 idx_pro_age_sta idx_pro_age_sta 83 const 10
3 idx_pro_age_sta idx_pro_age_sta 88 const,const 100
4 idx_pro_age_sta idx_pro_age_sta 83 const 10
5 null null null null 4.17

1 三个索引都被使用

2 一个索引被使用(两个被注释的索引失效)

3 两个索引被使用(一个被注释的索引失效)

4 一个索引被使用(中间断开,两个索引失效)

5 索引失效(最左侧断开,索引全失效)

范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效

# 使用范围查询 age > 30 右侧索引失效
explain select * from tb_user where profession = '软件工程' and age > 30 and status = '0';
# age >= 30 索引都被使用
explain select * from tb_user where profession = '软件工程' and age >= 30 and status = '0';

# 所以一般情况下,尽量使用 >= 

索引列运算

不要在索引列上进行运算操作,索引将失效

explain select * from tb_user where phone = '17799990015';
# substring(phone,0,3) 对索引进行运算 索引失效
explain select * from tb_user where substring(phone,0,3) = '177';

字符串不加引号

字符串类型字段使用时,不加引号,索引将失效。

# 索引列不加 '' 也会导致索引失效
explain select * from tb_user where phone = 17799990015;

模糊查询

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效

# 注意是 like 不是 =

# 使用索引
explain select * from tb_user where profession like '软件%';
# 不使用索引
explain select * from tb_user where profession like '%软件';
explain select * from tb_user where profession like '%工程';

or 连接的条件

用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

# 都没有使用索引
explain select *from tb_user where id = 10 or age = 23;
explain select * from tb_user where phone = '17799990017' or age = 23;

# 要使用的话, 对没有索引的列添加索引

数据分布影响

如果MySQL评估使用索引比全表更慢,则不使用索引。

# 17799990000 < phone < 17799990023
# 不使用索引
explain select * from tb_user where phone >= '17799990000';
# 不使用索引
explain select * from tb_user where phone >= '17799990018';

# 使用索引
explain select * from tb_user where phone >= '17799990019';
# 使用索引
explain select * from tb_user where phone >= '17799990023';

## is null / is not null 会进行评估走不走索引
# 使用索引
explain select * from tb_user where profession is null;
# 不使用索引
explain select * from tb_user where profession is not null;

SQL 提示

SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的

use ignore force

# 已存在索引 idx_pro_age_sta
create index idx_user_profession on tb_user (profession);

show index from tb_user;

# idx_pro_age_sta,idx_user_profession  --> 使用 idx_pro_age_sta
explain select * from tb_user where profession = '软件工程';

# use 建议使用 idx_user_profession
explain select * from tb_user use index(idx_user_profession) where profession = '软件工程';
# force 强制使用 idx_user_profession
explain select * from tb_user force index(idx_user_profession) where profession = '软件工程';
# ignore 不使用 idx_pro_age_sta
explain select * from tb_user ignore index(idx_pro_age_sta) where profession = '软件工程';

覆盖索引

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *

# 使用索引 idx_pro_age_sta 执行计划 Extra : Using index
explain select profession from tb_user where profession='软件工程' and age=31 and status='0';
explain select id,profession from tb_user where profession='软件工程' and age=31 and status='0';
explain select id,profession,age,status from tb_user where profession='软件工程'and age=31 and status='0';

# 使用索引 idx_pro_age_sta 执行计划 Extra : null
explain select id,profession,age,status,name from tb_user where profession='软件工程'and age=31 and status='0';
explain select id, name from tb_user where profession='软件工程'and age=31 and status='0';
explain select * from tb_user where profession='软件工程' and age=31 and status='0';

执行计划 Extra :

  • using index condition:查找使用了索引,但是需要回表查询数据
  • using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
  • null : 需要回表查询数据

image-20220416104257534

image-20220416104332414

image-20220416104148565

思考

一张表,有四个字段(id,username,password,status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:

select id,username,password from tb_user where username ='itcast';

对 username,password 建立联合索引 idx_uname_pwd

前缀索引

当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

  • 语法
create index idx_xxx on table_name(column(n));
  • 前缀长度
    • 可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
      select count(distinct substring(email,1,5)) / count(*) from tb_user ;
# 计算选择性
select count(distinct email) / count(*)  '选择性' from tb_user ;

select count(distinct substring(email,1,11)) / count(*) '选择性' from tb_user ;

create index idx_email_11 on tb_user (email(11));
drop index idx_email_11 on tb_user;

# 使用了索引 idx_email_11
explain select * from tb_user where email = '17799990017@123.com';

image-20220416111505533

当选择性 < 1 时,会对返回来的 row进行判断,后续字符,如果一致,作为最终结果返回,否则访问下一个结点(图中为 9)

单列索引与联合索引

  • 单列索引:即一个索引只包含单个列。
  • 联合索引:即一个索引包含了多个列。

多条件联合查询,有多个单列索引时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。

在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引

# idx_user_name,idx_user_phone -->  idx_user_name
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';

create index idx_phone_name on tb_user (phone, name) ;

# idx_user_name,idx_user_phone,idx_phone_name  -->  idx_user_name
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';

# use idx_phone_name
explain select id, phone, name from tb_user use index (idx_phone_name) where phone = '17799990010' and name = '韩信';

联合索引情况

image-20220416113957250

索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引。
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

3 SQL优化

插入数据优化

  • 批量插入(500~1000)
  • 手动提交事务
start transaction;
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
insert into th_test values(4,/'Tom'),(5,'Cat'),(6,'Jerry');
insert into tb_test values(7,'Tom'),(8,'Cat'),(9,Jerry');
commit;
  • 主键顺序插入

  • 大批量插入数据使用 load

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

      #客户端连接服务端时,加上参数--local-infile
      mysql --local-infile -u root -p
      
      #设置全局参数local inflle为1,开启从本地加载文件导入数据的开关
      select @@local_infile;
      set global local_infile =1;
      
      #执行load指令将准备好的数据,加载到表结构中
      
      load data local infile '/root/test_load.csv' 
      into table 'tb_user' fields terminated by ',' lines terminated by '\n';
      

主键优化

数据组织方式

在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table lOT)

image-20220416154402867

页分裂

页可以为空,也可以填充一半,也可以填充100%。每个页包含了2~N行数据(如果一行数据过大,会行溢出),根据主键排列。

image-20220416154639607

主键乱序插入页分裂
image-20220416154917313
image-20220416155108036
image-20220416154935096

页合并

当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。

image-20220416160549589

image-20220416160610821

image-20220416160632341

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

主键设计原则

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

order by优化

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

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

#没有创建索引时,根据age,phone进行排序
explain select id,age,phone from tb_user order by age, phone;
#创建索引
create index idx_usersage_phone_aa on tb_user(age,phone);
# create index idx_user_age_phone_ad on tb_user(age, phone desc);       # 8.0
#创建索引后,根据age,phone进行升序排序
explain select id,age,phone from tb_user order by age, phone;
#创建索引后,根据age,phone进行降序排序
explain select id,age,phone from tb_user order by age desc, phone desc;
create table tb_user select * from test_07_index.tb_user;	# 复制表数据和结构
alter table tb_user add primary key (id);	# 增加主键

image-20220416164129940

创建索引后

image-20220416164104585

create index idx_user_age_phone_ad on tb_user(age, phone desc); # MySQL 8.0image-20220416165721408

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则

  • 尽量使用覆盖索引

  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)

  • 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)

    image-20220416170225164

group by优化

#删除掉目前的联合索引 idx_user_pro_age_sta
drop index idx_user_pro_age_sta on tb_user;

#执行分组操作,根据profession字段分组
explain select profession, count(*) from tb_user group by profession ;
# Using temporary

#创建索引
create index idx_user_pro_age_sta on tb_user(profession, age, status);

#执行分组操作,根据profession字段分组
explain select profession, count(*) from tb_user group by profession;
# Using index

#执行分组操作,根据 profession 和 age 字段分组
explain select profession,count(*) from tb_user group by profession, age;
# Using index

#执行分组操作,根据 age 字段分组
explain select age,count(*) from tb_user group by age, profession;
# Using index; Using temporary

explain select age,count(*) from tb_user where profession = '软件工程' group by age, profession;
# Using index
  • 在分组操作时,可以通过索引来提高效率
  • 分组操作时,索引的使用也是满足最左前缀法则

limit优化

select * from million_data limit 0,10;
# execution: 98 ms

select * from million_data limit 10000,10;
# execution: execution: 109 ms

select * from million_data limit 4990000,10;
# execution: 4 s 299 ms

select id from million_data order by id limit 4990000,10;
# execution: 1 s 465 ms

# select * from million_data where id in (select id from million_data order by id limit 4990000,10);
#  This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

# ! ! !
select * from million_data m, (select id from million_data order by id limit 4990000,10) a where m.id = a.id;
# execution: 1 s 670 ms

一个常见又非常头疼的问题就是limit 4990000,10,此时需要 MySQL 排序前 4990000 记录,仅仅返回 4990000-4990010 的记录,其他记录丢弃,查询排序的代价非常大。

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

select * from million_data m, (select id from million_data order by id limit 4990000,10) a where m.id = a.id;

count优化

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

# count(*)
select count(*) from tb_user;   # 24
# count(主键)
select count(id) from tb_user;  # 24
# count(email)
update tb_user set email = null where id = 3;
select count(email) from tb_user;   # 23
# count(字段)
select count(null) from tb_user;    # 0
# count(1)
select count(1) from tb_user;   # 24
select count(2) from tb_user;   # 24  注意,不是 48	(理解count() 非空即加一)
  • count(主键)
    • InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)。
  • count(字段)
    • 没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
    • 有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
  • count (1)
    • InnoDB引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。
  • count (*)
    • InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

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

update优化

id 有索引 --> 行锁

update tb_user set email = '1234567@126.com' where id = 3;	# id 有索引

image-20220416230948646

name 无索引 --> 表锁

update tb_user set email = 'jiangzy@126.com' where name = '姜子牙';	# name 没有索引

image-20220416231440870

name 添加索引 --> 行锁

image-20220416232442535

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

要避免行锁升级为表锁

小结

  1. 插入数据
    • insert:批量插入、手动控制事务、主键顺序插入
    • 大批量插入:load data local infile
  2. 主键优化
    • 主键长度尽量短、顺序插入AUTO INCREMENT UUID
  3. order by 优化
    • using index:直接通过索引返回数据,性能高
    • using filesort:需要将返回的结果在排序缓冲区排序
  4. group by 优化
    • 索引,多字段分组满足最左前缀法则
  5. limit 优化
    • 覆盖索引+子查询
  6. count 优化
    • 性能:count(字段)<count主键id)<count(1)× count(*)
  7. update 优化
    • 尽量根据主键/索引字段进行数据更新

4 视图/存储过程/触发器

4.1 视图

视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的

通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。

# 数据准备
create table student
(
    id   int(3) auto_increment primary key comment '主键ID',
    no   char(10)    null comment '学号',
    name varchar(20) null comment '姓名'
) comment '学生表';

INSERT INTO student (id, no, name)
VALUES
       (1, '20220001', 'Alan'),
       (2, '20220002', 'Bob'),
       (3, '20220003', 'Cindy'),
       (4, '20220004', 'David'),
       (5, '20220005', 'Kite');
# 创建视图

create /*or replace*/ view stu_v_1 as select id, name from student where id <= 10;
# 查询视图
show create view stu_v_1;

select * from stu_v_1;
select * from stu_v_1 where id < 3;

# 修改视图
create or replace view stu_v_1 as select id, name, no from student where id <= 10;

alter view stu_v_1 as select id, no from student where id <= 10;
# 删除视图
drop view /*if exists*/ stu_v_1;

检查选项

当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入,更新,删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:CASCADED和LOCAL,默认值为CASCADED

cascaded

image-20220417094813737

## cascaded

create or replace view stu_v_1 as select id, name from student where id <= 20;
create or replace view stu_v_2 as select id, name from stu_v_1 where id >= 10 with cascaded check option;

insert stu_v_1 values (7,'Tom1');   # Y
insert stu_v_2 values (7,'Tom1');   # N
insert stu_v_2 values (22,'Tom2');  # N     cascaded
insert stu_v_2 values (12,'Tom3');  # Y

create or replace view stu_v_3 as select id, name from stu_v_2 where id <= 15;

insert stu_v_2 values (17,'Tom1');   # Y
insert stu_v_3 values (7,'Tom1');   # N
insert stu_v_3 values (25,'Tom1');   # N
insert stu_v_3 values (14,'Tom2');  # Y
insert stu_v_3 values (18,'Tom3');  # Y


local

image-20220417094725146

## local

create or replace view stu_v_4 as select id, name from student where id <= 40;
create or replace view stu_v_5 as select id, name from stu_v_4 where id >= 30 with local check option;

insert stu_v_4 values (27,'Tom1');   # Y

insert stu_v_5 values (28,'Tom1');   # N
insert stu_v_5 values (42,'Tom2');  # Y
insert stu_v_5 values (35,'Tom3');  # Y

create or replace view stu_v_6 as select id, name from stu_v_5 where id <= 35;

insert stu_v_5 values (37,'Tom1');   # Y

insert stu_v_6 values (29,'Tom1');   # N
insert stu_v_6 values (43,'Tom1');   # Y
insert stu_v_6 values (38,'Tom2');  # Y
insert stu_v_6 values (33,'Tom3');  # Y

更新

要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:

  • 聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等)
  • DISTINCT
  • GROUP BY
  • HAVING
  • UNION或者 UNION ALL

视图作用

  • 简单
    • 视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
  • 安全
    • 数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据
  • 数据独立
    • 视图可帮助用户屏蔽真实表结构变化带来的影响。

案例

# 1 为了保证数据库表的安全性,开发人员在操作tb_user表时,只能看到的用户的基本字段,屏蔽手机号和邮箱两个字段。
create table tb_user select * from test_08_optimization.tb_user;

# 创建视图
create view t9_user_view as select id, name, profession,age,gender,status,createtime from tb_user;
# 查询操作
select * from t9_user_view;


# 2 查询每个学生所选修的课(三张表联查),这个功能在很多的业务中都有使用到,为了简化操作,定义一个视图。
create table student select * from test_07_index.student;
create table course select * from test_07_index.course;
create table student_course select * from test_07_index.student_course;

# 创建视图
create view t9_stu_course_view as (select s.id 'id', s.name 'name', c.name 'course' from student s, course c, student_course sc where s.id = sc.student_id and c.id = sc.course_id);
# 查询
select * from t9_stu_course_view;

通过视图简化了多表联查等复杂的操作

4.2 存储过程

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

存储过程思想上很简单,就是数据库SOL语言层面的代码封装与重用

特点

  • 封装,复用
  • 可以接收参数,也可以返回数据
  • 减少网络交互,效率提升

image-20220417102451049

基本语法

# 创建
create procedure p1()
begin
    select count(*) from student;
end;


# 在 linux 中
delimiter $$

create procedure p1()
begin
    select count(*) from student;
end $$

# 调用
call p1();

# 查看
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = 'test_09_view';

show create procedure p1;

# 删除
drop procedure if exists p1;

delimiter $$ delimiter ; 定义结束符

变量

系统变量

系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION,默认)。

  • 查看系统变量
SHOW [SESSION|GLOBAL] VARIABLES;			-- 查看所有系统变量
SHOW [SESSION]GLOBAL] VARIABLES LIKE'……';	-- 可以通过LIKE模糊匹配方式查找变量
SELECT @@[SESSION|GLOBAL]系统变量名;			-- 查看指定变量的值
  • 设置系统变量
SET[SESSION|GLOBAL]系统变量名=值;
SET@@[SESSION|GLOBAL]系统变量名=值;
SHOW GLOBAL VARIABLES;        -- 查看所有系统变量
SHOW SESSION VARIABLES;          -- 查看所有系统变量

SHOW GLOBAL VARIABLES LIKE'auto%'; -- 可以通过LIKE模糊匹配方式查找变量
SHOW SESSION VARIABLES LIKE'auto%';    -- 可以通过LIKE模糊匹配方式查找变量

# SELECT @@[SESSION|GLOBAL]系统变量名;
select @@autocommit;

# SET[SESSION|GLOBAL]系统变量名=值;
set session autocommit = 1;
set global autocommit = 1;
# 即使设置全局,在重启后也会恢复默认选项
  • 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
  • mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf中配置。

用户自定义变量

用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用“@变量名”使用就可以。其作用域为当前连接。

  • 赋值

    SET @var_name =expr [ @var_name =expr]...;
    SET @var_name :=expr [ @var_name :=expr]...;
    SELECT @var_name :=expr [ @var_name:=expr].…;
    SELECT 字段名 INTO @var_name FROM 表名;
    
  • 使用

    SELECT @var_name;
    
# 赋值
set @myname = 'Kite';
set @myage := 25;
set @mygender := '男', @myhobby := 'java';

select @mytest := 'test';
select count(*) into @mynum from student;

# 使用
select @myname, @myhobby,@mytest,@mynum;
select @myno;   # null

局部变量

局部变量是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN.….END块。

  • 声明 DECLARE变量名变量类型[DEFAULT ...];

变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。

  • 赋值

    SET变量名=值;
    SET变量名:=值;
    SELECT 字段名 INTO 变量名 FROM 表名…;
    
create procedure p2()
begin
    declare stu_count int default 0;
    select count(*) into stu_count from student;
    select stu_count;
end ;

call p2();

if 判断

create procedure p3()

begin
    declare score int default 0;
    declare result varchar(10);

    if score >= 85 then
        set result := '优秀';
    elseif score >= 60 then
        set result := '及格';
    else
        set result := '不及格';
    end if;

    select result;

end;


call p3();
drop procedure p3;

参数

类型 含义 备注
IN 该类参数作为输入,也就是需要调用时传入值 默认
OUT 该类参数作为输出,也就是该参数可以作为返回值
INOUT 既可以作为输入参数,也可以作为输出参数
CREATE PROCEDURE 存储过程名称(【IN/OUT/INOUT 参数名参数类型])
BEGIN
	SQL语句
END ;
-- 根据传入参数score,判定当前分数对应的分数等级,并返回。
-- score>=85分,等级为优秀。
-- score>=60分且score<85分,等级为及格。
-- score<60分,等级为不及格。

create procedure p4(in score int, out result varchar(10))
begin
    if score >= 85 then
        set result := '优秀';
    elseif score >= 60 then
        set result := '及格';
    else
        set result := '不及格';
    end if;
end;

drop procedure p4;

call p4(88, @result);
select @result;

-- 将传入的200分制的分数,进行换算,换算成百分制,然后返回。

create procedure p5(inout score double)
begin
    set score := score * 0.5;
end;

drop procedure p5;
set @score = 180;
call p5(@score);
select @score;

case

# 语法一
CASE case_value
	WHEN when_value1 THEN statement_list1
	[WHEN when_value2 THEN statement_list 2] ...
	[ELSE statement_list]
END CASE;

# 语法二
CASE
	WHEN search_condition1 THEN statement_list1
	[WHEN search_condition2 THEN statement_list2]...
	[ELSE statement_list]
END CASE;
create procedure season(in month int, out season varchar(5))

begin
    case
        when month between 1 and 3 then
            set season := '春';
        when month between 4 and 6 then
            set season := '夏';
        when month between 7 and 9 then
            set season := '秋';
        when month between 10 and 12 then
            set season := '冬';
        else
            SET season := '错误';
        end case;

    select concat('您输入的月份为:' , month, '所属的季节为:', season);

end;

call season(7,@season);

while

create procedure total (in n int)

begin
    declare total int default 0;

    while n > 0 do
        set total := total + n;
        set n := n - 1;
        end while;
    select total;
end;

call total(4);

repeat

create procedure fact(in n int)
begin
    declare tmp int default 1;
    repeat
        set tmp := tmp*n;
        set n := n - 1;
    until n <= 1
    end repeat;

    select tmp;
end;

call fact(5);

loop

LOOP实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。

  • leave:配合循环使用,退出循环。
  • iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
# 累加和
create procedure totalnum(in num int)
begin
    declare tmp int default 0;

    total : loop
        if num <= 0 then
            leave total;
        end if;

        set tmp := num + tmp;
        set num := num - 1;
    end loop total;

    select tmp;
end;

call totalnum(5);


# 累加偶数和
create procedure totalnum2(in num int)
begin
    declare tmp int default 0;
    total : loop
        if num <= 0 then
            leave total;
        end if;

        if (num % 2 = 1) then
            set num := num - 1;
            iterate total;
        end if;

        set tmp := num + tmp;
        set num := num - 1;
    end loop total;

    select tmp;
end;
call totalnum2(5);

cursor

游标(cURSOR)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和CLOSE。

  • 声明游标
  • DECLARE 游标名称 CURSOR FOR 查询语句;
  • 打开游标
  • OPEN 游标名称;
  • 获取游标记录
  • FETCH 游标名称 INTO变量[变量];
  • 关闭游标
  • CLOSE 游标名称;

之前的不足之处:

# create procedure p2()
# begin
#     declare stu_count int default 0;
#     select count(*) into stu_count from student;
#     select stu_count;
# end ;
#
# call p2();


create procedure p6()
begin
    declare stu_count int default 0;
    select * into stu_count from student;
    select stu_count;
end ;

call p6();   # The used SELECT statements have a different number of columns

根据传入的参数 uage ,来查询用户表 tb_user 中,所有的用户年龄小于等于 uage 的用户姓名 (name) 和专业 (profession) ,并将用户的姓名和专业插入到所创建的一张新表(id, name, profession)中。

create procedure p7(in uage int)
begin
    declare uname varchar(50);
    declare upro varchar(100);
    declare u_cursor cursor for select name,profession from tb_user where age <= uage;


    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
        id int primary key auto_increment,
        name varchar(50),
        profession varchar(100)
    );

    open u_cursor;
    while true do
        fetch u_cursor into uname, upro;
        insert into tb_user_pro values (null,uname, upro);
        end while;

        close u_cursor;
end;

call p7(40);    # 可以执行,但会报错 No data - zero rows fetched, selected, or processed 

条件处理程序

解决报错 No data - zero rows fetched, selected, or processed

create procedure p7_2(in uage int)
begin
    declare uname varchar(50);
    declare upro varchar(100);
    declare u_cursor cursor for select name,profession from tb_user where age <= uage;
   
   #!!!!!!!
    declare exit handler for SQLSTATE '02000' close u_cursor;


    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
        id int primary key auto_increment,
        name varchar(50),
        profession varchar(100)
    );

    open u_cursor;
    while true do
        fetch u_cursor into uname, upro;
        insert into tb_user_pro values (null,uname, upro);
        end while;

        close u_cursor;
end;
call p7_2(40);

条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。

declare handler_action handler for condition_value [ condition_value]... statement
# declare exit handler for SQLSTATE '02000' close u_cursor;

# handler_action
	CONTINUE:继续执行当前程序
	EXIT:终止执行当前程序
	
# condition_value

    SQLSTATE sqlstate_value:状态码,如 02000
    
    SQLWARNING:所有以O1开头的SQLSTATE代码的简写
    NOT FOUND:所有以O2开头的SQLSTATE代码的简写
    SQLEXCEPTION:所有没有被SQLWARNING或 NOT FOUND捕获的SQLSTATE代码的简写
declare exit handler for SQLSTATE '02000' close u_cursor;
declare exit handler for NOT FOUND close u_cursor;    # 同上

存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:

CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic...]
BEGIN
	-- SQL语句
	RETURN ..;
END;


# characteristic说明:
    DETERMINISTIC:相同的输入参数总是产生相同的结果
    NOSQL:不包含SQL语句。
    READSSQLDATA:包含读取数据的语句,但不包含写入数据的语句。
# 累加和
create function fun1(n int)
returns int deterministic
begin
    declare total int default 0;

    while n >= 0 do
        set total := total + n;
        set n := n - 1;
        end while;
    return total;
end;

select fun1(10);

4.3 触发器

触发器是与表有关的数据库对象,指在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。

使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句
级触发。

触发器类型 NEW 和OLD
INSERT型触发器 NEW表示将要或者已经新增的数据
UPDATE型触发器 OLD表示修改之前的数据,NEW表示将要或已经修改后的数据
DELETE型触发器 OLD表示将要或者已经删除的数据

语法

# 创建
CREATE TRIGGER trigger_name

BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW 	#行级触发器
BEGIN
	trigger_stmt ;
END;
# 查看
SHOW TRIGGERS ;
# 删除
DROP TRIGGER [schema_name.]trigger_name;# 如果没有指定schema_name,默认为当前数据库。

案例

# 通过触发器记录tb_user表的数据变更日志,将变更日志插入到日志表user_logs中,包含增加,修改,删除;

create table user_logs(
    id              int(11) not null primary key auto_increment,
    operation       varchar(20) not null comment'操作类型,insert/update/delete',
    operate_time    datetime not null comment '操作时间',
    operate_id      int(11)not null comment'操作的ID',
    operate_params  varchar(500)comment'操作参数'
)engine=innodb default charset=utf8;

# 插入数据触发器
create trigger tb_user_insert_trigger
    after insert on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
        (null, 'insert', now(), new.id,
         concat('插入的数据为:id=',new.id, 'name=', new.name, 'phone=', new.phone,
             'email=', new.email, 'profession=', new.profession));
end;


show triggers;
drop trigger tb_user_insert_trigger;

# 插入数据进行检验
insert into tb_user VALUES (25,'二皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now());


# 修改数据触发器
create trigger tb_user_update_trigger
    after update on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
        (null, 'update', now(), new.id,
         concat('更新之前的数据为:id=',old.id, 'name=', old.name, 'phone=', old.phone, 'email=', old.email, 'profession=', old.profession,
             '\n更新之后的数据为:id=',new.id, 'name=', new.name, 'phone=', new.phone, 'email=', new.email, 'profession=', new.profession));
end;

show triggers;
drop trigger tb_user_update_trigger;

# 插入数据进行检验
update tb_user set age = 60 where id = 24 ;

update tb_user set phone = '1234567' where id <= 4 ;

# 删除数据触发器


create trigger tb_user_delete_trigger
    after delete on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
        (null, 'delete', now(), old.id,
         concat('删除之前的数据为:id=',old.id, 'name=', old.name, 'phone=', old.phone, 'email=', old.email, 'profession=', old.profession));
end;

show triggers;
drop trigger tb_user_delete_trigger;

# 插入数据进行检验
delete from  tb_user where id = 25 ;

delete from  tb_user  where id between 23 and 24;

小结

  • 视图(VIEW)
    • 虚拟存在的表,不保存查询结果,只保存查询的SQL逻辑
    • 简单、安全、数据独立
  • 存储过程(PROCEDURE)
    • 事先定义并存储在数据库中的一段SQL语句的集合。
    • 减少网络交互,提高性能、封装重用
    • 变量、if、case、参数(in/out/inout)、while、repeat、loop、cursor、handler
  • 存储函数(FUNCTION)
    • 存储函数是有返回值的存储过程,参数类型只能为IN类型
    • 存储函数可以被存储过程替代
  • 触发器(TRIGGER)
    • 可以在表数据进行INSERT、UPDATE、DELETE之前或之后触发
    • 保证数据完整性、日志记录、数据校验

5 锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

MySQL中的锁,按照锁的粒度分,分为以下三类:

  • 全局锁:锁定数据库中的所有表
  • 表级锁:每次操作锁住整张表
  • 行级锁:每次操作锁住对应的行数据

全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。

其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

image-20220417202527927

数据库中加全局锁,是一个比较重的操作,存在以下问题:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。

在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction参数来完成不加锁的一致性数据备份。

# 本地仓库
mysqldump -uroot -p123456 test_09_view > E:/Documents/DataGripProject/export/test_09.sql

# 远程服务器
mysqldump -h 101.201.100.130 -uroot -p123456 test_09_view > E:/Documents/DataGripProject/export/test_09.sql

# 使用 --single-transaction

# 本地仓库
mysqldump --single-transaction -uroot -p123456 test_09_view > E:/Documents/DataGripProject/export/test_09.sql 
# 远程服务器
mysqldump --single-transaction -h 101.201.100.130 -uroot -p123456 test_09_view > E:/Documents/DataGripProject/export/test_09.sql 

表级锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。

表锁

对于表锁,分为两类: 表共享读锁(read lock)表独占写锁(write lock)

加锁:lock tables 表名…. read/write

释放锁:unlock tables / 客户端断开连接

表共享读锁(read lock)

image-20220418083313125

image-20220418084808347

表独占写锁(write lock)

image-20220418084944645

image-20220418085957921

元数据锁(meta data lock,MDL)

MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。

MDL锁主要作用是维护表元数据的数据一致性在表上有活动事务的时候,不可以对元数据进行写入操作

为了避免DML与DDL冲突,保证读写的正确性。

在MySQL5.5中引入了MDL,

  • 当对一张表进行增删改查的时候,加MDL读锁(共享)
  • 当对表结构进行变更操作的时候,加MDL写锁(排他)
对应SQL 锁类型 说明
lock tables xxx read /write SHARED_READ_ONLY / SHARED_NO_READ_WRITE
select 、 select ... lock in share mode SHARED READ 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert、 update、 delete、 select ... for update SHARED_WRITE 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table ... EXCLUSIVE 与其他的MDL都互斥

查询元数据锁

select object_type,object_schema,object_name,lock_type,lock_duration 
from performance_schema.metadata_locks ;
  • 读操作会加读共享锁,写操作会加写共享锁,互相兼容

image-20220418092717248

  • 两边都执行写操作会加两个写共享锁,互相兼容; 若操作同一行数据, 陷入阻塞

image-20220418093925448

  • 只要存在读共享锁或者写共享锁,均不可与排它锁共存

image-20220418094730308

意向锁

意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。

意向排他锁(IX):与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥。

可以通过以下SQL,查看意向锁及行锁的加锁情况:

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data 
from performance_schema.data_locks;
  • 意向共享锁(IS)与表锁共享锁(read)兼容,与表锁排它锁(write)互斥
    • 意向共享锁手动加 lock in share mode

image-20220418095709302

  • 意向排他锁(IX)与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥
    • 意向排他锁会在修改数据以后自动添加

image-20220418100441958

行级锁

行锁

InnoDB实现了以下两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁
当前锁类型\请求锁类型 S(共享锁) X(排它锁)
S(共享锁) 兼容 冲突
X(排它锁) 冲突 冲突
SQL 行锁类型 说明
INSERT ... 排他锁 自动加锁
UPDATE ... 排他锁 自动加锁
DELETE .. 排他锁 自动加锁
SELECT(正常) 不加任何锁
SELECT ... LOCK IN SHARE MODE 共享锁 需要手动在SELECT之后加LOCK IN SHARE MODE
SELECT ... FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE

默认情况下,InnoDB 在 REPEATABLE READ 事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引扫描,以防止幻读。

  • 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  • InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁
    • 例子参考前文 【SQL优化-->update 优化】

间隙锁/临键锁

默认情况下,InnoDB 在 REPEATABLE READ 事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引扫描,以防止幻读。

  • 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
  • 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁。
  • 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。

  • 给不存在的记录加锁时,优化为间隙锁(下图中3-8之间),在没有commit之前,其他事务不能访问其中内容。

image-20220418104454762

  • 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁。
    • 如果索引到 18 (普通索引),会将16到18之间,以及18到29之间的间隙加锁

image-20220418105044939

image-20220418105758063

  • 普通索引与唯一索引的对比

image-20220418110312622

  • 索引上的范围查询(唯一索引)-- 会访问到不满足条件的第一个值为止

image-20220418111537136

LOCK_MODE LOCK_DATA 锁范围
X,REC_NOT_GAP 15 15 那条数据的行锁
X,GAP 15 15 那条数据之前的间隙,不包含 15
X 15 15 那条数据的间隙,包含 15

小结

  • 概述
    • 在并发访问时,解决数据访问的一致性、有效性问题
    • 全局锁、表级锁、行级锁
  • 全局锁
    • 对整个数据库实例加锁,加锁后整个实例就处于只读状态
    • 性能较差,数据逻辑备份时使用
  • 表级锁
    • 操作锁住整张表,锁定粒度大,发生锁冲突的概率高
    • 表锁、元数据锁、意向锁
  • 行级锁
    • 操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低
    • 行锁、间隙锁、临键锁

6 InnoDB引擎

逻辑存储结构

image-20220418133531729

  • 表空间 (Taablespace)(ibd文件),一个mysql实例可以对应多个表空间,用于存储记录、索引等数据。

  • 段 (Segment),分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment),InnoDB
    是索引组织表,数据段就是B+树的叶子节点,索引段即为 B+ 树的非叶子节点。段用来管理多个Extent(区)。

  • 区 (Extent),表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页。

  • 页 (Page),是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16KB。为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区。

  • 行 (row),InnoDB存储引擎数据是按行进行存放的。

    • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
    • Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

架构

MySQL5.5版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。

image-20220418140348513

内存结构

image-20220418140710314

Buffer Pool:缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改
查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷
新到磁盘,从而减少磁盘I0,加快处理速度。
缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
·free page:空闲page,未被使用。
·clean page:被使用page,数据没有被修改过。
·dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。

image-20220418141142980

image-20220418141302889

show variables like '%hash_index%'

image-20220418141527324

image-20220418141951822

show variables like '%innodb%log%';

innodb_flush_log_at_trx_commit 1 
 innodb_log_buffer_size		16777216

磁盘结构

image-20220418142629055

show variables like '%innodb_data_file_path%';
show variables like '%innodb_file_per_table%';

image-20220418142819223

image-20220418143109054

image-20220418143421744

后台线程

image-20220418144148217

Master Thread

核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收

IO Thread

在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IO Thread主要负责这些IO请求的回调

show engine innodb status;

image-20220418144009942

线程类型 职责 默认个数
Read thread 负责读操作 4
Write thread 负责写操作 4
Log thread 负责将日志缓冲区刷新到磁盘 1
Insert buffer thread 负责将写缓冲区内容刷新到磁盘 1

Purge Thread

主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。

Page Cleaner Thread

协助Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。

事务原理

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败

特性

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

image-20220418145959619

redo log

重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性

该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

image-20220418150441685

image-20220418150958564

undo log

回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log 中的逻辑记录读取到相应的内容并进行回滚。

Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。

Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含 1024 个 undo log segment。

MVCC

基本概念

当前读

读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select .…. lock in share mode(共享锁),select .…. for update、update、insert、delete(排他锁)都是一种当前读。

两个事务对同一张表操作时,select 不能即时查看到另一个事务做的改变,select .…. lock in share mode可以,但如果操作对象是同一索引,则会陷入阻塞,代另一方提交后方可查看(在隔离等级为 Repeatable Read时,即使一方提交另一方select也不能看到)。

image-20220418152751751

快照读

简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • Read Committed:每次select,都生成一个快照读。
  • Repeatable Read:开启事务后第一个select语句才是快照读的地方。
  • Serializable:快照读会退化为当前读。

MVCC

Multi-Version Concurrency Control,多版本并发控制。

指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

记录中的隐藏字段

隐藏字段 含义
DB_TRX_ID 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。
DB_ROLL_PTR 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。
DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。

undo log

回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,“在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

undo log 版本链

image-20220418160131708

不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。

ReadView

ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id

ReadView中包含了四个核心字段:

字段 含义
m_ids 当前活跃的事务 ID 集合
min_trx_id 最小活跃事务 ID
max_trx_id 预分配事务 ID,当前最大事务 ID+1(因为事务 ID 是自增的)
creator_trx_id ReadView创建者的事务 ID

image-20220418160659199

不同的隔离级别,生成ReadView的时机不同:

  • READ COMMITTED:在事务中每一次执行快照读时生成ReadView。
  • REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。

READ COMMITTED (RC)隔离级别

在事务中每一次执行快照读时生成ReadView

image-20220418161318085

  • 4==5 ? 不成立 , 4 < 3 ? 不成立 , 4 > 6 ? 不成立 , 4 不在 3 ~ 5 之间 ? 不成立 -->
  • 3==5 ? 不成立 , 3 < 3 ? 不成立 , 4 > 6 ? 不成立 , 3 不在 3 ~ 5 之间 ? 不成立 -->
  • 2==5 ? 不成立 , 2 < 3 ? 成立,返回

image-20220418161423163

4==5 ? 不成立 , 4 < 4 ? 不成立 , 4 > 6 ? 不成立 , 4 不在 4 ~ 5 之间 ? 不成立 -->

3==5 ? 不成立 , 3 < 4 ? 成立

REPEATABLE READ (RR)隔离级别

仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView

image-20220418163255165

image-20220418163439393

小结

  • 逻辑存储结构
    • 表空间、段、区、页、行
  • 架构
    • 内存结构
    • 磁盘结构
  • 事务原理
    • 原子性 -- undo log
    • 持久性 -- redo log
    • 一致性 -- ndo log +redo log
    • 隔离性 -- 锁+MVCC
  • MVCC
    • 记录隐藏字段
    • undo log 版本链
    • readView

7 MySQL管理

数据库 含义
mysql 存储MySOL服务器正常运行所需要的各种信息(时区、主从、用户、权限等)
information_schema 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等
performance_schema 为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数
Sys 包含了一系列方便DBA和开发人员利用performance_schema性能数据库进行性能调优和诊断的视图

常用工具

在 Linux 中 若是以下指令不能使用

find / -name mysqladmin
find / -name mysqldump
find / -name mysqld
find / -name mysqlshow
find / -name mysqlbinlogn
find / -name mysqlimport

# 将返回的路径添加到 usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqladmin /usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqldump /usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqld /usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqlshow /usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqlbinlog /usr/bin
ln -fs /usr/local/mysql8.0/bin/mysqlimport /usr/bin

在 Windows 中

# 使用 net start mysql 或 net start mysql8  启动 mysql 的配置方法

在注册表中计算机\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\mysql
DisplayName 值为 mysql8
ImagePath 值为
D:\Program_Files\mysql-8.0.23\bin\mysqld.exe –defaults-file=D:\Program_Files\mysql-8.0.23\my.ini mysql

mysql

该mysql不是指mysql服务,而是指mysql的客户端工具。

# 语法:
mysql [options] [database]
# 选项:

-u, -- user=name		# 指定用户名
-p, -- password[=name]	# 指定密码
-h, -- host=name		#指定服务器IP或域名
-P, -- port=port		#指定连接端口
-e, -- execute=name		#执行SQL语句并退出


# -e 选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。
mysql -uroot -p123456 db01 -e "select *from stu";
mysql -h101.201.100.130 -P3306 -uroot -p123456 db01 -e "select *from stu";

mysqladmin

mysqladmin --help
mysqladmin -uroot -p1234 create db02
mysqladmin -uroot -p1234 drop db02
mysqladmin -uroot -p1234 version

#start-slave
#stop-slave

mysqlbinlog

服务器生成的二进制日志文件以二进制格式保存,如果想要检查这些文本的文本格式,就会使用到mysqlbinlog日志管理工具。

mysqlbinlog [options] log-files1 log-files2 ...

# 选项:
-d, --database=name	# 指定数据库名称,只列出指定的数据库相关操作。
-o, --offset=#		# 忽略掉日志中的前n行命令。
-r,--result-file=name	# 将输出的文本格式日志输出到指定文件。
-s, --short-form	# 显示简单格式,省略掉一些信息。

--start-datatime=date1 --stop-datetime=date2	# 指定日期间隔内的所有日志。
-start-position=pos1 -stop-position=pos2		# 指定位置间隔内的所有日志。
mysqlbinlog binlog.000011
mysqlbinlog -s binlog.000011

mysqlshow

mysqlshow客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。

mysqlshow [options] [db_name [table_name [col_name]]]

# 选项:

--count		# 显示数据库及表的统计信息(数据库,表均可以不指定)
-i			# 显示指定数据库或者指定表的状态信息

# 示例:

#查询每个数据库的表的数量及表中记录的数量
mysqlshow -uroot -p1234 --count
mysqlshow -uroot -p1234 db01 --count
mysqlshow -uroot -p1234 db01 table01 --count
mysqlshow -uroot -p1234 db01 table01 id --count

mysqlshow -uroot -p1234 -i
mysqlshow -uroot -p1234 db01 -i
mysqlshow -uroot -p1234 db01 table01 -i
mysqlshow -uroot -p1234 db01 table01 id -i

mysqldump

mysqldump客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的sQL语句。

mysqldump [options] db_name [tables]
mysqldump [options] -database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A

# 连接选项:
-u, -user=name			# 指定用户名
-p, -password[=name]	# 指定密码
-h, -host=name			# 指定服务器ip或域名
-P,--port=#				# 指定连接端口

# 输出选项:
--add-drop-database		# 在每个数据库创建语句前加上 drop database 语句
--add-drop-table		# 在每个表创建语句前加上 drop table 语句,默认开启;不开启(-skip-add-drop-table)
-n, -no-create-db		# 不包含数据库的创建语句
-t, --no-create-info	# 不包含数据表的创建语句	
-d --nq-data			# 不包含数据
-T, --tab=name			# 自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件
# 不包含数据表的创建语句
mysqldump -uroot -p1234 -t db01 > E:/db02.sql
cat db02.sql;
# 一个.sql文件,创建表结构的语句;一个.txt文件,数据文件
mysqldump -uroot -p1234 -T E:/ db01 table01		# 需要备份到 mysql 信任的文件夹
mysqldump -uroot -pll546546 -T /tmp/test test tb_user;
 ./mysqld --defaults-file=/etc/my.cnf --basedir=/usr/local/mysql8.0/ --datadir=/usr/local/mysql8.0/data/ --user=mysql --initialize

mysqlimport

mysqlimport是客户端数据导入工具,用来导入mysqldump加-T参数后导出的文本文件(不能执行SQL脚本)。

mysqlimport [options] db_name textfile1 [textfile2...]
# 示例:
mysqlimport -uroot -p1234 test /tmp/city.txt

# 如果需要导入sql文件,可以使用mysql中的source指令:
source /root/test.sql

小结

  • mysql
    • Mysql客户端工具,-e执行SQL并退出
  • mysqladmin
    • Mysql管理工具
  • mysqlbinlog
    • 二进制日志查看工具
  • mysqlshow
    • 查看数据库、表、字段的统计信息
  • mysqldump
    • 数据备份工具
  • mysqlimport / source
    • 数据导入工具
posted @ 2022-04-20 22:29  Kite_Lee  阅读(39)  评论(0编辑  收藏  举报