MySQL for OPS 02:SQL 基础

写在前面的话

上一节主要谈谈 MySQL 是怎么安装的以及最简单的初始化我们应该做哪些配置。其中也用到了一些简单的用户操作 SQL,所以这一节主要学习常用的 SQL 使用。

SQL 介绍

在了解 SQL 之前,对于 SQL 需要有以下简单的认知:

1. 主流的来个标准,SQL92 和 SQL99,在 MySQL 5.7 以后采用的是 SQL92。

2. 在 5.7 中新增了 sql_mode,作用在于限制哪些 SQL 能够使用,一个很明显的例子是 group by 的使用。

在上一节简单说过常用的 SQL 分类(主要前 3 个):

DDL:数据定义语言

DCL:数据控制语言

DML:数据操作语言

DQL:数据查询语言

数据类型

这算是数据定义过程中的一个重点,针对不同的数据我们给定不同的数据类型,作用在于保证数据的准确性和标准性。

数值类型:

类型说明
tinyint 整数 很小,0 - 255
smallint 整数 较小,-2^15 - 2^15
mediumint 整数 中等,很少用
int 整数 常规,-2^31 - 2^31
bigint 整数 较大,-2^63 - 2^63
float 浮点数 小型单精度浮点数,四个字节
double 浮点数 常规单精度浮点数,八个字节
decimal 定点数 包含整数部分,小数部分或者同时包含二者精确数值
bit BIT 位字段值

字符类型:

类型说明
char 文本 固定长度字符串,最多 255 个字符
varchar 文本 可变长度字符串,最多 65535 个字符
tinytext 文本 可变长度字符串,最多 255 个字符
text 文本 可变长度字符串,最多 65535 个字符
mediumtext 文本 可变长度字符串,最多 1600万+ 个字符
longtext 文本 可变长字符串,最多 42亿+ 字符
enum 整数 一组固定合法值组成的枚举
set 整数 一组固定合法值组成的集

在生产中最容易出现的就是字符串字段长度不足的问题,所以在设计的时候注意选对类型。

char 之所以是定长,比如我们定义 char(10),那就意味着传的最大长度是 10,如果不够补充空格,反正就是占用 10 个字符。

varchar 相比之下,则是在指定的范围内按需分配,如 varchar(10),最大长度 10,不足就不足,不浪费。

至于 enum,则属于特别的使用,如本字段的值是指定范围,如 enum("北京", "上海", "广州", "深圳"),这样使用能够优化索引。但用的其实并不多。

时间类型:

类型格式示例
date YYYY-MM-DD 2019-08-08
time hh:mm:ss[.uuuuuu] 10:50:29.123456
datetime YYYY-MM-DD hh:mm:ss[.uuuuuu] 2019-08-08 10:50:29.123456
timestamp YYYY-MM-DD hh:mm:ss[.uuuuuu] 2019-08-08 10:50:29.123456
year YYYY 2019

timestamp 会受到时区的影响,且范围有限制。不是很建议。

二进制类型(这类不建议存到 MySQL):

类型说明
binary 二进制 类似 char 固定长度,但存储的是二进制
varbinary 二进制 类型 varchar
tinyblob blob 最大长度 255 的 blob 列
blob blob 最大长度 65535 的 blob 列
mediumblob blob 最大长度 1600万+ 的 blob 列
longblob blob 最大长度 42亿+ 的 blob 列

表属性

1. 列属性(主要关键字):

primary key:主键,非空唯一约束,一个表只能有一个,但是能由多个列组成。

not null:非空约束,属于设计规范,尽可能不要列空,可以使用默认值 0 替代空。

unique key:唯一键,值不能重复 。

unsigned:无符号,主要用于数字列,非负数。

key:索引,可以给某列建立索引来优化查询。

default:默认值,列没有值时默认填充。

auto_increment:自增,主要针对数字,顺序填充数据,默认 1 开始,可以设置起始值和偏移量。

