数据库是往全栈发展不得不跨过的一道坎,大家不可避免会学到用到相关知识,最近查资料的时候发现网上很多内容要么就特别深,要么不成体系,对一些希望浅尝辄止仅仅是使用一下的人不太友好。最近刚好有机会学到 MySQL,集中一些时间学习了一下 MySQL 同时做了一些笔记,每个概念基本都有代码示例,每一行都是在下手打,读者可以直接复制了代码到命令行中运行,希望对大家有所帮助~ 😜
本文介绍的知识都不是特别深,目标用户是对 MySQL 零基础或弱基础的小伙伴们,可以帮助对 MySQL 建立一些概念,至少碰到相关问题知道怎么去百度,也不会碰到后端给的数据库文件看不懂。
对于 Docker 和 CentOS 相关知识不了解的小伙伴可以看看 <手摸手带你 Docker 从入门到实践> 和 <半小时搞会 CentOS 入门必备基础知识> 两篇文章,反正 Docker 和 CentOS 也早晚会用到 😂
所有代码保存在 Github 上,可以自行 Clone 下来阅读和执行。
CentOS 版本: 7.6
MySQL 版本:8.0.21
上面这个脑图可以加文末公众号回复 「mysql脑图」 获取 xmind 源文件。
1. 什么是数据库
数据库是一个以某种有组织的方式存储的数据集合,可以将其想象为一个文件柜。
1.1 基本信息
MySQL 数据库隶属于MySQL AB公司,总部位于瑞典,后被 oracle 收购。是目前最流行的关系型数据库。
优点:
- 成本低:开放源代码,一般可以免费试用;
- 性能高:执行很快;
- 简单:很容易安装和使用。
1.2 MySQL 安装
MySQL 建议使用 Docker 安装,几行命令就安装好了,参见 <手摸手带你 Docker 从入门到实践> - 安装 MySQL
我这里的命令是:
# 创建mysql容器 docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=888888 \ -v /Users/sherlocked93/Personal/configs/mysql.d:/etc/mysql/conf.d \ -v /Users/sherlocked93/Personal/configs/data:/var/lib/mysql \ --name localhost-mysql mysql # 创建好之后进入 mysql 容器: docker exec -it localhost-mysql bash # 登录 mysql mysql -u root -p888888 复制代码
如果你机子上安装了 navicate,可以参考一下下面这个配置
选择 New Connection 之后填一下配置:
就可以看到你数据库里面的内容了。
就可以啦,效果如下图:
不用 Docker 可以去官网 MySQL Community Server 下载对应版本的 MySQL 安装包,Community Server 社区版本是不要钱的,下载安装完毕也可以,基本一直下一步就行了。
废话少说,下面直接开始知识灌体!
2. MySQL 简单使用
2.1 数据库相关术语
数据库相关的概念和术语:
- 数据库(database) 保存有组织的数据的容器;
- 表(table) 某种特定类型数据的结构化清单;
- 模式(schema) 关于数据库和表的布局及特性的信息;
- 列(column) 表中的一个字段,所有表都是由一个或多个列组成的;
- 数据类型(datatype) 所容许的数据的类型;
- 行(row) 表中的一个记录;
- 主键(primary key) 一列(或一组列),其值能够唯一区分表中每个行;
- 外键(foreign key) 表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
- 子句(clause) SQL 语句由子句构成,有些子句是必需的,而有的是可选的。比如 select 语句的 from 子句。
2.2 主键
主键的概念十分重要,它唯一标识表中每行的单个或者多个列称为主键。主键用来表示一个特定的行。
虽然并不总是都需要主键,但应尽量保证每个表都定义有主键,以便于以后的数据操纵和管理。没有主键,无法将不同的行区分开来,更新或删除表中特定行很困难。
表中的任何列都可以作为主键,只要它满足以下条件:
- 任意两行都不具有相同的主键值;
- 每个行都必须具有一个主键值(主键列不允许 NULL 值)。
在使用多列作为主键时,上述条件必须应用到构成主键的所有列,所有列值的组合必须是唯一的(单个列的值可以不唯一)。
几个普遍认可的最好习惯为:
- 不更新主键列中的值;
- 不重用主键列的值;
- 不在主键列中使用可能会更改的值。
2.3 语法规范
语法规范:
- 输入
help
或\h
获取帮助; - 不区分大小写,但建议关键字大写,表名、列名小写;
- 每条命令最好使用分号
;
或\g
结尾,仅按 Enter 不执行命令; - 每条命令根据需要,可以进行缩进、换行;
- 用
#
开头进行多行注释,用/* ... */
进行多行注释; - 输入
quit
或exit
推出 MySQL 命令行;
语法特点:
- 大小写不敏感;
- 可以写在一行或多行,可以分成多行以便于阅读和调试;
- 关键字不能被缩写也不能分行;
- 各子句一般分行写;
- 推介使用缩进提高语句的可读性;
常见的简单命令:
mysql -u root -p # –h 主机名 –u 用户名 -P 端口号 –p密码,注意-p跟密码之间不能加空格其他可以加可以不加 select version(); # 查看 mysql 服务版本 show databases; # 查看所有数据库,注意最后有 s create database [库名]; # 创建库 use [库名]; # 打开指定的库 show tables; # 查看当前库的所有表 show tables from [库名]; # 查看其他库的所有表 desc [表名]; # 查看表结构 create table [表名] ( # 创建表 列名 列类型, 列名 列类型, ); drop database [库名]; # 删除库 drop table [表名]; # 删除表 exit; # 退出 复制代码
2.4 创建表并填充数据
首先我们整点数据,方便后面的代码演示。
mysql -u root -p888888 # 输入用户名密码,进入mysql命令行 复制代码
然后在 Github 下载文件 create.sql 并运行(也可以直接复制文件里的内容到 MySQL 命令行中执行)。
如果你用的是 navicate,在上一章创建到 localhost-mysql 的连接后,运行一下即可:
同理运行另一个文件 populate.sql,填充每个表中的数据。
运行之后在命令行中 show tables
就可以看到库中的表了,如下图:
2.5 关系表
简单介绍一下刚刚创建好的表。
为了数据分类处理,顾客 customers、供应商 vendors、订单 orders、订单信息 orderitems、产品记录 productnotes、产品 products 表分别存储不同的信息。
比如供应商信息表 vendors 总每个供应商都有一个唯一标识,也就是主键 vend_id,而 products 产品表的每个产品也有一个主键 prod_id,还有一个字段 vend_id 供应商 ID 和供应商表中的 vend_id 一一对应,这就是外键。
如果你希望通过产品 ID 查到对应的供应商信息,那么就通过外键来找到另一个表中的信息。外键避免了每个产品都重复保存供应商的详细信息,只要保存供应商的 ID 就行,当供应商信息变了,比如邮箱、地址变更,也不用挨个改每一行的数据,只需更改供应商表中对应供应商信息。
这样做的好处:
- 供应商信息不重复,从而不浪费时间和空间;
- 如果供应商信息变动,可以只更新 vendors 表中的单个记录,相关表中的数据不用改动;
- 由于数据无重复,显然数据是一致的,这使得处理数据更简单。
可伸缩性(scale),能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。
2.6 数据类型
MySQL 数据类型定义了列中可以存储什么数据以及该数据怎样存储的规则。
数值型
整型:Tinyint
、Smallint
、Mediumint
、Int
(Integer
)、Bigint
,可以为无符号和有符号,默认有符号。
- 如果不设置有无符号默认是有符号,如果想设置无符号,可以添加
unsigned
关键字; - 如果插入的数值超出了整型的范围,会报 out of range 异常,并且插入临界值;
- 如果不设置长度,会有默认的长度。
小数
- 定点数:
dec(M,D)
、decimal(M,D)
- 浮点数:
float(M, D)
、double(M, D)
M 为整数部位+小数部位,D 为小数部位,M 和 D 都可以省略。如果是 decimal
,则 M 默认为 10,D 默认为 0。
字符型
- 较短的文本:
char(n)
、varchar(n)
中的 n 代表字符的个数,不代表字节个数。 - 较长的文本:
text
(长文本数据)、blob
(较长的二进制数据)。 binary
、varbinary
用于保存较短的二进制。enum
用于保存枚举。set
用于保存集合。
日期和时间类型
date
格式 YYYY-MM-DD,保存日期;time
格式 HH:MM:SS,保存时间;year
格式 YYYY,保存年;datetime
格式 YYYY-MM-DD HH:MM:SS,保存日期+时间,范围1000-9999
,不受时区印象;timestamp
时间戳,格式保存日期+时间,范围1970-2038
,受时区影响;
3. 检索数据 select
用来查询的 select
语句大概是最常用的了,用来从一个或多个表中检索信息,一条 select
语句必须至少给出两条信息:想选择什么、从什么地方选择。
# 基本语法 select [查询列表] from [表名]; # 查询单个/多个/所有字段 select cust_name from customers; select cust_name,cust_city,cust_address from customers; select `select` from customers; # 如果某字段是关键字,可以用 ` 符号包起来 select * from customers; # * 表示所有 # 查询常量值/表达式/函数 select 100; select 'zhangsan'; select 100%98; select version(); 复制代码
3.1 去重 distinct
查询出来的结果可能有多个重复值,可以使用 distinct
关键字来去重
select order_num from orderitems; # 有一些重复值 select distinct order_num from orderitems; # 将重复值去重 复制代码
3.2 限制结果 limit
select 语句返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可使用 limit
子句。
limit m
表示返回找出的前 m 行,limit m,n
表示返回第 m 行开始的 n 行,也可以使用 limit m offset n
含义和前面一样。
注意,检索出来的第一行的索引为 0 行。
3.3 完全限定表名与列名
在某些情况下,语句可能使用完全限定的列明与表名:
select orderitems.order_num from mysql_demo1.orderitems; # 上面这句等价于 select order_num from orderitems; 复制代码
4. 排序检索数据 order by
上一章从 orderitems
这个表中检索的数据是没有排序的,一般情况下返回的顺序是在底层表中出现的顺序。可以通过 order by
子句来对检索后的数据进行排序。
可以用 asc
、desc
关键字来指定排序方向。order by asc
升序、order by desc
降序,不写默认是升序。
order by
子句中可以支持单个字段、多个字段、表达式、函数、别名,一般放在句子的最后面,除了 limit
之外。
select * from orderitems order by item_price; # 按item_price升序排列 # 先按 quantity 升序排列,quantity 的值一样时按 item_price 的值升序排列 select * from orderitems order by quantity,item_price; # 先按 quantity 降序排列,quantity 的值一样时按 item_price 的值升序排列 select * from orderitems order by quantity desc,item_price; # 找到最贵订单 select * from orderitems order by item_price desc limit 1; 复制代码
5. 过滤数据 where
在 from
子句后使用 where
关键字可以增加筛选条件,过滤数据。
# 基本语法 select [查询列表] from [表名] where [筛选条件] order by [排序条件]; 复制代码
按条件表达式来筛选 >
、=
、<
、>=
、<=
、!=
、<>
、<=>
# 找出产品价格为 2.5 的产品名字 select prod_name, prod_price from products where prod_price=2.5; # 找出产品价格小于等于 10 的产品名字,并按产品价格降序排列 select prod_name, prod_price from products where prod_price <= 10 order by prod_price desc; # 找到供应商 id 不为 1003 的产品,!= 和 <> 含义一样,都是不等于 select vend_id, prod_name from products where vend_id <> 1003; 复制代码
5.1 范围检查 between and
使用 between ... and ...
指定所需范围的开始值和结束值,可以达到范围查询的效果。
注意 between and
左右数字是按小大的顺序的,调过来不行。
# 查询产品价格在 3 到 10 内的产品 select prod_name, prod_price from products where prod_price between 3 and 10; # 单独使用 and 也可以打到这个效果 select prod_name, prod_price from products where prod_price <= 10 and prod_price >= 3; 复制代码
5.2 空值检查 is (not) null
创建表时,可以指定某些列可以不包含值,即可以为 null
,null
表示无值 no value,这与字段包含 0、空字符串或仅仅包含空格不同。
使用 is null
或 is not null
可以用来判断一个值是否为 null
。
说明:
- 等于
=
和不等于<>
、!=
不能用来判断null
,只能用is null
和is not null
来判断null
<=>
安全等于号可以用来判断null
# 找出顾客中邮箱不是 null 的顾客信息 select * from customers where cust_email is not null; # 使用安全等于号来判断 null 也是可以的 select * from customers where cust_email <=> null; 复制代码
5.3 逻辑与操作符 and
操作符(operator) 用来联结或改变 where 子句中的子句的关键字,也称为逻辑操作符(logical operator)。
前文提到了 and
操作符,通过这个操作符可以增加限定条件:
# 找出供应商为 1003 提供的价格小于等于 10 的产品 select * from products where vend_id = 1003 and prod_price <= 10; 复制代码
5.4 逻辑或操作符 or
or
操作符和 and
操作符相反,这是逻辑或操作符,返回匹配任一条件的行:
# 找出id为 1003 或 1001 的供应商 select * from products where vend_id = 1003 or vend_id = 1001; 复制代码
在 and
和 or
同时出现时,会优先处理 and
,比如这句:
select * from products where vend_id = 1001 or vend_id = 1003 and prod_price >= 10; 复制代码
这句会先处理 and
,表示 vend_id
为 1003 且 prod_price
大于等于 10 的产品,或者 vend_id
为 1001 的产品。
遇到这种情况,可以通过增加圆括号:
select * from products where (vend_id = 1001 or vend_id = 1003) and prod_price >= 10; 复制代码
这样检索的结果就是 vend_id
为 1001 或 1003 的产品里,所有 prod_price
大于等于 10 的产品列表了。
任何时候使用具有 and
和 or
操作符的 where
子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此,而且使用圆括号能消除歧义,增加可读性。
5.5 范围操作符 in (set)
使用 in
操作符可以指定条件范围,范围中的每个条件都可以进行匹配。in
要匹配的值放在其后的圆括号中:
# 找出id为 1003 或 1001 的供应商 select * from products where vend_id in (1001, 1003); 复制代码
in
操作符可以用 or
来取代,在以下情况建议使用 in
:
- 在选项比较多时,
in
更清楚且更直观; - 使用
in
时,计算的次序更容易管理(因为使用的操作符更少); in
一般比or
操作符清单执行更快;in
的最大优点是可以包含其他select
语句,使得能够更动态地建立where
子句。
5.6 逻辑否操作符 not
not
否操作符可以和前面的 in
和 between and
一起使用,表示对范围取反:
# 找出id不为 1001 1003 的产品 select * from products where vend_id not in (1001, 1003); # 选择产品价格不在 5 到 15 之间的产品 select * from products where prod_price not between 5 and 15; 复制代码
5.7 like 操作符
比如想找出名称中包含 anvil
的所有产品,可以通过 like
操作符来进行搜索。
like
表示后面跟的搜索模式使用通配符匹配而不是直接相等匹配。
操作符 %
最常使用的通配符是 %
操作符,%
表示任意多个字符,包括没有字符。
# 找出产品名字以 jet 开头的产品 select * from products where prod_name like 'jet%'; # 找出产品名中含有 on 的产品 select * from products where prod_name like '%on%'; # 找出产品名以 s 开头,以 e 结束的产品 select * from products where prod_name like 's%e'; 复制代码
注意,%
是无法匹配 null
的。
操作符 _
_
表示任意单个字符。
select * from products where prod_name like '_ ton anvil'; 复制代码
另外,转译使用 \
,比如 \_
# 找到描述中有 % 的产品 select * from products where prod_desc like '%\%%'; 复制代码
注意:
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
5.8 正则表达式 regexp
关于正则表达式,可以先简单看一下 「正则表达式必知必会」 这篇博客。
使用 regexp
关键字来表示匹配其后的正则表达式:
# 找到产品名以 1000 或 anvil 结尾的产品 select * from products where prod_name regexp '1000|anvil$'; 复制代码
正则表达式中转译使用 \\
,比如希望查找 .
这个字符而不是正则中的 .
通配符,使用 \\.
,为了转移 \
这个字符,使用 \\\
# 找到产品名以 . 字符开头的产品 select * from products where prod_name regexp '^\\.'; 复制代码
6. 计算字段
有时候我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化,这时我们就需要计算字段了。
6.1 别名 as
查询出来的虚拟表格可以起一个别名,方便理解,可读性好,另外如果查询的字段有重名的情况,可以使用别名 as
来区分开来。
# 使用 as 关键字 select cust_name as name from customers; # as 关键字也可以直接省略 select cust_name name from customers; # 可以给不同字段分别起别名 select cust_name name, cust_city location from customers; 复制代码
6.2 拼接 concat
想把多个字段连接成一个字段,可以使用到拼接字段函数 concat
:
# 将供应商的名字和地点拼接好后返回,并命名为 vend select concat(vend_name, '(', vend_country, ')') vend from vendors; 复制代码
注意中间如果有任何一个数据为 null
,拼接的结果也是 null
。
所以对某些可能为 null
的字段要使用 ifnull
函数判断一下,第一个参数为要判断的字段,第二个参数是如果是 null
希望返回的结果:
# 将顾客信息拼接起来 select concat(cust_name, '(', ifnull(cust_email, '-'), ')') customerInfo from customers; 复制代码
如果表中的数据前后有空格,可以使用 rtrim()
函数去除右边空格,ltrim()
去除左边空格,或者 trim()
去除前后空格:
# 将顾客信息处理后拼接起来 select concat(rtrim(vend_name), '(', trim(vend_country), ')') vend from vendors; 复制代码
6.3 算术计算 +-*/
基本的算术运算符在 select
语句中也是支持的:
# 计算订单每种总额,并按照总金额降序排列 select prod_id as id, quantity, quantity*item_price as totalPrice from orderitems order by totalPrice desc; 复制代码
基本运算符加减乘除都是支持的 +
、 -
、 *
、 /
。