2020年暑假期间我开始接触 MySQL,通过阅读了几本相关书籍以及网上的一些教程,我逐渐了解了 MySQL 的入门级内容。在暑假期间学完 MySQL 的基础知识后,我便将其搁置在一旁,没有再进行进一步的探索。
如今,我想要再次拾起 MySQL 时,却发现其中的一些语法和规则已经差不多遗忘。对 MySQL 进阶的需求,再加上以前学过的每本书籍(或每个教程)的知识侧重点偶尔不同,因此我打算用这篇博客来记录我所学习到的 MySQL 重要知识点(包括基础知识与进阶内容)。
这一部分内容将介绍数据库与 SQL 的基本理论。
数据库(Database,简称 DB)是指将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合。
而用来管理数据库的计算机系统称为数据库管理系统(Database Management System,DBMS)。
数据库管理系统的种类有:
很明显,MySQL 属于关系数据库中的内容。
RDBMS的常见系统结构是客户端 / 服务器类型(C/S类型)。
服务器指的是用来接收其他程序发出的请求,并对该请求进行相应处理的程序(软件),或者是安装了此类程序的设备(计算机)。
客户端是指向服务器发出请求的程序(软件),或者是安装了该程序的设备(计算机)。
关系数据库采用由行和列组成的二维表来管理数据,我们习惯将表的行称之为记录, 将表的列称之为字段。
关系数据库必须以行为单位进行数据读写,假设我们将行和列交汇的方格称为单元格。 那么一个单元格中只能输入一个数据。
MySQL语句的种类主要分为以下 3 类:
MySQL 语句以分号(;)为结尾,它的关键字、表名和列名不区分大小写,但是插入到表中的数据是区分大小写的。
字符串和日期常数需要使用单引号(’)括起来(MySQL 也可以使用双引号(")),单词之间需要使用半角空格或者换行符进行分隔。
在这里给出 MySQL 语句的书写顺序与执行顺序。关于语句中的各子句含义与用法,我们会在后续逐个解释。
①SELECT →②FROM →③WHERE →④GROUP BY →⑤HAVING →⑥ORDER BY →⑦LIMIT
①FROM →②WHERE →③GROUP BY →④HAVING →⑤SELECT →⑥ORDER BY →⑦LIMIT
这一部分内容将介绍如何创建和删除数据库、表,同时对创建表的语法中的约束与数据类型进行详细解释,还会介绍如何对表定义进行更新。
①创建数据库
CREATE DATABASE <数据库名称>;
②创建表
CREATE TABLE <表名>
(<列名1> <数据类型> <该列所需的约束>,
<列名2> <数据类型> <该列所需的约束>,
<列名3> <数据类型> <该列所需的约束>,
...
<该表的约束1>, <该表的约束2>, ...);
1> 命名规范
只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称, 名称必须以半角英文字母开头,且名称不能重复。
2> 数据类型
所有的列必须指明数据类型,数据类型主要分为:数字型、字符型和日期型。细分来说,有以下数据类型:
名称 | 类型 | 说明 |
---|---|---|
TINYINT | 短整型 | 范围为0~255 |
INT | 整型 | 4字节整数类型,范围约+/-21亿 |
BIGINT | 长整型 | 8字节整数类型,范围约+/-922亿亿 |
INTEGER | 整数 | 不能用来存放小数 |
REAL(FLOAT(24)) | 浮点型 | 4字节浮点数,范围约+/-1038 |
DOUBLE | 浮点型 | 8字节浮点数,范围约+/-10308 |
DECIMAL(M,N)(NUMERIC(M,N)) | 高精度小数 | 由用户指定精度的小数,例如,DECIMAL(20,10)表示一共20位,其中小数10位,通常用于财务计算 |
CHAR(N) | 定长字符串 | 存储指定长度的字符串,例如,CHAR(100)总是存储100个字符的字符串,不足字符数要求的话使用半角空格补足 |
VARCHAR(N) | 变长字符串 | 存储可变长度的字符串,例如,VARCHAR(100)可以存储0~100个字符的字符串 |
BOOLEAN | 布尔类型 | 存储True或者False |
DATE | 日期类型 | 存储日期,例如,2018-06-22 |
TIME | 时间类型 | 存储时间,例如,12:20:59 |
DATETIME | 日期和时间类型 | 存储日期+时间,例如,2018-06-22 12:20:59 |
3> 约束
在定义每一列的时候,我们可以在最后为该列加上相关的约束,例如:
约束 | 含义 |
---|---|
AUTO_INCREMENT | 编号从1开始,并1为基数递增 |
DEFAULT <数值> | 为某列设置默认值 |
PRIMARY KEY | 将某一列或某几列设置为主键 |
NOT NULL | 该列不能插入空数据 |
注意:
4> 键
我们在约束部分中提到了主键,所谓的键是指在指定特定数据时使用的列的组合,而主键指的是可以特定一行数据的列。
①删除数据库
DROP DATABASE <数据库名称>;
②删除表
DROP TABLE <表名称>;
还有一种删除表的方法,先判断表是否存在,如果存在便删除:
DROP TABLE IF EXISTS <表名>;
注意,数据库和表一旦删除便无法恢复。
当你定义了一个表之后,你可能会发现刚定义的表存在以下问题:少定义了一列、多定义了一列、某一列的列名写错了等,那这个时候怎么办?
我们可能会想到删除原来的表再重新定义,但其实不必这么大费周章,只需要运用 ALTER TABLE 语句即可解决以上问题。我们并没有删除原来表的定义,只是对它进行了更新,因此我们称之为表的定义更新。
①添加列
ALTER TABLE <表名> ADD COLUMN <列的定义> ;
这里列的定义就是像创建表时列的定义一样。
②删除列
ALTER TABLE <表名> DROP COLUMN <列名> ;
③重命名列名
ALTER TABLE <表名> CHANGE COLUMN <旧列名> <新列的定义>;
④重命名表名
RENAME TABLE <旧表名> TO <新表名>;
注意:表定义变更后无法恢复。
这一部分主要介绍 SELECT 语句中最基本的语法,以及三大运算符——算术运算符、比较运算符和逻辑运算符。
SQL 语句中最基础的语句就是 SELECT 语句,通过 SELECT 语句查询并选取出必要数据的过程称为匹配查询或查询(query)。
①基本语法
SELECT 语句的基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名>;
该 SELECT 语句包含了 SELECT 和 FROM 两个子句(clause)。SELECT 子句中列举了希望从表中查询出的列的名称,而 FROM 子句则指定了选取出数据的表的名称。
②SELECT 子句
1>名称
SELECT 子句中的列名还可以这样写——<表名>.<列名>,这对于 FROM 子句中存在不止一个表时(比如说表的联结)非常有用。(联结见后续章节)
2>列名
查询多列时,SELECT 子句中需要使用逗号进行分隔。查询结果中列的顺序和 SELECT 子句中的顺序相同。SELECT子句中可以只用一个星号( * ),星号代表全部列的意思。但是, 如果使用星号的话,就无法设定列的显示顺序了 。这时就会按照 CREATE TABLE 语句的定义对列进行排序。
3>关键字
可以使用 AS 关键字为列设定别名,同样也可以使用该关键字在 FROM 子句中对表设置别名。注意,我们可以设置汉字别名,设定汉语别名时需要使用双引号( " )括起来。此外,AS 关键字可以省略。
SELECT 子句中有一个 DISTINCT 关键字,使用 DISTINCT 可以删除重复行。注意两点:在使用 DISTINCT 时, NULL 也被视为一类数据;DISTINCT 关键字只能用在第一个列名之前,对多列数据同时进行去重。
4>常数
SELECT 子句中还可以书写常数,主要包括字符串常数、数字常数和日期常数,举例说明:
SELECT '人员' AS string, 27 AS total, '2020-12-12' AS date, <其他列名>
FROM <表名>;
查询结果有多少行,就会有多少行的常数。
③FROM子句
FROM 子句中的表名还可以这样写——<数据库名>.<表名>,这有利于我们更容易观察到某个表属于哪个数据库,而且还可以跨库对表进行联结。
④ WHERE 子句
前面的语句能将表中的所有记录都查询出来,但是我们也可以利用 WHERE 子句对数据进行筛选,只留下满足查询条件的记录。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名>
WHERE <条件表达式>;
WHERE 子句中可以包含多个查询条件,当包含多个查询条件时,就可以使用逻辑运算符进行组合。
⑤注释
注释是 SQL 语句中用来标识说明或者注意事项的部分,注释的书写方法有如下两种:
注释可以插入在子句之间。
运算符就是使用其两边的值进行四则运算或者字符串拼接、数值大小比较等运算,并返回结果的符号。
①算术运算符
提到算术运算符,最容易想到的就是四则运算:
含义 | 运算符 |
---|---|
加法运算 | + |
减法运算 | - |
乘法运算 | * |
除法运算 | / |
我们也可以像平常的运算表达式那样使用括号 ( )来提升运算优先级。
注意,所有包含 NULL 的算术运算,结果肯定是 NULL。
MySQL 中允许省略 FROM 子句,只利用 SELECT子句进行计算,但是我们通常不这么操作。举例说明:
SELECT 1+2;
②比较运算符
SQL 中主要的比较运算符如下表所示:
运算符 | 含义 |
---|---|
= | 等于 |
<> | 不等于 |
>= | 大于等于 |
> | 大于 |
<= | 小于等于 |
< | 小于 |
注意:
③逻辑运算符
就像我们在 WHERE子句中提到的那样,通过使用逻辑运算符,可以将多个查询条件进行组合。常用的逻辑运算符如下表所示:
运算符 | 含义 |
---|---|
NOT | 否定某一条件 |
AND | 在其两侧的查询条件都成立时整个查询条件才成立 |
OR | 在其两侧的查询条件有一个成立时整个查询条件都成立 |
注意:
这一部分主要介绍使用 SQL 语句进行汇总操作以及排序操作的方法,主要包含:聚合函数、GROUP BY 子句、HAVING子句和 ORDER BY 子句。
聚合函数(也称为“聚集函数”)主要用来对表中的数据进行某种汇总操作或计算。SQL 中常见的聚合函数如下表所示:
函数 | 含义 |
---|---|
COUNT | 计算表中的记录数(行数) |
SUM | 计算表中数值列中数据的合计值 |
AVG | 计算表中数值列中数据的平均值 |
MAX | 计算表中任意列中数据的最大值 |
MIN | 计算表中任意列中数据的最小值 |
注意:
使用 GROUP BY 子句可以先把表分成几组,然后再进行汇总处理。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名>
WHERE <条件表达式>
GROUP BY <聚合键1>, <聚合键2>, ...;
注意以下几点:
注意, DISTINCT 和 GROUP BY 子句,都能够删除后续列中的重复数据,那么到底应该使用哪一个?
其实这个问题本身就是本末倒置的,我们应该考虑的是该 SELECT 语句是否满足需求。选择的标准其实非常简单,在“想要删除选择结果中的重复记录”时使用 DISTINCT ,在“想要计算汇总结果”时使用 GROUP BY 。
使用 GROUP BY 子句,可以得到将表分组后的结果,而 HAVING 子句可以通过指定条件来选取特定组。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名>
WHERE <条件表达式>
GROUP BY <聚合键1>, <聚合键2>, ...
HAVING <分组结果对应的条件>;
注意:
原则上,聚合键所对应的条件既可以写在 HAVING 子句当中,又可以写在 WHERE 子句当中。但是,聚合键所对应的条件不应该书写在 HAVING 子句当中,而应该书写在 WHERE 子句当中。原因如下:
通常在 SELECT 语句末尾(这是因为对数据行进行排序的操作必须在结果即将返回时执行)添加 ORDER BY 子句来明确指定记录的排列顺序。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名>
WHERE <条件表达式>
GROUP BY <聚合键1>, <聚合键2>, ...
HAVING <分组结果对应的条件>
ORDER BY <排序键>;
注意:
这一部分将会介绍更新表中数据的方法。数据的更新处理大体可以分为插入( INSERT )、删除( DELETE )和更新( UPDATE )三类。此外,还会介绍数据库中用来管理数据更新的重要概念——事务。
SQL 中的 INSERT 语句用来向表中插入数据。基本语法如下:
INSERT INTO <表名> (列1, 列2, 列3, ...) VALUES (值1, 值2, 值3, ...);
①清单的概念
将列名和值用逗号隔开,分别括在 () 内,这种形式称为清单,例如基本语法中的列清单和值清单。
②插入多行数据
原则上,执行一次 INSERT 语句会插入一行数据。但是 MySQL中支持多行插入数据:只要将值清单之间用逗号分隔即可。此外,对表进行全列 INSERT 时,可以省略表名后的列清单。
③插入 NULL 值
INSERT 语句中想给某一列赋予 NULL 值时,可以直接在 VALUES 子句的值清单中写入 NULL 。但是,想要插入 NULL 的列一定不能设置 NOT NULL 约束,否则会报错。
④插入默认值
我们还可以向表中插入默认值(初始值)。可以通过在创建表的CREATE TABLE 语句中设置 DEFAULT 约束来设定默认值。插入默认值主要有以下两种方法:
注意:
⑤从其他表中复制数据
基本语法如下:
INSERT INTO <表名> (列1, 列2, 列3, ...)
SELECT <列名> FROM <表名>;
该 INSERT 语句中的 SELECT 语句,也可以使用 WHERE 子句或者 GROUP BY 子句等,但是使用 ORDER BY 子句并不会产生任何效果,因为无法保证表内部记录的排列顺序。
DELETE 语句用于删除表中的数据。基本语法如下:
DELETE FROM <表名>;
①DELETE 语句和 DROP 语句的区别:
②搜索型 DELETE
想要删除部分数据行时,可以像 SELECT 语句那样使用 WHERE 子句指定删除条件。这种指定了删除对象的 DELETE 语句称为搜索型 DELETE 。它的基本语法如下:
DELETE FROM <表名>
WHERE <条件>;
③其他子句的使用
DELETE 语句中不能使用 GROUP BY 、HAVING 和 ORDER BY 三类子句,而只能使用 WHERE 子句,因为在删除表中数据时它们都起不到什么作用。
④TRUNCATE语句
MySQL中支持一种名为 TRUNCATE 语句的数据删除语句,它的功能与简单型 DELETE 语句相同,那就是只能删除整张表的数据。正是因为它不能具体地控制删除对象,所以其处理速度比 DELETE 要快得多。
使用 INSERT 语句向表中插入数据之后,有时我们想要再更改数据,这时候并不需要把数据删除再重新插入,直接使用 UPDATE 语句就可以更新表中的数据。基本语法如下:
UPDATE <表名>
SET <列名> = <表达式>;
我们会将表中的该列均更新为表达式所对应的值。
①搜索型 UPDATE
更新数据时也可以像 DELETE 语句那样使用 WHERE 子句,这种指定更新对象的 UPDATE 语句称为搜索型 UPDATE 语句。基本语法如下:
UPDATE <表名>
SET <列名> = <表达式>
WHERE <条件>;
②更新为 NULL 值
使用 UPDATE 也可以将列更新为 NULL (该更新俗称为 NULL 清空)。此时只需要将赋值表达式右边的值直接写为 NULL 即可,但仅限于未设置 NOT NULL 约束的列。
③多列更新
就像 MySQL 支持多列插入一样,它也支持多列更新,基本语法如下:
UPDATE <表名>
SET <列名1> = <表达式1>,
<列名2> = <表达式2>,
...;
我在学习到这部分的时候,产生了一个疑惑,那就是:如果我在进行多列更新时,先对列 A 进行了更新,然后对列 B 的更新时表达式中用到了列 A,那么列 B 在更新时用的是列 A 的旧值还是新值?
后来经过测试后,我发现列 B 用的是列 A 的新值,这说明数据更新的时效性还蛮快的。
事务是需要在同一个处理单元中执行的一系列更新处理的集合。
①事务的开始
事务并没有标准的开始指令存在,而是根据 DBMS 的不同而不同。实际上,几乎所有的数据库产品的事务都无需开始指令。这是因为大部分情况下,事务在数据库连接建立时就已经悄悄开始了,并不需要用户再明确发出开始指令。
像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常会有如下两种情况:
MySQL属于自动提交模式。
②事务的语法
START TRANSACTION; -- 事务开始语句
DML语句1(INSERT DELETE UPDATE);
DML语句2;
...
COMMIT 或者 ROLLBACK; -- 事务结束语句
③事务的四种特性——ACID特性
这一部分主要介绍嵌套在 SELECT 语句中的视图和子查询等技术。
①视图的概念
从SQL的角度来看,视图和表是相同的,两者的区别在于表中保存的是实际数据,而视图中保存的是 SELECT 语句(视图本身并不存储数据)。
数据库中的数据实际上会被保存到计算机的存储设备(通常是硬盘)中,但是使用视图时并不会将数据保存到存储设备之中,而且也不会将数据保存到其他任何地方。实际上视图保存的是 SELECT 语句,我们从视图中读取数据时,视图会在内部执行该 SELECT 语句并创建出一张临时表。
②视图的优点
视图有以下几大优点:
③视图的语法
CREATE VIEW <视图名称> (<视图列名1>, <视图列名2>, ...)
AS
<SELECT 语句 >
注意:
④多重视图
以视图为基础创建的视图称为多重视图。但是应该尽量避免在视图的基础上创建视图,因为对多数DBMS来说,多重视图会降低SQL的性能。
⑤视图的限制
存在第二条限制的原因:视图和表需要同时进行更新,因此通过汇总等操作得到的视图无法进行更新,因为无法将视图的更改反映到原表。
⑥删除视图的语法
DROP VIEW <视图名称>;
简单来说,子查询就是一次性视图(SELECT 语句)。与视图不同,子查询在 SELECT 语句执行完毕之后就会消失。
①子查询的语法
子查询就是将用来定义视图的 SELECT 语句直接用于 FROM 子句当中,作为内查询,它会首先执行。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM (
SELECT <列名1>, <列名2>, ...
FROM <表名>
WHERE <条件>
GROUP BY <聚合键>
) AS <子查询别名>;
注意:
②标量子查询
标量子查询就是返回单一值的子查询,必须而且只能返回 1 行 1 列的结果,绝对不能返回多行结果。
由于聚合函数不能写在 WHERE 子句中,因此我们可以使用标量子查询来代替。
能够使用常数或者列名的地方,无论是 SELECT 子句、 GROUP BY 子句、 HAVING 子句,还是 ORDER BY 子句,几乎所有的地方都可以使用。
①基本语法
关联子查询在子查询中添加的 WHERE 子句。基本语法如下:
SELECT <列名1>, <列名2>, ...
FROM <表名1> AS <表别名1>
WHERE <列名3> > (SELECT <对列名3进行聚合>
FROM <表名2> AS <表别名2>
WHERE <表别名1>.<列名1> = <表别名2>.<列名4>);
单看关联子查询的基本语法有些不容易理解,举例来说明:
SELECT product _type , product_name, sale_price
FROM Product AS P1
WHERE sale_price > (SELECT AVG(sale_price)
FROM Product AS P2
WHERE P1.product_type = P2.product_type);
对以上代码我的理解如下:
注意:
②关联名称的作用域
结合条件一定要写在子查询中。原因与关联名称的作用域有关。
关联名称是指像上例中 P1、P2 这样作为表别名的名称;而关联名称存在一个有效范围(或者叫生存范围)的限制,即为作用域。概括来说,关联名称的作用域为“内部可以看到外部,而外部看不到内部”。
这一部分将介绍具有代表性的函数以及特殊版本的函数(谓词和CASE 表达式)的使用方法。
SQL中的函数主要分为以下几种:
种类 | 功能 |
---|---|
算术函数 | 用来进行数值计算的函数 |
字符串函数 | 用来进行字符串操作的函数 |
日期函数 | 用来进行日期操作的函数 |
转换函数 | 用来转换数据类型和值的函数 |
聚合函数 | 用来进行数据聚合的函数 |
①算术函数
函数 | 含义 |
---|---|
+ - * / | 加减乘除四则运算 |
ABS(数值) | 对数值求绝对值,NULL 的绝对值仍然是 NULL |
MOD(被除数,除数) | 求余,两个参数中有一个为 NULL 结果为 NULL |
ROUND(对象数值,保留的小数位数值) | 对数值进行四舍五入,当对象数值为 NULL 时结果为 NULL。 第二个参数可以省略,此时为对对象数值四舍五入取整 |
②字符串函数
函数 | 含义 |
---|---|
CONCAT(列1, 列2, 列3, …) | 字符串拼接函数(MySQL中特有的函数) |
LENGTH(字符串) CHAR_LENGTH(字符串) |
计算字符串的字节长度 计算字符串的字符长度 |
LOWER(字符串) UPPER(字符串) |
对英文字母小写 对英文字母大写 |
REPLACE(对象字符串,替换前的字符串,替换后的字符串) | 字符串的替换,三个参数中有一个为 NULL 结果为 NULL |
SUBSTRING(对象字符串 FROM 截取的起始位置 FOR 截取的字节数) | 字符串的截取,从字符串最左侧开始计算,1为起始值 |
注意:
③日期函数
函数 | 含义 |
---|---|
CURRENT_DATE | 返回当前日期,也就是该函数执行时的日期 SELECT CURRENT_DATE; |
CURRENT_TIME | 返回当前时间,也就是该函数执行时的时间 SELECT CURRENT_TIME; |
CURRENT_TIMESTAMP | 返回当前日期的时间,也就是该函数执行时的日期和时间 SELECT CURRENT_TIMESTAMP; |
EXTRACT( 日期元素 FROM 日期 ) | 截取出日期数据中的一部分 注意,返回值是数值类型而不是日期类型 日期元素有:YEAR、MONTH、DAY、HOUR、MINUTE、SECOND |
④转换函数
“转换”在 SQL 中主要有两层意思:一是数据类型的转换,简称为类型转换,在英语中称为 cast;另一层意思是值的转换。
函数 | 含义 |
---|---|
CAST(转换前的值 AS 想要转换的数据类型) | 数据类型的转换,例如SELECT CAST(‘0001’ AS SIGNED INTEGER) AS int_col; |
COALESCE( 数据 1, 数据 2, 数据 3, …) | 该函数将 NULL 值转换为其他值,返回可变参数中左侧开始第1个不是 NULL 的值 |
谓词就是返回值为真值(TRUE / FALSE / UNKNOWN)的函数。
①LIKE谓词
当需要进行字符串的部分一致查询时需要使用该谓词。部分一致大体可以分为前方一致、中间一致和后方一致三种类型。
部分一致类型 | 含义 |
---|---|
前方一致 | 选取出作为查询条件的字符串与查询对象字符串起始部分相同的记录 |
中间一致 | 选取出查询对象字符串中含有作为查询条件的字符串的记录 |
后方一致 | 选取出作为查询条件的字符串与查询对象字符串的末尾部分相同的记录 |
基本语法如下:
<列名> LIKE 'ddd%'; -- 前方一致
<列名> LIKE '%ddd%'; -- 中间一致
<列名> LIKE '%ddd'; -- 后方一致
注意:
对于 LIKE 谓词有两个字符使用的比较频繁:
字符 | 含义 |
---|---|
% | 0字符以上的任意字符串,包括0 |
_ | 任意一个字符 |
②BETWEEN谓词
使用 BETWEEN 可以进行范围查询。基本语法如下:
<列名> BETWEEN <下限值> AND <上限值>;
注意:
③ IS NULL、IS NOT NULL 谓词
为了选取出某些值为 NULL 的列的数据,不能使用 = ,而只能使用特定的谓词 IS NULL、IS NOT NULL。
基本语法如下:
<列名> IS NULL;
<列名> IS NOT NULL;
④ IN、NOT IN 谓词
IN 谓词是 OR的简便用法,例如以下 SQL 语句:
<列名> = <值1> OR <列名> = <值2> OR <列名> = <值3>;
可以写成:
<列名> IN (值1, 值2, 值3);
注意:
⑤ EXIST谓词
该谓词的作用就是“判断是否存在满足某种条件的记录”。如果存在这样的记录返回 TRUE,否则返回 FALSE。
注意:
CASE 表达式是在区分情况时使用的,这种情况的区分在编程中通常称为**(条件)分支**。CASE 表达式分为简单 CASE 表达式和搜索 CASE 表达式两种。
简单 CASE 表达式的基本语法如下:
CASE < 表达式 >
WHEN < 表达式 > THEN < 表达式 >
WHEN < 表达式 > THEN < 表达式 >
WHEN < 表达式 > THEN < 表达式 >
...
ELSE < 表达式 >
END
搜索 CASE 表达式的基本语法如下:
CASE
WHEN < 求值表达式 > THEN < 表达式 >
WHEN < 求值表达式 > THEN < 表达式 >
WHEN < 求值表达式 > THEN < 表达式 >
...
ELSE < 表达式 >
END
注意:
这一部分将会介绍使用 2 张以上的表的 SQL 语句,包括以行方向(竖)为单位的集合运算符和以列方向(横)为单位的联结。
集合运算,就是对满足同一规则的记录进行的加减等四则运算,它以行方向(纵向)为单位进行操作。用来进行集合运算的运算符称为集合运算符。基本语法如下:
SELECT <列1>, <列2>, <列3>
FROM <表1>
<集合运算符>
SELECT <列A>, <列B>, <列C>
FROM <表2>
常见的集合运算符如下表所示:
集合运算符 | 含义 |
---|---|
UNION | 并集 |
UNION ALL | 包含重复行的并集 |
INTERSECT | 选取表中公共部分的交集 |
EXCEPT | 差集 |
注意:
**联结(JOIN)就是将其他表中的列添加过来,进行“添加列”的集合运算,它以列方向(横向)**为单位进行操作。联结大体上可分为内联结、外联结和交叉联结。
内联结是只包含表内信息的联结,只能选取出同时存在于两张表中的数据。基本语法如下:
FROM <表名A> AS <别名A> INNER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
注意:
②外联结——OUTTER JOIN
外联结中包含原表中不存在(在原表之外)的信息,只要数据存在于某一张表中就能用方法读取出来。MySQL中外联结有左联结(LEFT OUTER JOIN,下方第一个图)和右联结(RIGHT OUTER JOIN,下方第二个图)。
基本语法如下:
-- 左联结
FROM <表名A> AS <别名A> LEFT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
-- 右联结
FROM <表名A> AS <别名A> RIGHT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
注意:
进阶:
1> A独有或B独有
我们可以利用外联结来实现只存在与表 A 或者表 B 的行,这在某种意义上也实现了表的差集运算。
基本语法如下:
-- A表独有
FROM <表名A> AS <别名A> LEFT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
WHERE <别名B>.<列2> IS NULL;
-- B表独有
FROM <表名A> AS <别名A> RIGHT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
WHERE <别名A>.<列1> IS NULL;
2> AB全有
MySQL 不支持外联结(FULL OUTER JOIN),但可以通过左联结 + UNION + 右联结实现。
基本语法如下:
-- AB全有
FROM <表名A> AS <别名A> LEFT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
UNION
FROM <表名A> AS <别名A> RIGHT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
3> A独有+B独有
A、B独有并集,相当于A、B全有去掉AB的共有(交集)。
基本语法如下:
-- A表独有+B表独有
FROM <表名A> AS <别名A> LEFT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
WHERE <别名B>.<列2> IS NULL;
UNION
FROM <表名A> AS <别名A> RIGHT OUTER JOIN <表名B> AS <别名B>
ON <别名A>.<列1> = <别名B>.<列2>
WHERE <别名A>.<列1> IS NULL;
③交叉联结——CROSS JOIN
对满足相同规则的表进行交叉联结的集合运算符是 CROSS JOIN (笛卡儿积)。
进行交叉联结时无法使用内联结和外联结中所使用的 ON 子句,这是因为交叉联结是对两张表中的全部记录进行交叉组合,因此结果中的记录数通常是两张表中行数的乘积。
这一部分介绍的是 SQL 中的高级聚合处理,包括窗口函数的用法和 GROUPING 运算符。
窗口函数也称为 OLAP 函数,OLAP 是 OnLine Analytical Processing 的简称,意思是对数据库数据进行实时分析处理。基本语法如下:
<窗口函数> OVER ([PARTITION BY <列清单>] ORDER BY <排序列清单>)
-- []表示可选的意思
窗口函数大体可以分为以下两种:
种类 | 举例 |
---|---|
聚合函数 | SUM、AVG、COUNT、MAX、MIN |
专用窗口函数 | RANK 、 DENSE _ RANK 、 ROW _ NUMBER |
①关于 PARTITION BY 子句:
②关于 ORDER BY 子句:
③专用窗口函数
常用的专用窗口函数含义如下:
函数 | 含义 |
---|---|
RANK | 计算排序时,如果存在相同位次的记录,则会跳过之后的位次。 例如,有 3 条记录排在第 1 位时:1 位、1 位、1 位、4 位…… |
DENSE_RANK | 同样是计算排序,即使存在相同位次的记录,也不会跳过之后的位次。 例如,有 3 条记录排在第 1 位时:1 位、1 位、1 位、2 位…… |
ROW_NUMBER | 赋予唯一的连续位次。 例如,有 3 条记录排在第 1 位时:1 位、2 位、3 位、4 位…… |
根据函数名称,我们可以轻易记住每个函数的功能。例如,RANK(排序)就是正常的排序,DENSE_RANK(密集排序)说明排名紧凑不会跳位,ROW_NUMBER就是单纯的行编号12345。
注意:
④聚合函数
与专用窗口函数不同的是,作为窗口函数使用的聚合函数,需要在括号内指定作为汇总对象的列。
将聚合函数作为窗口函数使用时,会以当前记录为基准来决定汇总对象的记录(顺序)。
⑤计算移动平均
窗口函数就是将表以窗口为单位进行分割,并在其中进行排序的函数。其实其中还包含在窗口中指定更加详细的汇总范围的备选功能,该备选功能中的汇总范围称为框架。其语法需要在 ORDER BY 子句之后使用指定范围的关键字:
ORDER BY <列名> ROWS <N行> PRECEDING;
将框架指定为“截止到之前 N 行”,也就是将作为汇总对象的记录限定为的“最靠近的 (N+1) 行”(包括自身行)。
-- 将框架指定为“截止到之后 N 行”
ORDER BY <列名> ROWS <N行> FOLLOWING;
-- 将当前记录的前后 N 行作为汇总对象
ORDER BY <列名> ROWS <M行> PRECEDING AND <N行> FOLLOWING;
⑥容易混淆的一点
我在做题的时候碰见一种情况: 在使用窗口函数时,利用 ORDER BY 对注册日期进行排序并利用 SUM 函数求销售单价的累加值,SQL 语句如下:
SUM(sale_price) OVER(ORDER BY regist_date)
但是,regist_date 中有个重复值重复了三次,那么 SQL 会以怎样的顺序对该重复值进行排序呢?
我原本以为会以某种规则进行排序,然后逐渐累加。而实际运行发现,当遇到了这个重复值时,它会将三个重复值对应的汇总列加起来再与前面的值进行累加,也就是说三个重复值对应的累加值是一样的。
只使用 GROUP BY 子句和聚合函数是无法同时得出小计和合计的。如果想要同时得到,可以使用 GROUPING 运算符。GROUPING 运算符包含以下几种:ROLLUP、GROUPING 函数、CUBE、GROUPING SETS。除 ROLLUP 外,MySQL 目前不支持其他运算符。
①ROLLUP
基本语法如下:
GROUP BY <列1>, <列2>, <列3>, ... WITH ROLLUP
功能:
一次计算出不同聚合键组合的结果,组合的个数为 n+1(n 是聚合键的个数)。在上述语法中就是一次计算出了如下组合的汇总结果:
GROUP BY () 表示没有聚合键,也就相当于没有 GROUP BY子句(这时会得到全部数据的合计行的记录)。因此,ROLLUP能够同时得到小计和合计。
得到的合计行记录(包括合计和小计)称为超级分组记录,超级分组记录默认使用 NULL 作为聚合键。
② GROUPING 函数(MySQL目前不支持)
基本语法如下:
-- 直接对列名使用即可
GROUPING(<列名>);
功能:
GROUPING 函数用来判断超级分组记录的 NULL 的特定函数,该函数在其参数列的值为超级分组记录所产生的 NULL 时返回 1 ,其他情况返回 0。因此,使用 GROUPING 函数能够简单地分辨出原始数据中的 NULL 和超级分组记录中的 NULL 。
③ CUBE(MySQL目前不支持)
GROUP BY CUBE(<列1>, <列2>, ...)
功能:
将 GROUP BY 子句中聚合键的“所有可能的组合”的汇总结果集中到一个结果中。因此,组合的个数就是 2 n (n 是聚合键的个数)。在上述语法中就是一次计算出了如下组合的汇总结果:
注意:
④ GROUPING SETS(MySQL目前不支持)
GROUP BY GROUPING SETS(<列1>, <列2>, ...)
功能:
该运算符可以用于从 ROLLUP 或者 CUBE 的结果中取出部分记录,与 ROLLUP 或者 CUBE 能够得到规定的结果相对, GROUPING SETS 用于从中取出个别条件对应的不固定的结果。在上述语法中就是一次计算出了如下组合的汇总结果:
MySQL 中有以下主要配置文件:
注意:
MySQL的逻辑框架主要分为四层:
可以通过 "show engines"命令查看MySQL支持的存储引擎:
也可以通过 “show variables like ‘%storage_engine%’” 查看MySQL的当前默认存储引擎:
这里主要对MyISAM和InnoDB进行比较,主要区别如下表:
索引在 sql 调优部分占据着重要的位置,了解并深入索引对我们来说也是非常重要的。
MySQL官方对索引的定义如下:索引(Index)是帮助 MySQL 高效获取数据的数据结构。因此索引的本质就是数据结构。索引的目的在于提高查询效率,可类比字典、书籍的目录等这种形式。
简单来说,索引是关系数据库中对某一列或多个列的值进行预排序的快速查找数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
平常所说的索引,如果没有特别指明,都是B树索引。其中聚集索引、次要索引、覆盖索引、前缀索引、唯一索引默认都是用B树。
通过 “show index from <表名>” 可以查看表的索引情况:
优点:
缺点:
索引主要分为以下三类:
索引的结构:
①创建索引
create index <索引名称> on <表名> (<列名>(长度));
alter table <表名> add index <索引名称> (<列名>(长度));
注意:
②删除索引
drop index <索引名称> on <表名>;
③查看索引
show index from <表名>;
④其他索引的创建方式
1> 添加主键索引
ALTER TABLE <表名> ADD PRIMARY KEY (<列名>);
2> 添加唯一索引
ALTER TABLE <表名> ADD UNIQUE (<列名>);
也可以只对某一列添加一个唯一约束而不创建唯一索引:
ALTER TABLE <表名> ADD CONSTRAINT <约束名称> UNIQUE (<列名>);
这种情况下,列并没有索引,但仍然具有唯一性保证。
3> 添加全文索引
ALTER TABLE <表名> ADD FULLTEXT (<列名>);
4> 添加普通索引
ALTER TABLE <表名> ADD INDEX <索引名称> (<列名>);
5> 添加组合索引
ALTER TABLE <表名> ADD INDEX <索引名称> (<列名1>, <列名2>, <列名3>, ...);
需建立索引的情况:
不需要创建索引的情况:
使用 EXPLAIN(执行计划)关键字可以模拟优化器执行sql查询语句,从而知道MySQL是如何处理sql语句。explain主要用于分析查询语句或表结构的性能瓶颈。
EXPLAIN 的基本语法很简单:
EXPLAIN + sql 语句;
通过 EXPLAIN + sql 语句可以知道如下内容:
我们假设创建了如下两个表:
explain select * from tb_emp;
得到的结果如下所示:
① id
id 代表 select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序,该字段通常与 table 字段搭配来分析。
1> id相同,执行顺序从上到下
2> id 不同,如果是子查询 id 的序号会递增,id 值越大执行优先级越高
3> id 同时存在相同和不同
id 如果相同,可以认为是同一组,执行顺序从上到下。在所有组中,id 值越大执行优先级越高。所以执行顺序为:t3 -> derived2(衍生表,也叫临时表) -> t2。
总结:id 的值表示 select 子句或表的执行顺序。id相同,执行顺序从上到下,id不同,值越大的执行优先级越高。
② select_type
select_type 代表查询的类型,主要用于区别普通查询、联合查询、子查询等复杂的查询。其值主要有六个:
1> SIMPLE
简单的 select 查询,查询中不包含子查询或 union 查询。
2> PRIMARY
查询中若包含任何复杂的子部分,最外层查询为 PRIMARY,也就是最后加载的就是 PRIMARY。
3> SUBQUERY
在 select 或 where 子句中包含了子查询,就为被标记为 SUBQUERY。
4> DERIVED
在 from 子句中包含的子查询会被标记为 DERIVED(衍生),MySQL会递归执行这些子查询,将结果放在临时表中。
5> UNION
若第二个 select 出现在 union 后,则被标记为 UNION。
若 union 包含在 from 子句的子查询中,union 中的前一个 select 将被标记为 DERIVED。
6> UNION RESULT
从 union 表获取结果的 select。
③ table
显示 sql 操作属于哪张表。
④ partitions
官方定义为 The matching partitions(匹配的分区),该字段应该是看 table 所在的分区(NULL 表示表未被分区)。
⑤ type
表示查询所使用的访问类型,我的理解是:type 字段表示你使用的 select 语句在对表进行检索行(筛选行)操作时使用了哪种类型的访问方式。
type 的值表示使用的查询 sql 语句的好坏,从最好到最差依次为:system-> const -> eq_ref -> ref -> range -> index -> ALL。
有一些不常用的 type 值没有给出。一般来说,需保证查询至少达到 range 级别,最好能达到 ref。
1> system
表只有一行记录(等于系统表),是 const 的特例类型,平时不会出现,可以忽略不计。在 MySQL 8.0 版本时,不会出现该字段值,只能出现 const,但是在 MySQL 5.5.48 版本可以出现该情况。猜测 MySQL 8.0 版本可能是进行了优化。
5.5.48:
8.0:
注:两个引擎的执行信息不一样,5.5.4 8执行过程中产生了临时表(DERIVED),8.0 为简单查询。
2> const
表示通过一次索引就找到了结果,常出现于 primary key 或 unique 索引。因为只匹配一行数据,所以查询非常快。如将主键置于 where 条件中,MySQL 就能将查询转换为一个常量。
注意:
3> eq_ref
唯一索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见主键或唯一索引扫描。
4> ref
非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回匹配某个条件的多行值,属于查找和扫描的混合体。由于是非唯一性索引扫描,所以我们在这里对 tb_emp 表的 deptid 字段创建索引:
create index idx_tb_emp_deptid on tb_emp(deptid);
5> range
只检索给定范围的行,使用一个索引(必须是索引)来检索行,一般出现在 where 语句的条件中,例如使用 between、>、<、in、and、or 等查询。这种索引的范围扫描比全索引扫描要好,因为索引的开始点和结束点都固定,范围相对较小。
6> index
全索引扫描,index 和 ALL 的区别:index 只遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。虽说 index 和 ALL 都是全表扫描,但是 index 是从索引中读取,ALL 是从磁盘中读取。
7> ALL
全表扫描。
⑥ possible_keys、key 和 key_len
possible_keys:显示可能应用在表中的索引,可能一个或多个。查询涉及到的字段若存在索引(如下图所示,deptid 列和 name 列均设置了单值索引),则该索引将被列出,但不一定被查询实际使用。
key:实际中使用的索引,如果为 NULL,则表示未使用索引。若查询中使用了覆盖索引,则该索引和查询的 select 字段重叠。覆盖索引的定义:select 的数据列只从索引中就能取得数据,不必读取数据行。
key_len:表示索引中所使用的字节数,可通过该列计算查询中使用的索引长度。在不损失精确性的情况下,长度越短越好。key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,并不是通过表内检索得出的。
简单来说,possible_keys 表示理论上可能用到的索引,key 表示实际中使用的索引。
possible_keys 为 NULL 表示可能未用到索引,但 key=idx_tb_emp_deptid_name 表示在实际查询的过程中进行了索引的全扫描。
在使用索引查询时,当条件越精确,key_len 的长度可能会越长,所以在不影响结果的情况下,key_len的值越短越好。
⑦ ref
显示关联的字段。如果使用常数等值查询,则显示 const;如果是连接查询,则会显示关联的字段。如果使用的列不是索引列,那么 ref 会显示为 NULL。
注意:
⑧ rows
根据表统计信息及索引选用情况大致估算出找到所需记录所要读取的行数,即每张表有多少行被优化器查询,当然该值越小越好。
⑨ filtered
百分比值,表示存储引擎返回的数据经过滤后,剩下多少满足查询条件记录数量的比例。
⑩ Extra
显示十分重要的额外信息。其取值有以下几个:
1> Using filesort
Using filesort 表明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。mysql 中无法利用索引完成的排序操作称为“文件排序”。
出现 Using filesort 就非常危险了,在数据量非常大的时候几乎“九死一生”。出现Using filesort 尽快优化 sql 语句。
比如 deptname 字段未建索引的情况:
为 deptname 字段创建索引后:
2> Using temporary
使用了临时表保存中间结果,常见于排序 order by 和分组查询 group by。非常危险,“十死无生”,急需优化。
例如,将 tb_emp 中 name 的索引先删除,出现如下图 Using temporary 结果,非常烂,“十死无生”:
为 name 字段创建索引后:
3> Using index
表明相应的 select 操作中使用了覆盖索引,避免访问表的额外数据行,效率不错。如果同时出现了 Using where,表明存在索引被用来执行索引键值的范围查询(where id>1);如果没有同时出现 Using where,表明索引未用来执行范围查询动作。
例如,删除 tb_emp 表中 name 和 deptid 字段的单独索引,创建复合索引。
上面的例子中,我们创建了(name, deptid)的复合索引,查询的时候也使用复合索引或部分,这就形成了覆盖索引。简记:查询使用复合索引,并且查询的列就是索引列,不能多,个数需对应。
使用优先级 Using index > Using filesort > Using temporary,也就说出现后面两项表明 sql 语句是非常烂的,急需优化!!!
EXPLAIN(执行计划)包含的信息十分的丰富,着重关注以下几个字段信息:
着重关注上述五个字段信息,对日常生产过程中调优十分有用。
我们假设创建了如下三个表:
①首先对员工表和描述表执行 LEFT JOIN 操作:
select e.id, e.username, d.empid from tb_emp e left join tb_desc d on e.id=d.empid;
explain select e.id, e.username, d.empid from tb_emp e left join tb_desc d on e.id=d.empid;
结果如下:
从 explain 执行结果可以看出对两表都是用了全表扫描(ALL),并且在 tb_desc 表中还使用了 join 连接缓存,需要进行优化。但是如何优化?是在左表建立索引还是右表建立索引呢?
因为左连接左表是全有,所以左表的每一行数据都会包含,因此建立索引没有太大意义,而右表不一定包含所有的数据行,因此应该在右表建立索引实现快速查找。
③右表创建索引:
create index idx_empid on tb_desc(empid);
通过 explain 执行可以看到,在创建索引后,获得了比较不错的结果(type=ref,Extra=Using index)。
结论:left join(左连接)情况下,应该在右表创建索引。
④ RIGHT JOIN
我们直接交换两表位置,并将 left join 改变成 right join:
explain select e.id, e.username, d.empid from tb_desc d right join tb_emp e on e.id=d.empid;
结果如下:
与 left join 进行对比,可以得到如下结论:
索引优化的目的主要是让索引不失效。
我们假设创建了如下表格:
①最佳左前缀法则
法则:在利用多列创建了复合索引的情况下,查询从索引的最左列开始且不能跳过索引中的列。
简单来说,如果利用多个列创建了复合索引,在使用索引时要按照创建索引的顺序来使用,不能缺少或跳过,当然只依次使用左边的索引列是可以的。通俗理解:“带头大哥不能死,中间兄弟不能断”。要点:“头不能掉”。
Case 1
下面创建组合索引,并执行 explain:
create index idx_nameagegender on tb_emp (name,age, gender);
explain select * from tb_emp where name= 'Tom' ;
explain select * from tb_emp where name='Tom' and email="[email protected]";
Case 2
explain select * from tb_emp where age=22;
Case 3
explain select * from tb_emp where name='Tom' and age=22;
explain select * from tb_emp where name='Tom' and gender= 'male';
explain select * from tb_emp where age=22 and name='Tom';
Case 4
explain select * from tb_emp where name='Tom'and age=22 and gender= 'male';
②不要在索引列上做任何操作
在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效从而转向全表扫描。
Case 1
explain select * from tb_emp where left(name,3)='Tom';
Case 2
explain select * from tb_emp where name=123;
③范围右边全失效
存储引擎不能使用索引中范围右边的列,也就是说范围右边的索引列会全部失效。这里的右边指的是创建复合索引时的顺序,而不是 where 子句中的顺序。
Case 1
explain select * from tb_emp where name='Jack';
explain select * from tb_emp where name='Jack' and age=27;
explain select * from tb_emp where name='Jack' and age=27 and gender='male';
explain select * from tb_emp where name='Jack' and age>27 and gender='male';
④尽量使用覆盖索引
尽量使用覆盖索引(查询列和索引列尽量一致,通俗说就是对 A、B 列创建了索引,然后查询中也使用 A、B 列),减少 select * 的使用。
Case 1
explain select * from tb_emp where name='Jack' and age=27 and gender='male';
explain select name, age, gender from tb_emp where name='Jack' and age=27 and gender='male';
结果如下:
分析:
⑤使用不等于(!=或<>)会使索引失效
Case 1
explain select * from tb_emp where name !='Jack';
⑥ is null 或 is not null也无法使用索引
Case 1
explain select * from tb_emp where name is null;
explain select * from tb_emp where name is not null;
⑦ like 谓词以 % 开头会使索引失效
Case 1
explain select * from tb_emp where name like '%JACK%';
explain select * from tb_emp where name like '%JACK';
explain select * from tb_emp where name like 'JACK%';
在实际生产环境中,% 仅出现在右边可能不能够解决我们的问题,所以解决 % 出现在左边导致索引失效的方法是使用覆盖索引:
Case 4
explain select name from tb_emp where name like '%JACK%';
Case 5
explain select id from tb_emp where name like '%JACK%';
Case 6
explain select age from tb_emp where name like '%Jack%';
explain select name, age from tb_emp where name like '%Jack%';
explain select name, age, gender from tb_emp where name like '%Jack%';
explain select id, name, age, gender from tb_emp where name like '%Jack%';
Case 7
explain select id, name, age, gender, email from tb_emp where name like '%Jack%';
⑧字符串不加单引号导致索引失效
varchar类型的字段,在查询的时候不加单引号会导致索引失效,转向全表扫描。其实这种情况我们在前面情况②中已经讨论过了,相当于在进行比较时索引列发生了类型的自动转换,导致索引失效。
⑨少用 or,因此用 or 连接会使索引失效
Case 1
explain select * from tb_emp where name='Jack'or name='Mary ';
我们假设创建了如下表格:
我们对表的 c1、c2、c3 和 c4 字段创建复合索引:
create index idx_test_c1234 on test(c1,c2,c3,c4);
Case 1:常量等值查询索引列的顺序
explain select * from test where c1='a1' and c2='a2' and c3='a3' and c4='a4';
explain select * from test where c1='a1' and c3='a3' and c2='a2' and c4='a4';
explain select * from test where c1='a1' and c4='a4' and c3='a3' and c2='a2';
explain select * from test where c4='a4' and c3='a3' and c2='a2' and c1='a1';
结论:
Case 2:范围右边索引列
explain select * from test where c1='a1' and c2='a2';
explain select * from test where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
结论:
explain select * from test where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
结论:
Case 3:order by 中的索引列
explain select * from test where c1='a1' and c2='a2' and c4='a4' order by c3;
explain select * from test where c1='a1' and c2='a2' order by c3;
explain select * from test where c1='a1' and c2='a2' order by c4;
explain select * from test where c1='a1' and c5='a5' order by c2,c3;
explain select * from test where c1='a1' and c5='a5' order by c3,c2;
explain select * from test where c1='a1' and c2='a2' order by c2,c3;
explain select * from test where c1='a1' and c2='a2' and c5='a5' order by c2,c3;
explain select * from test where c1='a1' and c2='a2' order by c3,c2;
Case 4:group by 中的索引列
explain select * from test where c1='a1' and c4='a4' group by c2,c3;
explain select * from test where c1='a1' and c4='a4' group by c3,c2;
通过以上 Case 的分析,进行如下总结:
在使用 order by 时,经常出现 Using filesort,因此对于此类 sql 语句需尽力优化,使其尽量使用 Using index。
我们假设创建了如下表格:
我们对表的 c1、c2、c3 和 c4 字段创建复合索引:
create index idx_c1234 on test(c1,c2,c3,c4);
Case 1
explain select * from test where c1>'a1' order by c1;
explain select * from test where c1>'a1' order by c2;
explain select * from test where c1 in ('a1','a2','a3') order by c2,c3;
c1 in ('a1','a2','a3')
使用了范围查询,这相当于 c1 使用了范围,因此 c2 索引失效,Extra 中出现了 Using filesort。explain select * from test where c1='a1' and c2>'a2' order by c2;
Case 2
explain select * from test where c1>'a1' order by c1,c2;
explain select * from test where c1>'a1' order by c2,c1;
explain select * from test where c2>'a2' order by c1,c2;
explain select c2 from test where c2>'a2' order by c1,c2;
explain select * from test where c1='a1' and c2>'a2' order by c2,c5;
Case 3
explain select * from test order by c2;
explain select c1 from test order by c2;
explain select c1 from test where c2>'a2' order by c2;
explain select c1 from test order by c1 asc,c2 desc;
explain select c3 from test order by c1 desc,c2 desc;
Case 4
explain select * from test order by c1,c2;
explain select c3 from test order by c1,c2;
具体来说,filesort 有两种排序算法,分别是双路排序和单路排序。
双路排序
在 MySQL4.1 之前使用双路排序,就是两次磁盘扫描,得到最终数据。从磁盘中读取行指针(rowid)和 order by 排序列,对它们进行排序,然后扫描已经排好序的列表,按照列表中的值重新从磁盘中读取对应的数据输出。即从磁盘读取排序字段,在 buffer 进行排序,再从磁盘取其他字段。
双路排序步骤如下:
如果使用双路排序,取一批数据要对磁盘进行两次扫描,众所周知,I/O 操作是很耗时的,因此在 MySQL4.1 以后,出现了改进的算法:单路排序。
单路排序
从磁盘中查询所需的列,按照 order by 列在 buffer 中对它们进行排序,然后扫描排序后的列表进行输出。它的效率更高一些,避免了第二次读取数据,并且把随机 I/O 变成了顺序 I/O,但是会使用更多的空间,因为它把每一行都保存在内存中了。
单路排序步骤如下:
两种排序算法的区别:
MySQL根据 max_length_for_sort_data 变量来确定使用哪种算法,默认值是1024字节,如果需要返回的列的总长度大于 max_length_for_sort_data,使用第一种算法,否则使用第二种算法。
解决方式有:
提升 order by 速度的方式有:
group by 与 order by 很类似,其实质是先排序后分组,遵照索引创建顺序的最佳左前缀法则。当无法使用索引列的时候,也要对 sort_buffer_size 和 max_length_for_sort_data 参数进行调整。注意 where 高于 having,能写在 where 中的限定条件就不要去 having 限定了。
在使用 group by 时,经常出现 Using temporary,因此对于此类 sql 语句需尽力优化,使其尽量使用 Using index。
我们假设创建了如下表格:
我们对表的 c1、c2、c3 和 c4 字段创建复合索引:
create index idx_c1234 on test(c1,c2,c3,c4);
Case 1
explain select * from test where c1>'a1' group by c1;
explain select * from test where c1>'a1' group by c2;
explain select * from test where c1='a1' and c2>'a2' group by c2;
Case 2
explain select * from test where c1>'a1' group by c1,c2;
explain select * from test where c1>'a1' group by c2,c1;
explain select * from test where c2>'a2' group by c1,c2;
explain select * from test where c1='a1' and c2>'a2' group by c2,c5;
Case 3
explain select * from test group by c2;
explain select c1 from test group by c2;
explain select c1 from test where c2>'a2' group by c2;
Case 4
explain select * from test group by c1,c2;
explain select c3 from test group by c1,c2;
慢查询日志是 MySQL 提供的一种日志记录,它记录 MySQL 中响应时间超过阈值的语句,具体指运行时间超过 long_query_time 值的sql语句,该 sql 语句会被记录到慢查询日志中。慢查询日志主要与 explain 进行联合分析。
默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要,一般不建议开启该参数,因为开启慢查询日志或多或少会带来一定的性能影响。
① 首先查看慢查询日志是否开启
show variables like '%slow_query_log%';
结果如下:
② 使用命令开启慢查询日志
当 slow_query_log 的 Value 为 OFF 时,代表慢查询日志处于关闭状态,可用以下命令来开启:
set global slow_query_log=1;
注意:
① 查看慢查询的阈值时间
该值由 long_query_time 控制。默认情况下为 10 秒。
show variables like '%long_query_time%';
② 设置 long_query_time 的值
set global long_query_time=8;
当设置 long_query_time 值后,查看其值并没有变化,解决方式:
③ 查看慢查询 sql 的数目
-- 在MySQL中执行select sleep(N)可以让此语句运行N秒钟
SELECT sleep(9);
SELECT sleep(9);
show global status like '%Slow_queries%';
查看慢查询日志文件:
从文件中可看到两条 select sleep(9) 语句。
因为直接分析日志文件是个体力活,因此 mysql 为我们提供了相关工具 mysqldumpslow 来对慢查询日志文件进行分析。具体使用方式可用 mysqldumpslow --help 命令查看具体参数。
我们假设创建了如下表格:
字段 | 含义 |
---|---|
id | 主键 |
deptno | 部门编号 |
dname | 部门名称 |
loc | 备注 |
字段 | 含义 |
---|---|
id | 主键 |
empno | 员工编号 |
empname | 员工名字 |
job | 工作 |
mgr | 上级编号 |
hiredate | 入职时间 |
sal | 薪水 |
comm | 红利 |
deptno | 部门编号 |
由于在创建函数时,可能会报错:This function has none of DETERMINISTIC.....
。因此我们需开启函数创建的信任功能,这一功能由 log_bin_trust_function_creators 参数来开启:
show variables like '%log_bin_trust_function_creators%';
Value 值为 OFF,说明未开启:
可通过 set global log_bin_trust_function_creators=1
的形式开启该功能,也可通过在 my.cnf 中永久配置的方式开启该功能,在 [mysqld] 下配置 log_bin_trust_function_creators=1
。
MySQL 中函数编写的基本语法如下:
create function <函数名称>([参数1 参数1类型],[参数2 参数2类型],...)
returns <返回值类型>
begin
<语句;>
return 返回值;
end;
注意:
delimiter $$
将结束标志改成 “$$”,但是在创建完函数后记得将命令结束标志改回分号。我们接下来创建两个函数:随机生成字符串的函数和随机生成编号的函数,以满足后续向表中插入不同的数据。
①创建随机生成字符串的函数
delimiter $$
drop function if exists rand_string; -- 如果函数存在就先删除
create function rand_string(n int)
returns varchar(255)
begin
declare chars_str varchar(52) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
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 $$
drop function if exists rand_num;
create function rand_num()
returns int(5)
begin
declare i int default 0;
set i=floor(100+rand()*100);
return i;
end $$
delimiter ;
注意:
show function status;
MySQL 中存储过程编写的基本语法如下:
create procedure <存储过程名称>(<IN 或 OUT 或 INOUT> 参数1 参数1类型, <IN 或 OUT 或 INOUT> 参数2 参数2类型, ...)
begin
<语句>; -- 过程体
end
注意:
我们接下来创建两个存储过程,分别用于向部门表和员工表中批量插入数据。
①创建往 tb_dept_bigdata 表中插入数据的存储过程
delimiter $$
drop procedure if exists insert_dept;
create procedure insert_dept(in max_num int(10))
begin
declare i int default 0;
set autocommit=0;
repeat
set i=i+1;
insert into tb_dept_bigdata (deptno,dname,loc) values(rand_num(),rand_string(10),rand_string(8));
until i=max_num
end repeat;
commit;
end $$
delimiter ;
注意:
set autocommit=0;
将禁止自动提交,只有当出现 commit;
时才会提交更改,保证了原子性(在事务结束时,其中所包含的更新处理要么全部执行,要么完全不执行);②创建往 tb_emp_bigdata 表中插入数据的存储过程
delimiter $$
drop procedure if exists insert_emp;
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 tb_emp_bigdata (empno,empname,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6),'developer',0001,curdate(),2000,400,rand_num());
until i=max_num
end repeat;
commit;
end $$
delimiter ;
注意:
show procedure status;
执行创建随机生成字符串的函数的 sql 语句。
执行创建随机生成编号的函数的 sql 语句。
查看函数是否创建成功。
执行创建往 tb_dept_bigdata 表中插入数据的存储过程的 sql 语句。
执行创建往 tb_emp_bigdata 表中插入数据的存储过程的 sql 语句。
查看存储过程是否创建成功。
执行存储过程,插入数据:
①首先执行 insert_dept 存储过程
call insert_dept(100);
select count(*) from tb_dept_bigdata;
说明:deptno 的范围为 [100, 200),因为 deptno 的值使用了 rand_num() 函数。
②然后执行 insert_emp 存储过程
call insert_emp(100,300);
select count(*) from tb_emp_bigdata;
注:对于部门表的 deptno 和员工表中 deptno 的数据都使用了 rand_num() 函数进行赋值,确保两边的值能对应。
删除函数与存储过程
-- 删除函数
drop function rand_num;
drop function rand_string;
-- 删除存储过程
drop procedure insert_dept;
drop procedure insert_emp;
在学习了数据批量插入后,我们接下来介绍小表驱动大表。首先给出结论:在查询的优化中,永远小表驱动大表,即小的数据集驱动大的数据集。
类似循环嵌套:
for(int i=5;.......)
{
for(int j=1000;......)
{
}
}
如果小的循环在外层,对于数据库连接来说就只连接5次,进行5000次操作,如果1000在外,则需要进行1000次数据库连接,从而浪费资源,增加消耗。这就是为什么要小表驱动大表。
在 tb_dept_bigdata 表中插入100条数据,在 tb_emp_bigdata 表中插入5000条数据:
call insert_dept(100);
call insert_emp(100,5000);
select count(*) as count_dept from tb_dept_bigdata;
select count(*) as count_emp from tb_emp_bigdata;
①当 B 表的数据集小于 A 表数据集时,用 in 优于 exists:
select * from tb_emp_bigdata A where A.deptno in (select B.deptno from tb_dept_bigdata B);
B 表为 tb_dept_bigdata:100条数据,A 表 tb_emp_bigdata:5000条数据。
用 in 的查询时间为:
将上面 sql 转换成 exists:
select * from tb_emp_bigdata A where exists(select B.deptno from tb_dept_bigdata B where B.deptno=A.deptno);
用exists的查询时间:
经对比可看到,在B表数据集小于A表的时候,用 in 要优于exists,当前的数据集并不大,所以查询时间相差并不多。
②当 A 表的数据集小于 B 表的数据集时,用 exists 优于 in:
select * from tb_dept_bigdata A where A.deptno in (select B.deptno from tb_emp_bigdata B);
用 in 的查询时间为:
将上面 sql 转换成 exists:
select * from tb_dept_bigdata A where exists(select 1 from tb_emp_bigdata B where B.deptno=A.deptno);
用 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
当 B 表的数据集必须小于 A 表的数据集时,用 in 优于 exists。
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:
select ... from table where exists(subquery);
可以理解为:将主查询的数据放入子查询中做条件验证,根据验证结果(True 或 False)来决定主查询的数据是否得以保留。
Show Profile 是 MySQL 提供的可以用来分析当前会话中 sql 语句执行的资源消耗情况的工具,可用于 sql 调优的测量。默认情况下处于关闭状态,开启后默认保存最近15次的运行结果。Show Profile 的分析步骤如下:
开启 Show Profile 功能,默认该功能是关闭的,使用前需开启。开启后只存活于当前会话,也就说每次使用前都需要开启。
show variables like 'profiling' ;
set profiling=on ;
show variables like 'profiling' ;
向十七章中创建的 tb_emp_bigdata 表中插入 50w 条数据。然后执行如下查询语句:
select * from tb_emp_bigdata group by id%10 limit 150000;
select * from tb_emp_bigdata group by id%20 order by 5;
使用 show profile 对 sql 语句进行诊断:
-- cpu、block io 为查询参数
-- Query_ID 为 show profiles 列表中的 Query_ID
show profile cpu,block io for query <Query_ID>;
因此,通过 show profiles 查看 sql 语句的耗时时间,然后通过 show profile 命令对耗时时间长的 sql 语句进行诊断。
Show Profile 语句中的常用查询参数有:
参数 | 含义 |
---|---|
ALL | 显示所有的开销信息 |
BLOCK IO | 显示块IO开销 |
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 | 出现了锁 |
如果在 show profile 诊断结果中出现了以上4条结果中的任何一条,则 sql 语句需要优化。
全局查询日志用于保存所有的 sql 执行记录,该功能主要用于测试环境,在生产环境中永远不要开启该功能。
开启全局查询日志的方法有两种:
show variables like '%general%';
set global general_log=1;
set global log_output='TABLE';
通过以上配置,执行过的 sql 语句将会记录到 mysql 库中 general_log 表里。我们在第十一章介绍 MySQL 存储引擎时,曾经对两大常用的存储引擎 MyISAM 和 InnoDB 进行了如下表的比较:
在上表中有一个比较项叫行表锁,我们当时简单地列出了 MyISAM 和 InnoDB 所支持的锁的类型,这一章我们将详细探讨表锁和行锁。
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算机资源(如 CPU、RAM、I/O 等)的争用外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
从对数据操作的类型来分,锁可以分为读锁(共享锁)和写锁(排它锁);从对数据操作的粒度来分,锁可以分为表锁和行锁。
特点:偏向 MyISAM 存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发度低。
我们创建如下所示的表:
增加表锁命令的基本语法如下:
lock table <表名1> <read 或 write>, <表名2> <read 或 write>;
查看表是否被加锁:
show open tables;
释放表锁命令:
unlock tables;
不需要加表名。
在 mylock 表上加读锁,并假设当前会话命名为 A,在 A 会话中查询 mylock 中的数据:
lock table mylock read;
select * from mylock;
再开一个会话,命名为 B,查询 mylock 中的数据:
数据查询依旧正常,没有任何问题。
在 A 会话中进行更新操作:
update mylock set name='a1' where id=1;
在 B 会话中读其他表:
select * from tb_emp;
A 会话 mylock 表的读锁,并不影响 B 会话对 mylock 表的读操作和其他表的读写操作。
在 A 会话中读其他表:
select * from tb_emp;
在 B 会话中修改 mylock 表中的内容:
update mylock set name='a1' where id=1;
出现了阻塞情况。原因是由于 A 会话对 mylock 表加锁,在锁未释放时,其他会话是不能对 mylock 表进行更新操作的。
在 A 会话中对 mylock 表进行解锁操作,注意观察 B 会话中的变化:
unlock tables;
在 A 会话中对 mylock 表加写锁:
lock table mylock write;
show OPEN TABLES where In_use >0;
在 A 会话中对 mylock 表进行读写操作:
update mylock set name='a1' where id=1;
select * from mylock;
在 A 会话中对其他表进行操作:
select * from tb_emp;
insert into tb_emp (name , age, gender, email) values ( 'lock ',1, 'male', '[email protected]');
在 B 会话中对 mylock 表进行读操作:
select * from mylock;
由于 mylock 表已经加写锁,而写锁为排它锁,因此在 B 会话中对 mylock 表进行读操作阻塞。由于 B 会话中对 mylock 的读操作都阻塞,所以其他操作也是阻塞的。
在 B 会话中对其他表进行读写操作:
select * from tb_emp;
insert into tb_emp (name , age, gender, email) values ( 'lock ',1, 'male', '[email protected]');
使用如下命令查看是否有表被锁定:
show open tables where In_use>0;
使用如下命令分析表锁:
show status like 'table%';
注意数据库引擎为 MyISAM。
简而言之,读锁会阻塞写,但是不会阻塞读,而写锁会把读和写都阻塞。此外,MyISAM 的读写锁调度是写优先,因此 MyISAM 不适合做以写为主的表的引擎,因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成长时间阻塞。
行锁偏向 InnoDB 存储引擎,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率低,但并发度高。
create index idx_lock_a on test_innodb_lock(a);
create index idx_lock_b on test_innodb_lock(b);
打开 A、B 两个会话,并在 A 会话中关闭数据库的自动提交:
set autocommit=0;
在 A 会话中做更新操作:
update test_innodb_lock set b='a1' where a=1;
select * from test_innodb_lock;
在 B 会话中做查询操作:
select * from test_innodb_lock;
B 会话中并没有读取到 A 会话中更新后的值。(读己知所写:自己更改的数据自己知道,但是如果未提交,其他人是不知道的。)
在 A 会话中做更新操作,然后在 B 会话中也做更新操作:
-- A 会话
update test_innodb_lock set b='a2' where a=1;
-- B 会话
update test_innodb_lock set b='a3' where a=1;
在 A 会话中 commit 操作,可看到B会话中发生了更新操作:
因为我们操作的是同一行数据,而由于 InnoDB 为行锁,在 A 会话未提交时,B 会话只有阻塞等待。如果操作不同行,则不会出现阻塞情况。
update test_innodb_lock set b='1000' where a=1;
update test_innodb_lock set b='2000' where a=2;
update test_innodb_lock set b='3000' where a=3;
update test_innodb_lock set b='4000' where a=4;
update test_innodb_lock set b='5000' where a=5;
-- A 会话
update test_innodb_lock set a=110 where b=1000;
-- B 会话
update test_innodb_lock set b='5001' where a=5;
间隙锁的定义:
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但不存在的记录,叫作“间隙(GAP)”。 InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
间隙锁的危害:
因为在 Query 执行过程中通过范围查找的话,会锁定整个范围内的所有索引键值,即使这个索引不存在。间隙锁有一个比较致命的弱点,就是当锁定一个范围键值后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定值范围内的任何数据。在某些场景下这个可能会对性能造成很大的危害。
间隙锁演示
我们需要对 test_innodb_lock 中的数据进行修改,修改后的数据如下:
在 A 会话中执行如下语句:
update test_innodb_lock set b='test' where a>1 and a<6;
在 B 会话中执行如下语句:
insert into test_innodb_lock values (2,'2000');
我们通常使用 for update
来锁定某一行。
在 A 会话中执行如下语句:
select * from test_innodb_lock where a=7 for update;
在B会话中对该行进行更新操作:
update test_innodb_lock set b='xxx' where a=7;
只有在 A 会话中进行了 commit 后,B 会话的更新操作才能执行:
使用如下命令:
sql show status like 'innodb_row_lock%';
各个状态量说明:
状态量 | 含义 |
---|---|
Innodb_row_lock_current_waits | 当前正在等待锁定的数量(阻塞中的数量) |
Innodb_row_lock_time | 从系统启动到现在等待锁定的总时长 |
Innodb_row_lock_time_avg | 每次等待锁所花平均时间 |
Innodb_row_lock_time_max | 从系统启动到现在锁等待最长的一次所花的时间 |
Innodb_row_lock_waits | 系统启动后到现在总共等待锁的次数 |
这个五个状态量中,比较重要的是:Innodb_row_lock_time、Innodb_row_lock_time_avg 和 Innodb_row_lock_waits。尤其是等待次数很高,而且每次等待时长不小时,就需要分析优化了。可以看出,Innodb_row_lock_waits * Innodb_row_lock_time_avg = Innodb_row_lock_time 。
还有一种锁叫页锁,它的开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般(了解即可)。
本章主要讲解 MySQL 主从复制的操作步骤。主机使用 Windows 环境,从机使用 Linux 环境。注意,MySQL 的版本最好一致。
slave(从机)会从 master(主机)读取 binlog 来进行数据同步。主要有以下三个步骤:
主从复制的规则如下:
在主从复制过程中,最大的问题就是延时。
要求:
MySQL 版本最好一致且后台以服务运行。并且保证主机与从机互相 ping 通。主从配置都在 [mysqld] 结点下,都是小写。
主机修改 my.ini 配置文件
log-bin = "E:\devSoft\mysql-5.7.22-winx64\data\mysql-bin"
。配置该项后,重新启动 mysql 服务,可看到如下内容:启用错误日志(可选):
log_error = "E:\devSoft\mysql-5.7.22-winx64\data\log\errorlog\log_error.log"
根目录、数据目录(可选):
# mysql安装根目录
basedir = "E:\devSoft\mysql-5.7.22-winx64"
# mysql数据文件所在位置
datadir = "E:\devSoft\mysql-5.7.22-winx64\data"
临时目录(可选):
tmpdir = "E:\devSoft\mysql-5.7.22-winx64\"
read-only=0,表示主机读写都可以。
可以设置不需要复制的数据库(可选):
binlog-ignore-db = mysql
可以设置需要复制的数据库(可选):
binlog-do-db=databasename
从机修改 my.cnf 配置文件
主机与从机都关闭防火墙,其实也可配置 ip 规则,但关闭防火墙更快速。
在 Windows 主机上建立账户并授权给 slave
# 字符%表示任何客户端都可以连接
grant all privileges on *.* to slaveaccount(用户名)@"%(或者指定ip)" identified by '你想设置的密码' with grant option;
flush privileges;
GRANT REPLICATION SLAVE ON *.* TO 'slaveaccount(上面创建的用户名)'@'从机数据库ip' IDENTIFIED BY '你想设置的密码';
查询 master 的状态:
show master status;
File 和 Position 这两个字段非常重要,File 告诉从机需要从哪个文件进行复制,Position 告诉从机从文件的哪个位置开始复制,在从机上配置时需用到。执行完此操作后,尽量不要在操作主服务器 MySQL,防止主服务器状态变化( File 和 Position 状态变化)。
在 Linux 从机上配置需要的主机
CHANGE MASTER TO MASTER_HOST='主机IP',MASTER_USER='salveaccount',MASTER_PASSWORD='主机授权的密码',MASTER_LOG_FILE='File名字',MASTER_LOG_POS=Position数字;
启动从服务器复制功能
start slave;
启动复制功能后,需要查看主从复制配置是否成功:
注意,只有当 Slave_IO_Running 和 Slave_SQL_Running 都为 Yes 的时候,主从复制配置才成功。
进行测试
停止从服务复制功能:
stop slave;