comment:注释。

2. 表属性:

存储引擎:在 5.7 默认是 innodb,在老版本中可能是 MyISAM。

字符集:常见的 utf8,utf8mb4 等。

校对(排序)规则:如 utf8_general_ci,utf8_bin 这种。主要用于大小写是否敏感。

可以通过以下 SQL 查看系统支持:

# 查看编码
show charset;

# 查看排序规则
show collation;

数据库操作(DDL)

1. 创建数据库并指定查看字符集:

# 默认创建
create database a;
show create database a;

# 指定字符集创建
create database b charset utf8mb4;
show create database b;

# 指定字符集和排序规则创建
create database c charset utf8mb4 collate utf8mb4_bin;
show create database c;

结果如下:

可以看到,MySQL 默认不指定字符集创建数据库的时候,创建的数据库的字符集为拉丁(latin1)

排序规则 ci 结尾的都是大小写不敏感的。bin 大小写敏感。

当然,创建数据库还可以使用:

create schema d;

这样也是能够创建数据库的。

建库规范:

a. 库名不应该包含大小写。

b. 库名不该以数字开头。

c. 建库一定要加字符集。

d. 库名要有意义。

特别注意:

禁止生产种执行 drop database xxx;

2. 修改数据库字符集:

alter database a charset utf8mb4;

查看:

但是值得注意的是,修改后的字符集必须比之前的字符集范围更大。原因是数据的兼容性。

同时,不到万不得已一般不要修改。

数据表操作(DDL)

语法格式:

create table students(
列1 属性(数据类型, 约束, 其它),
列2 属性,
...
)

1. 创建一个名为学校的测试库,创建一张名为学生的用户表:

# 创建库
create database school charset utf8mb4 collate utf8mb4_bin;

# 指定库
use school;

# 创建表
create table students (
id int not null primary key auto_increment comment "学号",
sname varchar(255) not null comment "姓名",
sage tinyint unsigned not null default 0 comment "年龄",
sgender enum("m", "f", "n") not null default "n" comment "性别",
id_card char(18) not null unique comment "身份证",
add_time timestamp not null default now() comment "入学时间"
) engine=innodb charset=utf8mb4 comment "学生表";

建表规范:

a. 表名小写,不能数字开头且具有意义。

b. 选择合适的数据类型,字符集,存储引擎。

c. 每个列都需要有注释说明且非空,如果为空选择 0 代替。

特别注意:

禁止生产种执行 drop table xxx;

2. 查看表结构:

desc students;

结果如图:

也可以查看建表语句:

show create table studentsG

3. 添加列:

a. 直接添加手机号列:

alter table students add mobile varchar(20) not null unique comment "手机号";

如图:

默认添加列加到最后面。

b. 在 id_card 后面添加微信列:

alter table students add wechat varchar(20) not null unique comment "微信" after id_card;

如图:

c. 在最前面加个列:

alter table students add school_id tinyint not null comment "学校编号" first;

如图:

4. 修改列:

a. 添加 qq 列,然后删除它:

# 添加
alter table students add qq varchar(20) not null unique comment "QQ";
desc students;

# 删除
alter table students drop qq;
desc students;

添加:

删除:

b. 修改列属性:

alter table students modify mobile varchar(15);

如图:

可以发现,虽然只是修改了 varchar,但是 null 也修改了。所以修改的时候建议多以属性都加一遍。

c. 修改列名和数据类型:

alter table students change sgender sg char(1) not null default 'n';

我们这里将 sgender 改为 sg,并修改类型:

在过去的版本中,我们应该避免在业务高峰期修改表结构,因为这会导致数据库锁表。

但可以使用 pt-osc 工具(Percona 的),可以在线修改,不再锁表,原理在于创建一个新表。

当然,在 MySQL 8.0 以后的版本以及自身集成了该工具。

5. 复制表结构建立一张新表:

create table t1 like students;

对于 DCL,主要就两个,一个是 grant,一个是 revoke。

数据增删改(DML)

1. 插入数据:

a. 最标准的 insert 语法:

insert into students(school_id,id,sname,sage,sg,id_card,wechat,add_time,mobile) values (11,1,'张三',18,'m','511123199311111214','13290909801',now(),'13290909801');

b. 省事写法:

由于我们每个字段都按照顺序写,所以没必须要把字段列出来。

insert into students values (11,2,'李四',19,'f','511123199311111124','13290222201',now(),'13290222201');

c. 部分插入:

因为有些字段是由默认值的,所以我们可以就使用默认值:

insert into students(school_id,sname,sage,id_card,wechat,mobile) values (11,'王五',18,'511123199311112224','13290909221','13290909221');

d. 同时插入多个:

insert into students(school_id,sname,sage,id_card,wechat,mobile) values
(11,'老赵',12,'511123133311112224','13233909221','13233909221'),
(11,'老钱',16,'511333133311112224','13333909221','13333909221'),
(12,'老孙',25,'511113133311112224','13111909221','13111909221');

e. 查看插入结果:

select * from students;

结果如图:

2. 修改数据:

a. 把张三的年龄改为 20:

update students set sage=20 where sname='张三';

  

b. 把所有名字老开头的性别改为 m:

update students set sg='m' where sname like '老%';

结果如图:

c. 同时修改多个值:

update students set sage=25,sg='f' where sname='张三';

结果如图:

3. 删除数据:

delete from students where sname="老孙";

不推荐使用!!!

清空表的方法:

delete from students;

delete 逐行全部删除,属于 DDL 操作,速度慢!!!

同时,我们可以从上面的 id 可以看到,由于发生了 delete 导致 id 不连续,确实的那一部分仍然占据着磁盘,这将导致可能数据量不大,但是磁盘占用很大的情况。这就是磁盘碎片。

truncate table students;

truncate 全部清空数据页,干干净净,属于 DML 操作,速度快。

都不推荐!!!

特别注意:

update / delete 一定要记得 where,否则原地爆炸。

在实际生产中,我们都是使用伪删除的方式,也就是新加数据状态字段,如可用为 1,不可用为 0,我们删除就将状态由 1 改为 0。

alter table students add status tinyint not null default 1 comment "数据状态";

删除就将改行数据 status 改为 0。

查询 DQL(Data Query Language)

1. 单独使用,查看系统参数:select @@xxx

select @@port;
select @@basedir;
select @@datadir;
select @@socket;
select @@server_id;

结果如下:

有些复杂的我们可以使用 show 来模糊查询:

show variables like 'log%';

如图:

2. 单独使用,调用函数:select 函数(); 

# 显示当前信息
select now();
select database();
select user();

# 打印输出
select concat("hello world");

# 定制化输出
select concat(user,"@",host) from mysql.user;

# 一行输出
select group_concat(user,"@",host) from mysql.user;

结果:

更多的函数可以查看官方文档:

https://dev.mysql.com/doc/refman/5.7/en/func-op-summary-ref.html

3. 单表子句,FROM

select * from students;

不建议在生产中使用,如果表数据太大会造成卡死。

查询指定列:

select sname,sage,mobile from students;

如图:

4. 单表子句,WHERE

在使用之前,MySQL 官方提供了专门用于学习的一个数据库:world,可以前往官网下载:

https://dev.mysql.com/doc/index-other.html

如图:

将示例的数据库导入 MySQL 中,当然也可以下载 world_x,那个是新数据,只是测试没必要:

包含三张表,城市,国家,国家语言。可以使用 desc 了解每张表的数据结构。

a. 等值查询:查询中国(CHN)的城市

select * from city where CountryCode="CHN";

结果:

也可以单独查询深圳:

select * from city where Name="shenzhen";

如图:

b. 比较查询:>,<,>=,<=,<>

查询世界人口小于 100 的城市:

select * from city where population<100;

如图:

c. 逻辑查询:and,or

 查询中国人口大于 500 万的:

select * from city where countrycode="CHN" and population>=5000000;

如图:

查询中国或美国的城市:

select * from city where countrycode="CHN" or countrycode="USA";

d. 模糊查询:%

查询 bei 开头的:

select * from city where name like "bei%";

查询名字中包含 bei 的(不走索引,性能极差,不推荐):

select * from city where name like "%bei%";

如图:

e. 列表匹配:in(类似 or)

select * from city where countrycode in ("CHN","USA");

如图:

f. 查询范围:between and(类似 >= and <=)

查询人口在 830000 和 840000 之间的城市:

select * from city where population between 830000 and 840000;

换成 and 写为:

select * from city where population>=830000 and population<=840000;

结果:

5. GROUP BY + 聚合函数

常用的聚合函数有以下一些:

最大值:max()
最小值:min()
平均值:avg()
求和:sum()
统计:count()
列转行:group_concat()

a. 统计每个国家的人口数量:

select countrycode,sum(population) from city group by countrycode;

结果:

b. 统计中国各省总人口:

select District,sum(population) from city where countrycode="CHN" group by District;

结果:

3. 统计世界上每个国家城市数量:

select CountryCode,count(name) from city group by CountryCode;

结果:

6. 单表子句:HAVING

例如统计中国各省总人口数,只显示小于 100 万的。

select district,sum(population) from city where countrycode="CHN" group by district having sum(population)<1000000;

如图:

之所以使用 having,是因为 where 有顺序要求,分别是 where -- group by -- having

在 group by 之后只能使用 having 不能再用 where 了。另外 having 后条件不走索引。

7. 排序和限制:ORDER BY + LIMIT

a. 查询中国的城市信息,并按照人口升序排序。

select * from city where countrycode="CHN" order by population;

如图:

b. 统计各省总人口数并按照降序排列。

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc;

默认升序,降序需要 desc

c. 统计全国各省人口大于 500 万的按照降序排列并取前三。

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc limit 3;

如图:

当然,limit 也可做限制,如:

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc limit 2,3;

limit n,m,这意味着跳过前面的 n 行,然后显示 m 行,于是结果为:

当然也可以另外的写法:limit m offset n,一个意思。

8. 去重复:DISTINCT

查询所有国家:

select distinct(countrycode) from city;

如果只是查询 countrycode 会有很多重复数据,可以通过 distinct 去掉重复:

9. 联合查询:union all

之前查询中国和美国使用了 and 和 in 的方法,但这并不是性能最优的方法。最好的是使用 union all:

select * from city where countrycode="CHN" union all select * from city where countrycode="USA";

值得注意的是 union 会去掉重复数据,而 union all 不会去重复。 

10. 多表连接查询:join

首选准备 4 张表,关系是这样的:

建表语句:

-- 删掉旧数据新建数据库
drop database school;
create database school charset utf8mb4 collate utf8mb4_bin;
use school;

-- 学生表
create table student(
sno int not null primary key auto_increment comment "学号",
sname varchar(20) not null comment "姓名",
sage tinyint unsigned not null comment "年龄",
sgender enum("m","f","n") not null default "n" comment "性别"
) engine=innodb charset=utf8mb4;

-- 课程表
create table course(
cno int not null primary key auto_increment comment "课程编号",
cname varchar(20) not null comment "课程名称",
tno int not null comment "教师编号"
) engine=innodb charset=utf8mb4;

-- 学生成绩表
create table sc(
sno int not null comment "学号",
cno int not null comment "课程编号",
score tinyint not null default 0 comment "成绩"
) engine=innodb charset=utf8mb4;

-- 教师表
create table teacher(
tno int not null primary key auto_increment comment "教师编号",
tname varchar(20) not null comment "教师名字"
) engine=innodb charset=utf8mb4;

基础数据:

-- 学生信息
INSERT INTO student VALUES
(1,'张三',18,'m'),
(2,'李四',18,'m'),
(3,'王五',18,'m'),
(4,'老赵',19,'f'),
(5,'老钱',20,'m'),
(6,'老孙',20,'f'),
(7,'老李',25,'m');

-- 教师信息
INSERT INTO teacher VALUES
(101,'李老师'),
(102,'张老师'),
(103,'王老师');

-- 课程信息
INSERT INTO course VALUES
(1001,'linux',101),
(1002,'python',102),
(1003,'mysql',103);

-- 学生成绩
INSERT INTO sc VALUES
(1,1001,80),
(1,1002,59),
(2,1002,90),
(2,1003,100),
(3,1001,99),
(3,1003,40),
(4,1001,79),
(4,1002,61),
(4,1003,99),
(5,1003,40),
(6,1001,89),
(6,1003,77),
(7,1001,67),
(7,1003,82); 

最后效果:

多表查询测试:

a. 统计张三学习了几门课:

select st.sname,count(sc.cno) as course_nums from student as st join sc on st.sno=sc.sno where st.sname="张三";

这里用到的知识有:

1. 通过 as 可以对字段就行取别名,便于后面书写使用。

2. 在一开始不知道这么写的时候可以选择将 select 和 from 之间的内容替换为 *,然后再根据需求修改。

结果:

b. 查询张三学习的课程名称:

同样可以先查出所有信息:

select * from student as st join sc on st.sno=sc.sno join course as co on co.cno=sc.cno where st.sname="张三";

结果:

然后我们只需要姓名列和课程名称列:

select st.sname,co.cname from student as st join sc on st.sno=sc.sno join course as co on co.cno=sc.cno where st.sname="张三";

结果:

c. 查询李老师教的学生名字:

select te.tname,co.cname,st.sname from teacher as te join course as co on co.tno=te.tno join sc on sc.cno=co.cno join student as st on st.sno=sc.sno where te.tname="李老师";

结果:

d. 计算李老师所教课程的平均分:

select avg(sc.score) from teacher as te join course as co on te.tno=co.tno join sc on co.cno=sc.cno where te.tname="李老师";

结果:

e. 计算每个老师的平均分,并降序排列:

select te.tname,avg(sc.score) from teacher as te join course as co on te.tno=co.tno join sc on co.cno=sc.cno group by te.tname order by avg(sc.score) desc;

结果:

f. 查询李老师80分以下的学生:

select * from teacher as te join course as co on te.tno=co.tno join sc on sc.cno=co.cno join student as st on st.sno=sc.sno where te.tname="李老师" and sc.score<80;

结果:

g. 查询所有老师成绩不及格的:

select * from teacher as te join course as co on co.tno=te.tno join sc on sc.cno=co.cno join student as st on sc.sno=st.sno where sc.score<60;

结果:

h. 查询平均成绩大于 60 的学生:

select st.sname,avg(score) from sc join student as st on st.sno=sc.sno group by sc.sno having avg(score)>60;

结果:

这里值得注意的是,由于 group by 后面不能使用 where,所以筛选条件变成 having

i. 显示各门成绩的最高分最低分:

select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

结果:

11. information_schema 视图库:

在说明这个之前,可以简单了解以下视图。可以这样理解,视图就是对复杂 SQL 的封装,举个例子:

select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

这是上面一条复杂的查询,如果每次用这个就写一次,这么长肯定很麻烦,这就可以将它保存为视图:

create view mytest_view as select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

可以在前面增加创建视图:create view 视图名字 as

此时就可以直接使用:

select * from mytest_view;

结果:

在 MySQL 5.7 中,有三个库用于存储视图:information_schema,performance_schema,sys

这里主要谈谈 information_schema 中的 tables 表,其中主要的字段包括:

字段名称作用
TABLE_SCHEMA 库名
TABLE_NAME 表名
ENGINE 引擎
TABLE_ROWS 表的行数
AVG_ROW_LENGTH 表中行的平均大小(字节)
DATA_LENGTH 数据占用空间大小(字节)
INDEX_LENGTH 索引占用空间大小(字节)

可以直接查询:

select TABLE_SCHEMA,TABLE_NAME,ENGINE,TABLE_ROWS,AVG_ROW_LENGTH,DATA_LENGTH,INDEX_LENGTH from tables;

示例:

a. 显示每个库都有哪些表:

select TABLE_SCHEMA,group_concat(TABLE_NAME) from information_schema.tables group by TABLE_SCHEMA;

结果类似:

| world              | countrylanguage,country,city|

b. 统计所有库下表的个数:

select TABLE_SCHEMA,count(TABLE_NAME) from information_schema.tables group by TABLE_SCHEMA;

结果:

c. 统计 world 下面每张表所占的磁盘:

select TABLE_NAME,concat((DATA_LENGTH+INDEX_LENGTH)/1024, " KB") as LENGTH from information_schema.tables where TABLE_SCHEMA="world";

结果:

当然会发现小数位数太多,没啥意义,可以设置小数:保留两位

select TABLE_NAME,concat(round((DATA_LENGTH+INDEX_LENGTH)/1024, 2)," KB") as LENGTH from information_schema.tables where TABLE_SCHEMA="world";

结果:

d. 统计所有库占用大小:

select TABLE_SCHEMA,concat(round(sum(DATA_LENGTH + INDEX_LENGTH)/1024,2)," KB") as DB_LENGTH from information_schema.tables group by TABLE_SCHEMA;

结果:

e. 统计整个库占用的空间:

select concat(sum(DATA_LENGTH + INDEX_LENGTH)/1024," KB") as TOTAL_LENGTH from information_schema.tables;

结果:

f. 假设现在有 1000 张表,需要单独生产备份语句,就需要用到 SQL 拼接:

mysqldump -uroot -p123 world city >/tmp/world_city.sql

这是备份单个表的语句,如果需要备份 1000 个,可以使用 SQL 来生成:

select concat("mysqldump -uroot -p123 ",TABLE_SCHEMA," ",TABLE_NAME," >/tmp/",TABLE_SCHEMA,"_",TABLE_NAME,".sql") from information_schema.tables where TABLE_SCHEMA="world";

结果如下:

但这只是单纯的 SQL 结果,可以将其输出到文件:前提是在 /etc/my.cnf 中指定安全目录 secure-file-priv=/tmp

select concat("mysqldump -uroot -p123 ",TABLE_SCHEMA," ",TABLE_NAME," >/tmp/",TABLE_SCHEMA,"_",TABLE_NAME,".sql") from information_schema.tables where TABLE_SCHEMA="world" into outfile '/tmp/1.sh'

12. show 命令如下表

命令作用
show databases; 查看数据库
show tables; 查看表
show tables from information_schema; 查看指定库的表
show create database world; 查看建库语句
show create table city; 查看建表语句
show grants for root@'%'; 查看用户授权
show charset; 查看支持的编码
show collation; 查看数据库支持的排序规则
show processlist; 查看数据库连接情况
show index from city; 查看表索引情况
show status; 查看数据库情况
show status like '%lock%'; 模糊查询数据库状态
show variables; 查看数据库配置信息
show variables like "%timeout%"; 模糊查询配置信息
show engines; 查看存储引擎
show engine innodb statusG 查看 innodb 相关信息
show binary logs; 列举所有二进制日志
show master status; 查看数据库日志位置
show binlog evnets in xxx 查看二进制日志事件
show slave statusG 查看从库状态
show relaylog events; 查看从库 relaylog

小结 

增删查改的核心语句都在这里,内容非常多!

原文地址:https://www.cnblogs.com/Dy1an/p/11323437.html