提交 08bc838d 编写于 作者: 骆昊的技术专栏's avatar 骆昊的技术专栏

更新了数据分析部分的文档

上级 2d5c4b5c
......@@ -4,13 +4,13 @@
1. 数据持久化 - 将数据保存到能够长久保存数据的存储介质中,在掉电的情况下数据也不会丢失。
2. 数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL数据库、NewSQL数据库。
2. 数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL 数据库、NewSQL 数据库。
> 1970年,IBM的研究员E.F.Codd在*Communication of the ACM*上发表了名为*A Relational Model of Data for Large Shared Data Banks*的论文,提出了**关系模型**的概念,奠定了关系模型的理论基础。后来Codd又陆续发表多篇文章,论述了范式理论和衡量关系系统的12条标准,用数学理论奠定了关系数据库的基础。
3. 关系数据库特点。
- 理论基础:**集合论****关系代数**
- 理论基础:**关系代数**
- 具体表象:用**二维表**(有行和列)组织数据。
......@@ -20,177 +20,266 @@
**ER模型**,全称为**实体关系模型**(Entity-Relationship Model),由美籍华裔计算机科学家陈品山先生提出,是概念数据模型的高层描述方式,如下图所示。
![](./res/er_diagram.png)
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210826003119.png" width="75%">
- 实体 - 矩形框
- 属性 - 椭圆框
- 关系 - 菱形框
- 重数 - 1:1(一对一) / 1:N(一对多) / M:N(多对多)
实际项目开发中,我们可以利用数据库建模工具(如:PowerDesigner)来绘制概念数据模型(其本质就是ER模型),然后再设置好目标数据库系统,将概念模型转换成物理模型,最终生成创建二维表的SQL(很多工具都可以根据我们设计的物理模型图以及设定的目标数据库来导出SQL或直接生成数据表)。
实际项目开发中,我们可以利用数据库建模工具(如:PowerDesigner)来绘制概念数据模型(其本质就是 ER 模型),然后再设置好目标数据库系统,将概念模型转换成物理模型,最终生成创建二维表的 SQL(很多工具都可以根据我们设计的物理模型图以及设定的目标数据库来导出 SQL 或直接生成数据表)。
![](./res/conceptual_model.png)
![](https://gitee.com/jackfrued/mypic/raw/master/20210826003212.png)
5. 关系数据库产品。
- [Oracle](https://www.oracle.com/index.html) - 目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系数据库,它是一个完备关系的产品;作为分布式数据库,它实现了分布式处理的功能。在Oracle最新的12c版本中,还引入了多承租方架构,使用该架构可轻松部署和管理数据库云。
- [DB2](https://www.ibm.com/analytics/us/en/db2/) - IBM公司开发的、主要运行于Unix(包括IBM自家的[AIX](https://zh.wikipedia.org/wiki/AIX))、Linux、以及Windows服务器版等系统的关系数据库产品。DB2历史悠久且被认为是最早使用SQL的数据库产品,它拥有较为强大的商业智能功能。
- [SQL Server](https://www.microsoft.com/en-us/sql-server/) - 由Microsoft开发和推广的关系型数据库产品,最初适用于中小企业的数据管理,但是近年来它的应用范围有所扩展,部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。
- [MySQL](https://www.mysql.com/) - MySQL是开放源代码的,任何人都可以在GPL(General Public License)的许可下下载并根据个性化的需要对其进行修改。MySQL因为其速度、可靠性和适应性而备受关注。
- [PostgreSQL]() - 在BSD许可证下发行的开放源代码的关系数据库产品。
- [Oracle](https://www.oracle.com/index.html) - 目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系数据库,它是一个完备关系的产品;作为分布式数据库,它实现了分布式处理的功能。在 Oracle 最新的 12c 版本中,还引入了多承租方架构,使用该架构可轻松部署和管理数据库云。
- [DB2](https://www.ibm.com/analytics/us/en/db2/) - IBM 公司开发的、主要运行于 Unix(包括 IBM 自家的 [AIX](https://zh.wikipedia.org/wiki/AIX))、Linux、以及 Windows 服务器版等系统的关系数据库产品。DB2 历史悠久且被认为是最早使用 SQL 的数据库产品,它拥有较为强大的商业智能功能。
- [SQL Server](https://www.microsoft.com/en-us/sql-server/) - 由 Microsoft 开发和推广的关系型数据库产品,最初适用于中小企业的数据管理,但是近年来它的应用范围有所扩展,部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。
- [MySQL](https://www.mysql.com/) - MySQL 是开放源代码的,任何人都可以在 GPL(General Public License)的许可下下载并根据个性化的需要对其进行修改。MySQL 因为其速度、可靠性和适应性而备受关注。
- [PostgreSQL]() - 在 BSD 许可证下发行的开放源代码的关系数据库产品。
### MySQL简介
### MySQL 简介
MySQL最早是由瑞典的MySQL AB公司开发的一个开放源码的关系数据库管理系统,该公司于2008年被昇阳微系统公司(Sun Microsystems)收购。在2009年,甲骨文公司(Oracle)收购昇阳微系统公司,因此在这之后MySQL成为了Oracle旗下产品。
MySQL 最早是由瑞典的 MySQL AB 公司开发的一个开放源码的关系数据库管理系统,该公司于2008年被昇阳微系统公司(Sun Microsystems)收购。在2009年,甲骨文公司(Oracle)收购昇阳微系统公司,因此 MySQL 目前也是 Oracle 旗下产品。
MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用于中小型网站开发。随着MySQL的不断成熟,它也逐渐被应用于更多大规模网站和应用,比如维基百科、谷歌(Google)、脸书(Facebook)、淘宝网等网站都使用了MySQL来提供数据持久化服务。
MySQL 在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用于中小型网站开发。随着 MySQL 的不断成熟,它也逐渐被应用于更多大规模网站和应用,比如维基百科、谷歌(Google)、脸书(Facebook)、淘宝网等网站都使用了 MySQL 来提供数据持久化服务。
甲骨文公司收购后昇阳微系统公司,大幅调涨MySQL商业版的售价,且甲骨文公司不再支持另一个自由软件项目[OpenSolaris](https://zh.wikipedia.org/wiki/OpenSolaris)的发展,因此导致自由软件社区对于Oracle是否还会持续支持MySQL社区版(MySQL的各个发行版本中唯一免费的版本)有所担忧,MySQL的创始人麦克尔·维德纽斯以MySQL为基础,成立分支计划[MariaDB](https://zh.wikipedia.org/wiki/MariaDB)(以他女儿的名字命名的数据库)。有许多原来使用MySQL数据库的公司(例如:维基百科)已经陆续完成了从MySQL数据库到MariaDB数据库的迁移。
甲骨文公司收购后昇阳微系统公司,大幅调涨 MySQL 商业版的售价,且甲骨文公司不再支持另一个自由软件项目 [OpenSolaris ](https://zh.wikipedia.org/wiki/OpenSolaris) 的发展,因此导致自由软件社区对于 Oracle 是否还会持续支持 MySQL 社区版(MySQL 的各个发行版本中唯一免费的版本)有所担忧,MySQL 的创始人麦克尔·维德纽斯以 MySQL 为基础,创建了 [MariaDB](https://zh.wikipedia.org/wiki/MariaDB)(以他女儿的名字命名的数据库)分支。有许多原来使用 MySQL 数据库的公司(例如:维基百科)已经陆续完成了从 MySQL 数据库到 MariaDB 数据库的迁移。
1. 安装和配置
### 安装 MySQL
> **说明**:下面的安装和配置都是以CentOS Linux环境为例,如果需要在其他系统下安装MySQL,读者可以自行在网络上查找对应的安装教程)。
#### Windows 环境
- 刚才说过,MySQL有一个分支版本名叫MariaDB,该数据库旨在继续保持MySQL数据库在[GNU GPL](https://zh.wikipedia.org/wiki/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81)下开源。如果要使用MariaDB作为MySQL的替代品,可以使用下面的命令进行安装
1. 通过[官方网站](https://www.mysql.com/)提供的[下载链接](https://dev.mysql.com/downloads/windows/installer/8.0.html)下载“MySQL社区版服务器”安装程序,如下图所示,建议大家下载离线安装版的MySQL Installer
```Shell
yum install mariadb mariadb-server
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105230905.png" style="zoom:50%">
- 如果要安装官方版本的MySQL,可以在[MySQL官方网站](<https://www.mysql.com/>)下载安装文件。首先在下载页面中选择平台和版本,然后找到对应的下载链接。下面以MySQL 5.7.26版本和Red Hat Enterprise Linux为例,直接下载包含所有安装文件的归档文件,解归档之后通过包管理工具进行安装。
2. 运行 Installer,按照下面的步骤进行安装。
```Shell
wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
tar -xvf mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
```
- 选择自定义安装。
如果系统上有MariaDB相关的文件,需要先移除MariaDB相关的文件。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231152.jpg" style="zoom:35%">
```Shell
yum list installed | grep mariadb | awk '{print $1}' | xargs yum erase -y
```
- 选择需要安装的组件。
接下来可以按照如下所示的顺序用RPM(Redhat Package Manager)工具安装MySQL。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231255.jpg" style="zoom:35%">
```Shell
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
```
- 如果缺少依赖项,需要先安装依赖项。
可以使用下面的命令查看已经安装的MySQL相关的包。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231620.png" style="zoom:35%">
```Shell
rpm -qa | grep mysql
```
- 准备开始安装。
- 配置MySQL。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231719.jpg" style="zoom:35%">
MySQL的配置文件在`/etc`目录下,名为`my.cnf`,默认的配置文件内容如下所示。如果对这个文件不理解并没有关系,什么时候用到这个配置文件什么时候再了解它就行了
- 安装完成
```Shell
cat /etc/my.cnf
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232024.jpg" style="zoom:35%">
```INI
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
```
- 准备执行配置向导。
- 启动MySQL服务。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105231815.jpg" style="zoom:35%">
可以使用下面的命令来启动MySQL
3. 执行安装后的配置向导
```Shell
service mysqld start
```
- 配置服务器类型和网络。
在CentOS 7中,更推荐使用下面的命令来启动MySQL。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232109.jpg" style="zoom:35%">
```Shell
systemctl start mysqld
```
- 配置认证方法(保护密码的方式)。
启动MySQL成功后,可以通过下面的命令来检查网络端口使用情况,MySQL默认使用3306端口。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232408.jpg" style="zoom:35%">
```Shell
netstat -ntlp | grep mysql
```
- 配置用户和角色。
也可以使用下面的命令查找是否有名为mysqld的进程。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232521.jpg" style="zoom:35%">
```Shell
pgrep mysqld
```
- 配置Windows服务名以及是否开机自启。
- 使用MySQL客户端工具连接服务器。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232608.jpg" style="zoom:35%">
命令行工具:
- 配置日志。
```Shell
mysql -u root -p
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232641.jpg" style="zoom:35%">
> 说明:启动客户端时,`-u`参数用来指定用户名,MySQL默认的超级管理账号为`root`;`-p`表示要输入密码(用户口令);如果连接的是其他主机而非本机,可以用`-h`来指定连接主机的主机名或IP地址
- 配置高级选项
如果是首次安装MySQL,可以使用下面的命令来找到默认的初始密码。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232724.jpg" alt="ACAC15B8633133B65476286A49BFBD7E" style="zoom:35%">
```Shell
cat /var/log/mysqld.log | grep password
```
- 应用配置。
上面的命令会查看MySQL的日志带有password的行,在显示的结果中`root@localhost:`后面的部分就是默认设置的初始密码。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232800.jpg" style="zoom:35%">
修改超级管理员(root)的访问口令为`123456`
4. 可以在 Windows 系统的“服务”窗口中启动或停止 MySQL
```SQL
set global validate_password_policy=0;
set global validate_password_length=6;
alter user 'root'@'localhost' identified by '123456';
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105232926.jpg" style="zoom:50%">
> **说明**:MySQL较新的版本默认不允许使用弱口令作为用户口令,所以我们通过上面的前两条命令修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,攻击数据库窃取数据和劫持数据库勒索比特币的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是不要让数据库服务器暴露在公网上(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好`root`账号的口令,应用系统需要访问数据库时,通常不使用`root`账号进行访问,而是创建其他拥有适当权限的账号来访问
5. 配置 PATH 环境变量,以便在命令行提示符窗口使用 MySQL 客户端工具
再次使用客户端工具连接MySQL服务器时,就可以使用新设置的口令了。在实际开发中,为了方便用户操作,可以选择图形化的客户端工具来连接MySQL服务器,包括:
- 打开 Windows 的“系统”窗口并点击“高级系统设置”。
- MySQL Workbench(官方提供的工具)
- Navicat for MySQL(界面简单,功能直观)
- SQLyog for MySQL(强大的MySQL数据库管理员工具)
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233054.jpg" style="zoom:50%">
2. 常用命令
- 在“系统属性”的“高级”窗口,点击“环境变量”按钮
- 查看服务器版本。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233312.jpg" style="zoom:50%">
```SQL
select version();
```
- 修改PATH环境变量,将MySQL安装路径下的`bin`文件夹的路径配置到PATH环境变量中。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233359.jpg" style="zoom:50%">
- 配置完成后,可以尝试在“命令提示符”下使用 MySQL 的命令行工具。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211105233643.jpg" style="zoom:50%">
#### Linux 环境
下面以 CentOS 7.x 环境为例,演示如何安装 MySQL 5.7.x,如果需要在其他 Linux 系统下安装其他版本的 MySQL,请读者自行在网络上查找对应的安装教程。
1. 安装 MySQL。
可以在 [MySQL 官方网站](<https://www.mysql.com/>)下载安装文件。首先在下载页面中选择平台和版本,然后找到对应的下载链接,直接下载包含所有安装文件的归档文件,解归档之后通过包管理工具进行安装。
```Shell
wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
tar -xvf mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
```
如果系统上有 MariaDB 相关的文件,需要先移除 MariaDB 相关的文件。
```Shell
yum list installed | grep mariadb | awk '{print $1}' | xargs yum erase -y
```
更新和安装可能用到的底层依赖库。
```Bash
yum update
yum install -y libaio libaio-devel
```
接下来可以按照如下所示的顺序用 RPM(Redhat Package Manager)工具安装 MySQL。
```Shell
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
```
可以使用下面的命令查看已经安装的 MySQL 相关的包。
```Shell
rpm -qa | grep mysql
```
2. 配置 MySQL。
MySQL 的配置文件在`/etc`目录下,名为`my.cnf`,默认的配置文件内容如下所示。
```Shell
cat /etc/my.cnf
```
```INI
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
```
通过配置文件,我们可以修改 MySQL 服务使用的端口、字符集、最大连接数、套接字队列大小、最大数据包大小、日志文件的位置、日志过期时间等配置。当然,我们还可以通过修改配置文件来对 MySQL 服务器进行性能调优和安全管控。
3. 启动 MySQL 服务。
可以使用下面的命令来启动 MySQL。
```Shell
service mysqld start
```
在 CentOS 7 中,更推荐使用下面的命令来启动 MySQL。
```Shell
systemctl start mysqld
```
启动 MySQL 成功后,可以通过下面的命令来检查网络端口使用情况,MySQL 默认使用`3306`端口。
```Shell
netstat -ntlp | grep mysql
```
也可以使用下面的命令查找是否有名为`mysqld`的进程。
```Shell
pgrep mysqld
```
4. 使用 MySQL 客户端工具连接服务器。
命令行工具:
```Shell
mysql -u root -p
```
> 说明:启动客户端时,`-u`参数用来指定用户名,MySQL 默认的超级管理账号为`root`;`-p`表示要输入密码(用户口令);如果连接的是其他主机而非本机,可以用`-h`来指定连接主机的主机名或IP地址。
如果是首次安装 MySQL,可以使用下面的命令来找到默认的初始密码。
```Shell
cat /var/log/mysqld.log | grep password
```
上面的命令会查看 MySQL 的日志带有`password`的行,在显示的结果中`root@localhost:`后面的部分就是默认设置的初始密码。
进入客户端工具后,可以通过下面的指令来修改超级管理员(root)的访问口令为`123456`
```SQL
set global validate_password_policy=0;
set global validate_password_length=6;
alter user 'root'@'localhost' identified by '123456';
```
> **说明**:MySQL 较新的版本默认不允许使用弱口令作为用户口令,所以上面的代码修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令,因为存在用户口令被暴力破解的风险。近年来,**攻击数据库窃取数据和劫持数据库勒索比特币**的事件屡见不鲜,要避免这些潜在的风险,最为重要的一点是**不要让数据库服务器暴露在公网上**(最好的做法是将数据库置于内网,至少要做到不向公网开放数据库服务器的访问端口),另外要保管好`root`账号的口令,应用系统需要访问数据库时,通常不使用`root`账号进行访问,而是**创建其他拥有适当权限的账号来访问**。
再次使用客户端工具连接 MySQL 服务器时,就可以使用新设置的口令了。在实际开发中,为了方便用户操作,可以选择图形化的客户端工具来连接 MySQL 服务器,包括:
- MySQL Workbench(官方工具,推荐大家使用)
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106063939.png" style="zoom:50%">
- Navicat for MySQL(界面简单清爽,功能直观)
- SQLyog for MySQL(强大的MySQL数据库管理员工具)
### MySQL 常用命令
- 查看所有数据库。
......@@ -198,35 +287,67 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行
show databases;
```
- 切换到指定数据库
- 查看所有字符集(编码方式)
```SQL
use mysql;
show character set;
```
- 查看所有的校对(排序)规则。
```SQL
show collation;
```
- 查看所有的引擎。
```SQL
show engines;
```
- 查看所有日志文件。
```SQL
show binary logs;
```
- 查看数据库下所有表。
```Shell
```SQL
show tables;
```
- 获取帮助。
```SQL
? contents;
? functions;
? numeric functions;
? round;
? data types;
? longblob;
```
1. 查看`show`命令的帮助。
```MySQL
? show
```
2. 查看有哪些帮助内容。
```MySQL
? contents
```
3. 获取函数的帮助。
```MySQL
? functions
```
4. 获取数据类型的帮助。
```MySQL
? data types
```
### SQL详解
### SQL 详解
我们通常可以将SQL分为三类:DDL(数据定义语言)、DML(数据操作语言)和DCL(数据控制语言)。DDL主要用于创建(create)、删除(drop)、修改(alter)数据库中的对象,比如创建、删除和修改二维表;DML主要负责插入数据(insert)、删除数据(delete)、更新数据(update)和查询(select);DCL通常用于授予权限(grant)和召回权限(revoke)
我们通常可以将 SQL 分为四类,分别是 DDL(数据定义语言)、DML(数据操作语言)、DQL(数据查询语言)和 DCL(数据控制语言)。DDL 主要用于创建、删除、修改数据库中的对象,比如创建、删除和修改二维表,核心的关键字包括`create``drop``alter`;DML 主要负责数据的插入、删除和更新,关键词包括`insert``delete``update`;DQL 负责数据查询,最重要的一个关键词是`select`;DCL 通常用于授予和召回权限,核心关键词是`grant``revoke`
> **说明**:SQL是不区分大小写的语言,为了书写和识别方便,下面的SQL都使用了小写字母来书写。
> **说明**:SQL 是不区分大小写的语言,为了书写和识别方便,下面的 SQL 都使用了小写字母来书写。
#### DDL(数据定义语言)
......@@ -235,7 +356,7 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行
drop database if exists `school`;
-- 创建名为school的数据库并设置默认的字符集和排序方式
create database `school` default character set utf8mb4;
create database `school` default character set utf8mb4 collate utf8mb4_general_ci;
-- 切换到school数据库上下文环境
use `school`;
......@@ -245,21 +366,21 @@ create table `tb_college`
(
`col_id` int unsigned auto_increment comment '编号',
`col_name` varchar(50) not null comment '名称',
`col_intro` varchar(5000) default '' comment '介绍',
`col_intro` varchar(500) default '' comment '介绍',
primary key (`col_id`)
) engine=innodb comment '学院表';
) engine=innodb auto_increment=1 comment '学院表';
-- 创建学生表
create table `tb_student`
(
`stu_id` int unsigned not null comment '学号',
`stu_name` varchar(20) not null comment '姓名',
`stu_sex` boolean default 1 comment '性别',
`stu_sex` boolean default 1 not null comment '性别',
`stu_birth` date not null comment '出生日期',
`stu_addr` varchar(255) default '' comment '籍贯',
`col_id` int unsigned not null comment '所属学院',
primary key (`stu_id`),
foreign key (`col_id`) references `tb_college` (`col_id`)
constraint `fk_student_col_id` foreign key (`col_id`) references `tb_college` (`col_id`)
) engine=innodb comment '学生表';
-- 创建教师表
......@@ -270,7 +391,7 @@ create table `tb_teacher`
`tea_title` varchar(10) default '助教' comment '职称',
`col_id` int unsigned not null comment '所属学院',
primary key (`tea_id`),
foreign key (`col_id`) references `tb_college` (`col_id`)
constraint `fk_teacher_col_id` foreign key (`col_id`) references `tb_college` (`col_id`)
) engine=innodb comment '老师表';
-- 创建课程表
......@@ -278,30 +399,30 @@ create table `tb_course`
(
`cou_id` int unsigned not null comment '编号',
`cou_name` varchar(50) not null comment '名称',
`cou_credit` int unsigned not null comment '学分',
`cou_credit` int not null comment '学分',
`tea_id` int unsigned not null comment '授课老师',
primary key (`cou_id`),
foreign key (`tea_id`) references `tb_teacher` (`tea_id`)
constraint `fk_course_tea_id` foreign key (`tea_id`) references `tb_teacher` (`tea_id`)
) engine=innodb comment '课程表';
-- 创建选课记录表
create table `tb_record`
(
`rec_id` bigint unsigned auto_increment comment '选课记录号',
`sid` int unsigned not null comment '学号',
`cid` int unsigned not null comment '课程编号',
`stu_id` int unsigned not null comment '学号',
`cou_id` int unsigned not null comment '课程编号',
`sel_date` date not null comment '选课日期',
`score` decimal(4,1) comment '考试成绩',
primary key (`rec_id`),
foreign key (`sid`) references `tb_student` (`stu_id`),
foreign key (`cid`) references `tb_course` (`cou_id`),
unique (`sid`, `cid`)
constraint `fk_record_stu_id` foreign key (`stu_id`) references `tb_student` (`stu_id`),
constraint `fk_record_cou_id` foreign key (`cou_id`) references `tb_course` (`cou_id`),
constraint `uk_record_stu_cou` unique (`stu_id`, `cou_id`)
) engine=innodb comment '选课记录表';
```
上面的DDL有几个地方需要强调一下:
- 创建数据库时,我们通过`default charset utf8`指定了数据库默认使用的字符集,我们推荐使用该字符集,因为utf8能够支持国际化编码。如果将来数据库中用到的字符可能包括类似于Emoji这样的图片字符,也可以将默认字符集设定为utf8mb4(最大4字节的utf-8编码)。查看MySQL支持的字符集可以执行下面的语句
- 创建数据库时,我们通过`default character set utf8mb4`指定了数据库默认使用的字符集为`utf8mb4`(最大`4`字节的`utf-8`编码),我们推荐使用该字符集,它也是 MySQL 8.x 默认使用的字符集,因为它能够支持国际化编码,还可以存储 Emoji 字符。可以通过下面的命令查看 MySQL 支持的字符集以及默认的排序规则
```SQL
show character set;
......@@ -356,14 +477,14 @@ unique (`sid`, `cid`)
41 rows in set (0.00 sec)
```
如果要设置MySQL服务启动时默认使用的字符集,可以修改MySQL的配置并添加以下内容
如果要设置 MySQL 服务启动时默认使用的字符集,可以修改MySQL的配置并添加以下内容。
```INI
[mysqld]
character-set-server=utf8
```
- 在创建表的时候,我们可以在右圆括号的后面通过`engine=XXX`来指定表的存储引擎,MySQL支持多种存储引擎,可以通过`show engines`命令进行查看。MySQL 5.5以后的版本默认使用的存储引擎是InnoDB,它正好也就是我们推荐大家使用的存储引擎(因为InnoDB更适合互联网应用对高并发、性能以及事务支持等方面的需求)
- 在创建表的时候,可以自行选择底层的存储引擎。MySQL 支持多种存储引擎,可以通过`show engines`命令进行查看。MySQL 5.5 以后的版本默认使用的存储引擎是 InnoDB,它是我们推荐大家使用的存储引擎(因为更适合当下互联网应用对高并发、性能以及事务支持等方面的需求),为了 SQL 语句的向下兼容性,我们可以在建表语句结束处右圆括号的后面通过`engine=innodb`来指定使用 InnoDB 存储引擎
```SQL
show engines\G
......@@ -455,9 +576,9 @@ unique (`sid`, `cid`)
| 批量插入性能 | 低 | 高 | 高 | 高 |
| 是否支持外键 | 支持 | | | |
通过上面的比较我们可以了解到,InnoDB是唯一能够支持外键、事务以及行锁的存储引擎,所以我们之前说它更适合互联网应用,而且它也是较新的MySQL版本中默认使用的存储引擎。
通过上面的比较我们可以了解到,InnoDB 是唯一能够支持外键、事务以及行锁的存储引擎,所以我们之前说它更适合互联网应用,而且在较新版本的 MySQL 中,它也是默认使用的存储引擎。
- 在定义表结构为每个字段选择数据类型时,如果不清楚哪个数据类型更合适,可以通过MySQL的帮助系统来了解每种数据类型的特性、数据的长度和精度等相关信息。
- 在定义表结构为每个字段选择数据类型时,如果不清楚哪个数据类型更合适,可以通过 MySQL 的帮助系统来了解每种数据类型的特性、数据的长度和精度等相关信息。
```SQL
? data types
......@@ -542,7 +663,7 @@ unique (`sid`, `cid`)
URL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html
```
在数据类型的选择上,保存字符串数据通常都使用VARCHAR和CHAR两种类型,前者通常称为变长字符串,而后者通常称为定长字符串;对于InnoDB存储引擎,行存储格式没有区分固定长度和可变长度列,因此VARCHAR类型好CHAR类型没有本质区别,后者不一定比前者性能更好。如果要保存的很大字符串,可以使用TEXT类型;如果要保存很大的字节串,可以使用BLOB(二进制大对象)类型。在MySQL中,TEXT和BLOB又分别包括TEXT、MEDIUMTEXT、LONGTEXT和BLOB、MEDIUMBLOB、LONGBLOB三种不同的类型,它们主要的区别在于存储数据的最大大小不同。保存浮点数可以用FLOAT或DOUBLE类型,而保存定点数应该使用DECIMAL类型。如果要保存时间日期,DATETIME类型优于TIMESTAMP类型,因为前者能表示的时间日期范围更大。
在数据类型的选择上,保存字符串数据通常都使用`VARCHAR``CHAR`两种类型,前者通常称为变长字符串,而后者通常称为定长字符串;对于 InnoDB 存储引擎,行存储格式没有区分固定长度和可变长度列,因此`VARCHAR`类型和`CHAR`类型没有本质区别,后者不一定比前者性能更好。如果要保存的很大字符串,可以使用`TEXT`类型;如果要保存很大的字节串,可以使用`BLOB`(二进制大对象)类型。在 MySQL 中,`TEXT``BLOB`又分别包括`TEXT``MEDIUMTEXT``LONGTEXT``BLOB``MEDIUMBLOB``LONGBLOB`三种不同的类型,它们主要的区别在于存储数据的最大大小不同。保存浮点数可以用`FLOAT``DOUBLE`类型,`FLOAT`已经不推荐使用了,而且在 MySQL 后续的版本中可能会被移除掉。而保存定点数应该使用`DECIMAL`类型。如果要保存时间日期,`DATETIME`类型优于`TIMESTAMP`类型,因为前者能表示的时间日期范围更大。
#### DML(数据操作语言)
......@@ -598,7 +719,7 @@ values
-- 插入选课数据
insert into `tb_record`
(`sid`, `cid`, `sel_date`, `score`)
(`stu_id`, `cou_id`, `sel_date`, `score`)
values
(1001, 1111, '2017-09-01', 95),
(1001, 2222, '2017-09-01', 87.5),
......@@ -625,83 +746,52 @@ values
```SQL
-- 查询所有学生的所有信息
select * from tb_student;
select stu_id, stu_name, stu_sex, stu_birth, stu_addr, col_id from tb_student;
-- 查询所有课程名称及学分(投影和别名)
-- 查询学生的学号、姓名和家庭住址(投影)
select stu_id, stu_name, stu_addr from tb_student;
-- 查询所有课程的名称及学分(投影和别名)
select cou_name as 课程名称, cou_credit as 学分 from tb_course;
-- 查询所有女学生的姓名和出生日期(筛选)
select stu_name, stu_birth from tb_student where stu_sex=0;
-- 查询所有80后学生的姓名、性别和出生日期(筛选)
select stu_name, stu_sex, stu_birth from tb_student
where stu_birth>='1980-1-1' and stu_birth<='1989-12-31';
select stu_name, stu_sex, stu_birth from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
-- 补充1:在查询时可以对列的值进行处理
select
stu_name as 姓名,
case stu_sex when 1 then '男' else '女' end as 性别,
stu_birth as 生日
from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
-- 补充2:MySQL方言(使用数据库特有的函数)
-- 例如:Oracle中做同样事情的函数叫做decode
select
stu_name as 姓名,
if(stu_sex, '男', '女') as 性别,
stu_birth as 生日
from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
-- 查询所有80后女学生的姓名和出生日期
select stu_name, stu_birth from tb_student
where stu_birth between '1980-1-1' and '1989-12-31' and stu_sex=0;
-- 查询所有的80后学生或女学生的姓名和出生日期
select stu_name, stu_birth from tb_student
where stu_birth between '1980-1-1' and '1989-12-31' or stu_sex=0;
select stu_name, stu_sex, stu_birth from tb_student where stu_birth>='1980-1-1' and stu_birth<='1989-12-31';
select stu_name, stu_sex, stu_birth from tb_student where stu_birth between '1980-1-1' and '1989-12-31';
-- 将表示性别的 1 和 0 处理成 “男” 和 “女”
select stu_name as 姓名, case stu_sex when 1 then '男' else '女' end as 性别, stu_birth as 生日 from tb_student where stu_birth between '1980-1-1' and '1989-12-31';
select stu_name as 姓名, if(stu_sex, '男', '女') as 性别, stu_birth as 生日 from tb_student where stu_birth between '1980-1-1' and '1989-12-31';
-- 查询姓“杨”的学生姓名和性别(模糊)
-- 在SQL中通配符%可以匹配零个或任意多个字符
select stu_name, stu_sex from tb_student where stu_name like '杨%';
-- 查询姓“杨”名字两个字的学生姓名和性别(模糊)
-- 在SQL中通配符_可以刚刚好匹配一个字符
select stu_name, stu_sex from tb_student where stu_name like '杨_';
-- 查询姓“杨”名字三个字的学生姓名和性别(模糊)
select stu_name, stu_sex from tb_student where stu_name like '杨__';
-- 查询名字中有“不”字或“嫣”字的学生的姓名(模糊)
-- 提示:前面带%的模糊查询性能基本上都是非常糟糕的
select stu_name from tb_student
where stu_name like '%不%' or stu_name like '%嫣%';
update tb_student set stu_name='岳不嫣' where stu_id=1572;
select stu_name from tb_student where stu_name like '%不%' or stu_name like '%嫣%';
-- 并集运算
select stu_name from tb_student where stu_name like '%不%'
select stu_name from tb_student where stu_name like '%嫣%'
union
select stu_name from tb_student where stu_name like '%%';
select stu_name from tb_student where stu_name like '%%';
select stu_name from tb_student where stu_name like '%不%'
union all
select stu_name from tb_student where stu_name like '%嫣%';
-- 正则表达式模糊查询
select stu_name, stu_sex from tb_student where stu_name regexp '^杨.{2}$';
-- 查询姓“杨”或姓“林”名字三个字的学生的姓名(正则表达式模糊查询)
select stu_name from tb_student where stu_name regexp '[杨林].{2}';
-- 查询没有录入家庭住址的学生姓名(空值)
-- null作任何运算结果也是产生null,null相当于是条件不成立
select stu_name from tb_student where stu_addr is null;
select stu_name from tb_student where stu_addr<=>null;
select stu_name from tb_student where stu_addr is null or stu_add='';
select stu_name from tb_student where stu_addr<=>null or stu_addr='';
-- 查询录入了家庭住址的学生姓名(空值)
select stu_name from tb_student where stu_addr is not null;
select stu_name from tb_student where stu_addr is not null and stu_addr<>'';
-- 查询学生选课的所有日期(去重)
select distinct sel_date from tb_record;
......@@ -710,197 +800,180 @@ select distinct sel_date from tb_record;
select distinct stu_addr from tb_student where stu_addr is not null;
-- 查询男学生的姓名和生日按年龄从大到小排列(排序)
-- asc - 升序(从小到大),desc - 降序(从大到小)
select stu_name, stu_birth from tb_student
select stu_name, stu_birth from tb_student where stu_sex=1 order by stu_birth asc;
select stu_name, stu_birth from tb_student
-- 将生日换算成年龄(日期函数、数值函数)
select stu_name, stu_birth, floor(datediff(curdate(), stu_birth)/365) as stu_age from tb_student where stu_sex=1 order by stu_age desc;
-- 查询年龄最大的学生的出生日期(聚合函数)
select min(stu_birth) from tb_student;
select
min(stu_birth) as 生日,
floor(datediff(curdate(), min(stu_birth))/365) as 年龄
from tb_student;
-- 查询年龄最小的学生的出生日期(聚合函数)
-- 查询年龄最小的学生的出生日期(聚合函数)
select
max(stu_birth) as 生日,
floor(datediff(curdate(), max(stu_birth))/365) as 年龄
from tb_student;
-- 查询所有考试的平均成绩
-- 聚合函数在遇到null值会做忽略的处理
-- 如果做计数操作,建议使用count(*),这样才不会漏掉空值
select max(stu_birth) from tb_student;
-- 查询编号为1111的课程考试成绩的最高分
select max(score) from tb_record where cou_id=1111;
-- 查询学号为1001的学生考试成绩的最低分
select min(score) from tb_record where stu_id=1001;
-- 查询课程编号为1111的课程的平均成绩(筛选和聚合函数)
-- 查询学号为1001的学生考试成绩的平均分
select avg(score) from tb_record where stu_id=1001;
select sum(score) / count(score) from tb_record where stu_id=1001;
-- 查询学号为1001的学生所有课程的平均分(筛选和聚合函数)
-- 查询学号为1001的学生考试成绩的平均分,如果有null值,null值算0分
select sum(score) / count(*) from tb_record where stu_id=1001;
select avg(ifnull(score, 0)) from tb_record where stu_id=1001;
-- 查询学号为1001的学生考试成绩的标准差
select std(score) from tb_record where stu_id=1001;
-- 查询男女学生的人数(分组和聚合函数)
-- SAC(Split - Aggregate - Combine)
select
if(stu_sex, '男', '女') as 性别,
count(*) as 人数
-- 查询男女学生的人数(分组和聚合函数)
select if(stu_sex, '男', '女') as 性别, count(*) as 人数 from tb_student group by stu_sex;
-- 统计每个学院男女学生的人数
select
col_id as 学院,
if(stu_sex, '男', '女') as 性别,
count(*) as 人数
-- 查询每个学院男女学生人数
select col_id as 学院编号, if(stu_sex, '男', '女') as 性别, count(*) as 人数 from tb_student group by col_id, stu_sex;
-- 查询每个学生的学号和平均成绩(分组和聚合函数)
-- 查询每个学生的学号和平均成绩(分组和聚合函数)
select
sid as 学号,
round(avg(score),1) as 平均分
from tb_record group by sid;
select stu_id as 学号, round(avg(score), 2) as 平均分 from tb_record group by stu_id;
-- 查询平均成绩大于等于90分的学生的学号和平均成绩
-- 查询平均成绩大于等于90分的学生的学号和平均成绩
-- 分组以前的数据筛选使用where子句,分组以后的数据筛选使用having子句
select
sid as 学号,
round(avg(score),1) as 平均分
from tb_record
select stu_id as 学号, round(avg(score), 2) as 平均分 from tb_record group by stu_id having 平均分>=90;
-- 查询年龄最大的学生的姓名(子查询)
-- 嵌套查询:把一个查询的结果作为另外一个查询的一部分来使用。
select stu_name from tb_student where stu_birth=(
select min(stu_birth) from tb_student
-- 查询1111、2222、3333三门课程平均成绩大于等于90分的学生的学号和平均成绩
select stu_id as 学号, round(avg(score), 2) as 平均分 from tb_record where cou_id in (1111, 2222, 3333) group by stu_id having 平均分>=90;
-- 查询年龄最大的学生姓名和年龄(子查询+运算)
select
stu_name as 姓名,
floor(datediff(curdate(), stu_birth) / 365) as 年龄
from tb_student where stu_birth=(
select min(stu_birth) from tb_student
-- 查询年龄最大的学生的姓名(子查询)
select stu_name from tb_student where stu_birth=(select min(stu_birth) from tb_student);
-- 查询选了两门以上的课程的学生姓名(子查询/分组条件/集合运算)
-- 查询选了两门以上的课程的学生姓名(子查询/分组条件/集合运算)
select stu_name from tb_student where stu_id in (
select sid from tb_record group by sid having count(*)>2
select stu_name from tb_student where stu_id in (select stu_id from tb_record group by stu_id having count(*)>2);
-- 查询学生的姓名、生日和所在学院名称
select stu_name, stu_birth, col_name from tb_student, tb_college where tb_student.col_id=tb_college.col_id;
select stu_name, stu_birth, col_name from tb_student t1 inner join tb_college t2 on t1.col_id=t2.col_id;
select stu_name, stu_birth, col_name from tb_student natural join tb_college;
-- 查询学生姓名、课程名称以及成绩(连接查询/联结查询)
select t2.stu_id, stu_name, t3.cou_id, cou_name, score from tb_record t1, tb_student t2, tb_course t3 where t1.stu_id=t2.stu_id and t1.cou_id=t3.cou_id and score is not null;
-- 查询课程的名称、学分和授课老师的姓名(连接查询)
select cou_name, cou_credit, tea_name
from tb_course, tb_teacher
select stu_name, cou_name, score from tb_student t1 inner join tb_record t2 on t1.stu_id=t2.stu_id inner join tb_course t3 on t2.cou_id=t3.cou_id where score is not null;
select cou_name, cou_credit, tea_name from tb_course t1
select stu_name, cou_name, score from tb_student natural join tb_record natural join tb_course where score is not null;
-- 查询学生姓名、课程名称以及成绩(连接查询)
select stu_name, cou_name, score
from tb_record, tb_student, tb_course
-- 分页查询(前5条数据)
select stu_name, cou_name, score from tb_student natural join tb_record natural join tb_course where score is not null order by score desc limit 5;
select stu_name, cou_name, score from tb_student
inner join tb_record on stu_id=sid
inner join tb_course on cou_id=cid
-- 分页查询(6-10条数据)
select stu_name, cou_name, score from tb_student natural join tb_record natural join tb_course where score is not null order by score desc limit 5 offset 5;
-- 分页查询(11-15条数据)
select stu_name, cou_name, score from tb_student natural join tb_record natural join tb_course where score is not null order by score desc limit 5 offset 10;
select stu_name, cou_name, score from tb_student natural join tb_record natural join tb_course where score is not null order by score desc limit 10,5;
-- 查询选课学生的姓名和平均成绩(子查询和连接查询)
-- 查询选课学生的姓名和平均成绩(子查询和连接查询)
select stu_name, avg_score
from tb_student, (select sid, round(avg(score),1) as avg_score
from tb_record group by sid
select stu_name, avg_score from tb_student t1, (select stu_id, round(avg(score),1) as avg_score from tb_record group by stu_id) t2 where t1.stu_id=t2.stu_id;
-- 查询学生的姓名和选课的数量
select stu_name, total from tb_student t1, (select stu_id, count(*) as total from tb_record group by stu_id) t2 where t1.stu_id=t2.stu_id;
-- 查询每个学生的姓名和选课数量(左外连接和子查询)
-- 查询每个学生的姓名和选课数量(左外连接和子查询)
select
stu_name as 姓名,
ifnull(total, 0) as 选课数量
from tb_student left outer join (
select sid, count(*) as total from tb_record group by sid
select stu_name, ifnull(total, 0) as total from tb_student t1 left outer join (select stu_id, count(*) as total from tb_record group by stu_id) t2 on t1.stu_id=t2.stu_id;
-- 删除tb_record表的外键约束
alter table tb_record drop foreign key fk_record_stu_id;
alter table tb_record drop foreign key fk_record_cou_id;
-- 给tb_record表加两条记录,学号5566在学生表没有对应的记录
insert into tb_record
values
(default, 5566, 1111, '2019-09-02', 80),
(default, 5566, 2222, '2019-09-02', 70);
-- 查询学生的姓名和选课数量(右外连接)
select t1.stu_id, stu_name, t2.stu_id, total as total from tb_student t1 right outer join (select stu_id, count(*) as total from tb_record group by stu_id) t2 on t1.stu_id=t2.stu_id;
-- 可以通过左外连接与右外连接求并集运算得到全外连接的结果
select t1.stu_id, stu_name, t2.stu_id, total as total from tb_student t1 left outer join (select stu_id, count(*) as total from tb_record group by stu_id) t2 on t1.stu_id=t2.stu_id
union
select t1.stu_id, stu_name, t2.stu_id, total as total from tb_student t1 right outer join (select stu_id, count(*) as total from tb_record group by stu_id) t2 on t1.stu_id=t2.stu_id;
```
上面的DML有几个地方需要加以说明:
1. MySQL 中支持多种类型的运算符,包括:算术运算符(`+``-``*``/``%`)、比较运算符(`=``<>``<=>``<``<=``>``>=``BETWEEN...AND..`.、`IN``IS NULL``IS NOT NULL``LIKE``RLIKE``REGEXP`)、逻辑运算符(`NOT``AND``OR``XOR`)和位运算符(`&``|``^``~``>>``<<`),我们可以在 DML 中使用这些运算符处理数据。
2. 在查询数据时,可以在`SELECT`语句及其子句(如`WHERE`子句、`ORDER BY`子句、`HAVING`子句等)中使用函数,这些函数包括字符串函数、数值函数、时间日期函数、流程函数等,如下面的表格所示。
常用字符串函数。
| 函数 | 功能 |
| ----------------------- | ----------------------------------------------------- |
| CONCAT | 将多个字符串连接成一个字符串 |
| FORMAT | 将数值格式化成字符串并指定保留几位小数 |
| FROM_BASE64 / TO_BASE64 | BASE64解码/编码 |
| BIN / OCT / HEX | 将数值转换成二进制/八进制/十六进制字符串 |
| LOCATE | 在字符串中查找一个子串的位置 |
| LEFT / RIGHT | 返回一个字符串左边/右边指定长度的字符 |
| LENGTH / CHAR_LENGTH | 返回字符串的长度以字节/字符为单位 |
| LOWER / UPPER | 返回字符串的小写/大写形式 |
| LPAD / RPAD | 如果字符串的长度不足,在字符串左边/右边填充指定的字符 |
| LTRIM / RTRIM | 去掉字符串前面/后面的空格 |
| ORD / CHAR | 返回字符对应的编码/返回编码对应的字符 |
| STRCMP | 比较字符串,返回-1、0、1分别表示小于、等于、大于 |
| 函数 | 功能 |
| --------------------------- | ----------------------------------------------------- |
| `CONCAT` | 将多个字符串连接成一个字符串 |
| `FORMAT` | 将数值格式化成字符串并指定保留几位小数 |
| `FROM_BASE64` / `TO_BASE64` | BASE64解码/编码 |
| `BIN` / `OCT` / `HEX` | 将数值转换成二进制/八进制/十六进制字符串 |
| `LOCATE` | 在字符串中查找一个子串的位置 |
| `LEFT` / `RIGHT` | 返回一个字符串左边/右边指定长度的字符 |
| `LENGTH` / `CHAR_LENGTH` | 返回字符串的长度以字节/字符为单位 |
| `LOWER` / `UPPER` | 返回字符串的小写/大写形式 |
| `LPAD` / `RPAD` | 如果字符串的长度不足,在字符串左边/右边填充指定的字符 |
| `LTRIM` / `RTRIM` | 去掉字符串前面/后面的空格 |
| `ORD` / `CHAR` | 返回字符对应的编码/返回编码对应的字符 |
| `STRCMP` | 比较字符串,返回-1、0、1分别表示小于、等于、大于 |
| `SUBSTRING` | 返回字符串指定范围的子串 |
常用数值函数。
| 函数 | 功能 |
| ------------------------------------------ | ---------------------------------- |
| ABS | 返回一个数的绝度值 |
| CEILING / FLOOR | 返回一个数上取整/下取整的结果 |
| CONV | 将一个数从一种进制转换成另一种进制 |
| CRC32 | 计算循环冗余校验码 |
| EXP / LOG / LOG2 / LOG10 | 计算指数/对数 |
| POW | 求幂 |
| RAND | 返回[0,1)范围的随机数 |
| ROUND | 返回一个数四舍五入后的结果 |
| SQRT | 返回一个数的平方根 |
| TRUNCATE | 截断一个数到指定的精度 |
| 函数 | 功能 |
| -------------------------------------------------------- | ---------------------------------- |
| `ABS` | 返回一个数的绝度值 |
| `CEILING` / `FLOOR` | 返回一个数上取整/下取整的结果 |
| `CONV` | 将一个数从一种进制转换成另一种进制 |
| `CRC32` | 计算循环冗余校验码 |
| `EXP` / `LOG` / `LOG2` / `LOG10` | 计算指数/对数 |
| `POW` | 求幂 |
| `RAND` | 返回[0,1)范围的随机数 |
| `ROUND` | 返回一个数四舍五入后的结果 |
| `SQRT` | 返回一个数的平方根 |
| `TRUNCATE` | 截断一个数到指定的精度 |
| `SIN` / `COS` / `TAN` / `COT` / `ASIN` / `ACOS` / `ATAN` | 三角函数 |
常用时间日期函数。
| 函数 | 功能 |
| ----------------------- | ------------------------------------- |
| CURDATE / CURTIME / NOW | 获取当前日期/时间/日期和时间 |
| ADDDATE / SUBDATE | 将两个日期表达式相加/相减并返回结果 |
| DATE / TIME | 从字符串中获取日期/时间 |
| YEAR / MONTH / DAY | 从日期中获取年/月/日 |
| HOUR / MINUTE / SECOND | 从时间中获取时/分/秒 |
| DATEDIFF / TIMEDIFF | 返回两个时间日期表达式相差多少天/小时 |
| 函数 | 功能 |
| ----------------------------- | ------------------------------------- |
| `CURDATE` / `CURTIME` / `NOW` | 获取当前日期/时间/日期和时间 |
| `ADDDATE` / `SUBDATE` | 将两个日期表达式相加/相减并返回结果 |
| `DATE` / `TIME` | 从字符串中获取日期/时间 |
| `YEAR` / `MONTH` / `DAY` | 从日期中获取年/月/日 |
| `HOUR` / `MINUTE` / `SECOND` | 从时间中获取时/分/秒 |
| `DATEDIFF` / `TIMEDIFF` | 返回两个时间日期表达式相差多少天/小时 |
| `MAKEDATE` / `MAKETIME` | 制造一个日期/时间 |
常用流程函数。
| 函数 | 功能 |
| ------ | ------------------------------------------------ |
| IF | 根据条件是否成立返回不同的值 |
| IFNULL | 如果为NULL则返回指定的值否则就返回本身 |
| 函数 | 功能 |
| -------- | ------------------------------------------------ |
| `IF` | 根据条件是否成立返回不同的值 |
| `IFNULL` | 如果为NULL则返回指定的值否则就返回本身 |
| `NULLIF` | 两个表达式相等就返回NULL否则返回第一个表达式的值 |
其他常用函数。
| 函数 | 功能 |
| ---------------------- | ----------------------------- |
| MD5 / SHA1 / SHA2 | 返回字符串对应的哈希摘要 |
| CHARSET / COLLATION | 返回字符集/校对规则 |
| USER / CURRENT_USER | 返回当前用户 |
| DATABASE | 返回当前数据库名 |
| VERSION | 返回当前数据库版本 |
| FOUND_ROWS / ROW_COUNT | 返回查询到的行数/受影响的行数 |
| LAST_INSERT_ID | 返回最后一个自增主键的值 |
| 函数 | 功能 |
| -------------------------- | ----------------------------- |
| `MD5` / `SHA1` / `SHA2` | 返回字符串对应的哈希摘要 |
| `CHARSET` / `COLLATION` | 返回字符集/校对规则 |
| `USER` / `CURRENT_USER` | 返回当前用户 |
| `DATABASE` | 返回当前数据库名 |
| `VERSION` | 返回当前数据库版本 |
| `FOUND_ROWS` / `ROW_COUNT` | 返回查询到的行数/受影响的行数 |
| `LAST_INSERT_ID` | 返回最后一个自增主键的值 |
| `UUID` / `UUID_SHORT` | 返回全局唯一标识符 |
#### DCL(数据控制语言)
......@@ -921,15 +994,15 @@ grant all privileges on school.* to 'hellokitty'@'%';
revoke insert, delete, update on school.* from 'hellokitty'@'%';
```
> 说明:创建一个可以允许任意主机登录并且具有超级管理员权限的用户在现实中并不是一个明智的决定,因为一旦该账号的口令泄露或者被破解,数据库将会面临灾难级的风险。
> **说明**:创建一个可以允许任意主机登录并且具有超级管理员权限的用户在现实中并不是一个明智的决定,因为一旦该账号的口令泄露或者被破解,数据库将会面临灾难级的风险。
### 索引
索引是关系型数据库中用来提升查询性能最为重要的手段。关系型数据库中的索引就像一本书的目录,我们可以想象一下,如果要从一本书中找出某个知识点,但是这本书没有目录,这将是意见多么可怕的事情(我们估计得一篇一篇的翻下去,才能确定这个知识点到底在什么位置)。创建索引虽然会带来存储空间上的开销,就像一本书的目录会占用一部分的篇幅一样,但是在牺牲空间后换来的查询时间的减少也是非常显著的。
MySQL中,所有数据类型的列都可以被索引,常用的存储引擎InnoDB和MyISAM能支持每个表创建16个索引。InnoDB和MyISAM使用的索引其底层算法是B-tree(B树),B-tree是一种自平衡的树,类似于平衡二叉排序树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的操作都在对数时间内完成。
MySQL 中,所有数据类型的列都可以被索引,常用的存储引擎 InnoDB 和 MyISAM 能支持每个表创建16个索引。InnoDB 和 MyISAM 使用的索引其底层算法是 B-tree(B树),B-tree 是一种自平衡的树,类似于平衡二叉排序树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的操作都在对数时间内完成。
接下来我们通过一个简单的例子来说明索引的意义,比如我们要根据学生的姓名来查找学生,这个场景在实际开发中应该经常遇到,就跟通过商品名称查找商品道理是一样的。我们可以使用MySQL的`explain`关键字来查看SQL的执行计划。
接下来我们通过一个简单的例子来说明索引的意义,比如我们要根据学生的姓名来查找学生,这个场景在实际开发中应该经常遇到,就跟通过商品名称查找商品道理是一样的。我们可以使用 MySQL 的`explain`关键字来查看 SQL 的执行计划。
```SQL
explain select * from tb_student where stuname='林震南'\G
......@@ -952,26 +1025,26 @@ possible_keys: NULL
1 row in set, 1 warning (0.00 sec)
```
在上面的SQL执行计划中,有几项值得我们关注:
在上面的 SQL 执行计划中,有几项值得我们关注:
1. `select_type`:查询的类型。
- SIMPLE:简单SELECT,不需要使用UNION操作或子查询。
- PRIMARY:如果查询包含子查询,最外层的SELECT被标记为PRIMARY。
- UNION:UNION操作中第二个或后面的SELECT语句。
- SUBQUERY:子查询中的第一个SELECT。
- DERIVED:派生表的SELECT子查询。
- SIMPLE:简单 SELECT,不需要使用 UNION 操作或子查询。
- PRIMARY:如果查询包含子查询,最外层的 SELECT 被标记为 PRIMARY。
- UNION:UNION 操作中第二个或后面的 SELECT 语句。
- SUBQUERY:子查询中的第一个 SELECT。
- DERIVED:派生表的 SELECT 子查询。
2. `table`:查询对应的表。
3. `type`:MySQL在表中找到满足条件的行的方式,也称为访问类型,包括:ALL(全表扫描)、index(索引全扫描,只遍历索引树)、range(索引范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)、const/system(常量级查询)、NULL(不需要访问表或索引)。在所有的访问类型中,很显然ALL是性能最差的,它代表的全表扫描是指要扫描表中的每一行才能找到匹配的行。
4. `possible_keys`:MySQL可以选择的索引,但是**有可能不会使用**
5. `key`:MySQL真正使用的索引,如果为NULL就表示没有使用索引。
3. `type`:MySQL 在表中找到满足条件的行的方式,也称为访问类型,包括:ALL(全表扫描)、index(索引全扫描,只遍历索引树)、range(索引范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)、const / system(常量级查询)、NULL(不需要访问表或索引)。在所有的访问类型中,很显然 ALL 是性能最差的,它代表的全表扫描是指要扫描表中的每一行才能找到匹配的行。
4. `possible_keys`:MySQL 可以选择的索引,但是**有可能不会使用**
5. `key`:MySQL 真正使用的索引,如果为NULL就表示没有使用索引。
6. `key_len`:使用的索引的长度,在不影响查询的情况下肯定是长度越短越好。
7. `rows`:执行查询需要扫描的行数,这是一个**预估值**
8. `extra`:关于查询额外的信息。
- `Using filesort`:MySQL无法利用索引完成排序操作。
- `Using filesort`:MySQL 无法利用索引完成排序操作。
- `Using index`:只使用索引的信息而不需要进一步查表来获取更多的信息。
- `Using temporary`:MySQL需要使用临时表来存储结果集,常用于分组和排序。
- `Using temporary`:MySQL 需要使用临时表来存储结果集,常用于分组和排序。
- `Impossible where``where`子句会导致没有符合条件的行。
- `Distinct`:MySQL发现第一个匹配行后,停止为当前的行组合搜索更多的行。
- `Distinct`:MySQL 发现第一个匹配行后,停止为当前的行组合搜索更多的行。
- `Using where`:查询的列未被索引覆盖,筛选条件并不是索引的前导列。
从上面的执行计划可以看出,当我们通过学生名字查询学生时实际上是进行了全表扫描,不言而喻这个查询性能肯定是非常糟糕的,尤其是在表中的行很多的时候。如果我们需要经常通过学生姓名来查询学生,那么就应该在学生姓名对应的列上创建索引,通过索引来加速查询。
......@@ -980,7 +1053,7 @@ possible_keys: NULL
create index idx_student_name on tb_student(stuname);
```
再次查看刚才的SQL对应的执行计划。
再次查看刚才的 SQL 对应的执行计划。
```SQL
explain select * from tb_student where stuname='林震南'\G
......@@ -1003,13 +1076,13 @@ possible_keys: idx_student_name
1 row in set, 1 warning (0.00 sec)
```
可以注意到,在对学生姓名创建索引后,刚才的查询已经不是全表扫描而是基于索引的查询,而且扫描的行只有唯一的一行,这显然大大的提升了查询的性能。MySQL中还允许创建前缀索引,即对索引字段的前N个字符创建索引,这样的话可以减少索引占用的空间(但节省了空间很有可能会浪费时间,**时间和空间是不可调和的矛盾**),如下所示。
可以注意到,在对学生姓名创建索引后,刚才的查询已经不是全表扫描而是基于索引的查询,而且扫描的行只有唯一的一行,这显然大大的提升了查询的性能。MySQL 中还允许创建前缀索引,即对索引字段的前N个字符创建索引,这样的话可以减少索引占用的空间(但节省了空间很有可能会浪费时间,**时间和空间是不可调和的矛盾**),如下所示。
```SQL
create index idx_student_name_1 on tb_student(stuname(1));
```
上面的索引相当于是根据学生姓名的第一个字来创建的索引,我们再看看SQL执行计划。
上面的索引相当于是根据学生姓名的第一个字来创建的索引,我们再看看 SQL 执行计划。
```SQL
explain select * from tb_student where stuname='林震南'\G
......@@ -1049,23 +1122,23 @@ drop index idx_student_name on tb_student;
我们简单的为大家总结一下索引的设计原则:
1. **最适合**索引的列是出现在**WHERE子句**和连接子句中的列。
2. 索引列的基数越大(取值多重复值少),索引的效果就越好。
2. 索引列的基数越大(取值多重复值少),索引的效果就越好。
3. 使用**前缀索引**可以减少索引占用的空间,内存中可以缓存更多的索引。
4. **索引不是越多越好**,虽然索引加速了读操作(查询),但是写操作(增、删、改)都会变得更慢,因为数据的变化会导致索引的更新,就如同书籍章节的增删需要更新目录一样。
5. 使用InnoDB存储引擎时,表的普通索引都会保存主键的值,所以**主键要尽可能选择较短的数据类型**,这样可以有效的减少索引占用的空间,利用提升索引的缓存效果。
5. 使用 InnoDB 存储引擎时,表的普通索引都会保存主键的值,所以**主键要尽可能选择较短的数据类型**,这样可以有效的减少索引占用的空间,利用提升索引的缓存效果。
最后,还有一点需要说明,InnoDB使用的B-tree索引,数值类型的列除了等值判断时索引会生效之外,使用>、<>=、<=、BETWEEN...AND... 、<>时,索引仍然生效;对于字符串类型的列,如果使用不以通配符开头的模糊查询,索引也是起作用的,但是其他的情况会导致索引失效,这就意味着很有可能会做全表查询。
最后,还有一点需要说明,InnoDB 使用的 B-tree 索引,数值类型的列除了等值判断时索引会生效之外,使用`>``<``>=``<=``BETWEEN...AND... ``<>`时,索引仍然生效;对于字符串类型的列,如果使用不以通配符开头的模糊查询,索引也是起作用的,但是其他的情况会导致索引失效,这就意味着很有可能会做全表查询。
### 视图
视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说,视图就是虚拟的表,但与数据表不同的是,数据表是一种实体结构,而视图是一种虚拟结构,你也可以将视图理解为保存在数据库中被赋予名字的SQL语句。
视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说,视图就是虚拟的表,但与数据表不同的是,数据表是一种实体结构,而视图是一种虚拟结构,你也可以将视图理解为保存在数据库中被赋予名字的 SQL 语句。
使用视图可以获得以下好处:
1. 可以将实体数据表隐藏起来,让外部程序无法得知实际的数据结构,让访问者可以使用表的组成部分而不是整个表,降低数据库被攻击的风险。
2. 在大多数的情况下视图是只读的(更新视图的操作通常都有诸多的限制),外部程序无法直接透过视图修改数据。
3. 重用SQL语句,将高度复杂的查询包装在视图表中,直接访问该视图即可取出需要的数据;也可以将视图视为数据表进行连接查询。
3. 重用 SQL 语句,将高度复杂的查询包装在视图表中,直接访问该视图即可取出需要的数据;也可以将视图视为数据表进行连接查询。
4. 视图可以返回与实体数据表不同格式的数据,
创建视图。
......@@ -1107,10 +1180,10 @@ select stuname, avgscore from vw_student_score order by avgscore desc;
既然视图是一张虚拟的表,那么视图的中的数据可以更新吗?视图的可更新性要视具体情况而定,以下类型的视图是不能更新的:
1. 使用了聚合函数(SUM、MIN、MAX、AVG、COUNT等)、DISTINCT、GROUP BY、HAVING、UNION或者UNION ALL的视图。
2. SELECT中包含了子查询的视图。
3. FROM子句中包含了一个不能更新的视图的视图。
4. WHERE子句的子查询引用了FROM子句中的表的视图。
1. 使用了聚合函数(`SUM``MIN``MAX``AVG``COUNT`等)、`DISTINCT``GROUP BY``HAVING``UNION`或者`UNION ALL`的视图。
2. `SELECT`中包含了子查询的视图。
3. `FROM`子句中包含了一个不能更新的视图的视图。
4. `WHERE`子句的子查询引用了`FROM`子句中的表的视图。
删除视图。
......@@ -1128,14 +1201,14 @@ drop view vw_student_score;
### 过程
过程(又称存储过程)是事先编译好存储在数据库中的一组SQL的集合,调用过程可以简化应用程序开发人员的工作,减少与数据库服务器之间的通信,对于提升数据操作的性能也是有帮助的。其实迄今为止,我们使用的SQL语句都是针对一个或多个表的单条语句,但在实际开发中经常会遇到某个操作需要多条SQL语句才能完成的情况。例如,电商网站在受理用户订单时,需要做以下一系列的处理。
过程(又称存储过程)是事先编译好存储在数据库中的一组 SQL 的集合,调用过程可以简化应用程序开发人员的工作,减少与数据库服务器之间的通信,对于提升数据操作的性能也是有帮助的。其实迄今为止,我们使用的 SQL 语句都是针对一个或多个表的单条语句,但在实际开发中经常会遇到某个操作需要多条 SQL 语句才能完成的情况。例如,电商网站在受理用户订单时,需要做以下一系列的处理。
1. 通过查询来核对库存中是否有对应的物品以及库存是否充足。
2. 如果库存有物品,需要锁定库存以确保这些物品不再卖给别人, 并且要减少可用的物品数量以反映正确的库存量。
3. 如果库存不足,可能需要进一步与供应商进行交互或者至少产生一条系统提示消息。
4. 不管受理订单是否成功,都需要产生流水记录,而且需要给对应的用户产生一条通知信息。
我们可以通过过程将复杂的操作封装起来,这样不仅有助于保证数据的一致性,而且将来如果业务发生了变动,只需要调整和修改过程即可。对于调用过程的用户来说,过程并没有暴露数据表的细节,而且执行过程比一条条的执行一组SQL要快得多。
我们可以通过过程将复杂的操作封装起来,这样不仅有助于保证数据的一致性,而且将来如果业务发生了变动,只需要调整和修改过程即可。对于调用过程的用户来说,过程并没有暴露数据表的细节,而且执行过程比一条条的执行一组 SQL 要快得多。
下面的过程实现了查询某门课程的最高分、最低分和平均分。
......@@ -1165,7 +1238,7 @@ call sp_score_by_cid(1111, @a, @b, @c);
select @a, @b, @c;
```
> **说明**:在定义过程时,因为可能需要书写多条SQL,而分隔这些SQL需要使用分号作为分隔符,如果这个时候,仍然用分号表示整段代码结束,那么定义过程的SQL就会出现错误,所以上面我们用`delimiter $$`将整段代码结束的标记定义为`$$`,那么代码中的分号将不再表示整段代码的结束,整段代码只会在遇到`end $$`时才会执行。在定义完过程后,通过`delimiter ;`将结束符重新改回成分号(恢复现场)。
> **说明**:在定义过程时,因为可能需要书写多条 SQL,而分隔这些 SQL 需要使用分号作为分隔符,如果这个时候,仍然用分号表示整段代码结束,那么定义过程的 SQL 就会出现错误,所以上面我们用`delimiter $$`将整段代码结束的标记定义为`$$`,那么代码中的分号将不再表示整段代码的结束,整段代码只会在遇到`end $$`时才会执行。在定义完过程后,通过`delimiter ;`将结束符重新改回成分号(恢复现场)。
上面定义的过程有四个参数,其中第一个参数是输入参数,代表课程的编号,后面的参数都是输出参数,因为过程不能定义返回值,只能通过输出参数将执行结果带出,定义输出参数的关键字是`out`,默认情况下参数都是输入参数。
......@@ -1187,11 +1260,11 @@ select @a as 最高分, @b as 最低分, @c as 平均分;
drop procedure sp_score_by_cid;
```
在过程中,我们可以定义变量、条件,可以使用分支和循环语句,可以通过游标操作查询结果,还可以使用事件调度器,这些内容我们暂时不在此处进行介绍。虽然我们说了很多过程的好处,但是在实际开发中,如果过度的使用过程并将大量复杂的运算放到过程中,必然会导致占用数据库服务器的CPU资源,造成数据库服务器承受巨大的压力。为此,我们一般会将复杂的运算和处理交给应用服务器,因为很容易部署多台应用服务器来分摊这些压力。
在过程中,我们可以定义变量、条件,可以使用分支和循环语句,可以通过游标操作查询结果,还可以使用事件调度器,这些内容我们暂时不在此处进行介绍。虽然我们说了很多过程的好处,但是在实际开发中,如果过度的使用过程并将大量复杂的运算放到过程中,必然会导致占用数据库服务器的 CPU 资源,造成数据库服务器承受巨大的压力。为此,我们一般会将复杂的运算和处理交给应用服务器,因为很容易部署多台应用服务器来分摊这些压力。
### MySQL8窗口函数
### MySQL8 窗口函数
MySQL从8.0开始支持窗口函数,大多数商业数据库和一些开源数据库早已提供了对窗口函数的支持,有的也将其称之为OLAP(联机分析和处理)函数,听名字就知道跟统计和分析相关。为了帮助大家理解窗口函数,我们先说说窗口的概念。
MySQL 从8.0开始支持窗口函数,大多数商业数据库和一些开源数据库早已提供了对窗口函数的支持,有的也将其称之为 OLAP(联机分析和处理)函数,听名字就知道跟统计和分析相关。为了帮助大家理解窗口函数,我们先说说窗口的概念。
窗口可以理解为记录的集合,窗口函数也就是在满足某种条件的记录集合上执行的特殊函数,对于每条记录都要在此窗口内执行函数。窗口函数和我们上面讲到的聚合函数比较容易混淆,二者的区别主要在于聚合函数是将多条记录聚合为一条记录,窗口函数是每条记录都会执行,执行后记录条数不会变。窗口函数不仅仅是几个函数,它是一套完整的语法,函数只是该语法的一部分,基本语法如下所示:
......@@ -1201,7 +1274,7 @@ MySQL从8.0开始支持窗口函数,大多数商业数据库和一些开源数
上面语法中,窗口函数的位置可以放以下两种函数:
1. 专用窗口函数,包括:`rank``dense_rank``row_number`等。
1. 专用窗口函数,包括:`lead``rank``dense_rank``row_number`等。
2. 聚合函数,包括:`sum``avg``max``min``count`等。
> **参考链接**:<https://zhuanlan.zhihu.com/p/92654574>。
......@@ -1233,19 +1306,19 @@ MySQL从8.0开始支持窗口函数,大多数商业数据库和一些开源数
- 检查约束(check)
> **说明**:在MySQL数据库中,检查约束并不起作用。
> **说明**:在 MySQL 8.x 以前,检查约束并不起作用。
#### 数据一致性
1. 事务:一系列对数据库进行读/写的操作,这些操作要么全都成功,要么全都失败。
2. 事务的ACID特性
2. 事务的 ACID 特性
- 原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行
- 一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态
- 隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行
- 持久性:已被提交的事务对数据库的修改应该永久保存在数据库中
3. MySQL中的事务操作
3. MySQL 中的事务操作
- 开启事务环境
......@@ -1271,11 +1344,13 @@ MySQL从8.0开始支持窗口函数,大多数商业数据库和一些开源数
rollback
```
大家应该能够想到,关于MySQL的知识肯定远远不止上面列出的这些,比如MySQL的性能优化、管理和维护MySQL的相关工具、MySQL数据的备份和恢复、监控MySQL、部署高可用架构等问题我们在这里都没有进行讨论。当然,这些内容也都是跟项目开发密切相关的,我们就留到有需要的时候再进行讲解。
### 总结
关于 MySQL 的知识肯定远远不止上面列出的这些,比如 MySQL 的性能优化、管理和维护 MySQL 的相关工具、MySQL 数据的备份和恢复、监控 MySQL、部署高可用架构等问题我们在这里都没有进行讨论。当然,这些内容也都是跟项目开发密切相关的,我们就留到有需要的时候再进行讲解。
### Python数据库编程
我们用如下所示的数据库来演示在Python中如何访问MySQL数据库。
我们用如下所示的 SQL 创建数据库,然后为大家演示在 Python 中如何访问 MySQL 数据库。
```SQL
drop database if exists hrs;
......@@ -1331,9 +1406,9 @@ insert into tb_emp values
(3588, '朱九真', '会计', 5566, 2500, null, 10);
```
Python 3中,我们通常使用纯Python的三方库PyMySQL来访问MySQL数据库,它应该是目前Python操作MySQL数据库最好的选择。
Python3 中,我们通常使用纯 Python 的三方库 PyMySQL 来访问 MySQL 数据库,它应该是目前 Python 操作 MySQL 数据库最好的选择。
1. 安装PyMySQL。
1. 安装 PyMySQL。
```Shell
pip install pymysql
......@@ -1402,7 +1477,7 @@ insert into tb_emp values
main()
```
> 说明:如果不希望每次SQL操作之后手动提交或回滚事务,可以像上面的代码那样,在创建连接的时候多加一个名为`autocommit`的参数并将它的值设置为`True`,表示每次执行SQL之后自动提交。如果程序中不需要使用事务环境也不希望手动的提交或回滚就可以这么做。
> 说明:如果不希望每次 SQL 操作之后手动提交或回滚事务,可以像上面的代码那样,在创建连接的时候多加一个名为`autocommit`的参数并将它的值设置为`True`,表示每次执行 SQL 之后自动提交。如果程序中不需要使用事务环境也不希望手动的提交或回滚就可以这么做。
4. 更新一个部门。
......
## Pandas的应用-4
### DataFrame的应用
#### 数据分析
经过前面的学习,我们已经将数据准备就绪而且变成了我们想要的样子,接下来就是最为重要的数据分析阶段了。当我们拿到一大堆数据的时候,如何从数据中迅速的解读出有价值的信息,这就是数据分析要解决的问题。首先,我们可以获取数据的描述性统计信息,通过描述性统计信息,我们可以了解数据的集中趋势和离散趋势。
例如,我们有如下所示的学生成绩表。
```Python
import numpy as np
import pandas as pd
scores = np.random.randint(50, 101, (5, 3))
names = ('关羽', '张飞', '赵云', '马超', '黄忠')
courses = ('语文', '数学', '英语')
df = pd.DataFrame(data=scores, columns=courses, index=names)
df
```
输出:
```
语文 数学 英语
关羽 96 72 73
张飞 72 70 97
赵云 74 51 79
马超 100 54 54
黄忠 89 100 88
```
我们可以通过`DataFrame`对象的方法`mean``max``min``std``var`等方法分别获取每个学生或每门课程的平均分、最高分、最低分、标准差、方差等信息,也可以直接通过`describe`方法直接获取描述性统计信息,代码如下所示。
计算每门课程成绩的平均分。
```Python
df.mean()
```
输出:
```
语文 86.2
数学 69.4
英语 78.2
dtype: float64
```
计算每个学生成绩的平均分。
```Python
df.mean(axis=1)
```
输出:
```
关羽 80.333333
张飞 79.666667
赵云 68.000000
马超 69.333333
黄忠 92.333333
dtype: float64
```
计算每门课程成绩的方差。
```Python
df.var()
```
输出:
```
语文 161.2
数学 379.8
英语 265.7
dtype: float64
```
> **说明**:通过方差可以看出,数学成绩波动最大,最不稳定。
获取每门课程的描述性统计信息。
```Python
df.describe()
```
输出:
```
语文 数学 英语
count 5.000000 5.000000 5.000000
mean 86.200000 69.400000 78.200000
std 12.696456 19.488458 16.300307
min 72.000000 51.000000 54.000000
25% 74.000000 54.000000 73.000000
50% 89.000000 70.000000 79.000000
75% 96.000000 72.000000 88.000000
max 100.000000 100.000000 97.000000
```
##### 排序和Top-N
如果需要对数据进行排序,可以使用`DataFrame`对象的`sort_values`方法,该方法的`by`参数可以指定根据哪个列或哪些列进行排序,而`ascending`参数可以指定升序或是降序。例如,下面的代码展示了如何将学生表按语文成绩排降序。
```Python
df.sort_values(by='语文', ascending=False)
```
输出:
```
语文 数学 英语
马超 100 54 54
关羽 96 72 73
黄忠 89 100 88
赵云 74 51 79
张飞 72 70 97
```
如果`DataFrame`数据量很大,排序将是一个非常耗费时间的操作。有的时候我们只需要获得排前N名或后N名的数据,这个时候其实没有必要对整个数据进行排序,而是直接利用堆结构找出Top-N的数据。`DataFrame``nlargest``nsmallest`方法就提供对Top-N操作的支持,代码如下所示。
找出语文成绩前3名的学生信息。
```Python
df.nlargest(3, '语文')
```
输出:
```
语文 数学 英语
马超 100 54 54
关羽 96 72 73
黄忠 89 100 88
```
找出数学成绩最低的3名学生的信息。
```Python
df.nsmallest(3, '数学')
```
输出:
```
语文 数学 英语
赵云 74 51 79
马超 100 54 54
张飞 72 70 97
```
##### 分组聚合操作
我们先从 Excel 文件中读取一组销售数据,然后再为大家演示如何进行分组聚合操作。
```Python
df = pd.read_excel('2020年销售数据.xlsx')
df.head()
```
> **说明**:如果需要上面例子中的 Excel 文件,可以通过下面的阿里云盘地址进行获取,该文件在“我的分享”下面的“数据集”目录中。地址:https://www.aliyundrive.com/s/oPi7DRAVKRm。
输出:
```
销售日期 销售区域 销售渠道 销售订单 品牌 售价 销售数量
0 2020-01-01 上海 拼多多 182894-455 八匹马 99 83
1 2020-01-01 上海 抖音 205635-402 八匹马 219 29
2 2020-01-01 上海 天猫 205654-021 八匹马 169 85
3 2020-01-01 上海 天猫 205654-519 八匹马 169 14
4 2020-01-01 上海 天猫 377781-010 皮皮虾 249 61
```
如果我们要统计每个销售区域的销售总额,可以先通过“售价”和“销售数量”计算出销售额,为`DataFrame`添加一个列,代码如下所示。
```Python
df['销售额'] = df['售价'] * df['销售数量']
df.head()
```
输出:
```
销售日期 销售区域 销售渠道 销售订单 品牌 售价 销售数量 销售额
0 2020-01-01 上海 拼多多 182894-455 八匹马 99 83 8217
1 2020-01-01 上海 抖音 205635-402 八匹马 219 29 6351
2 2020-01-01 上海 天猫 205654-021 八匹马 169 85 14365
3 2020-01-01 上海 天猫 205654-519 八匹马 169 14 2366
4 2020-01-01 上海 天猫 377781-010 皮皮虾 249 61 15189
```
然后再根据“销售区域”列对数据进行分组,这里我们使用的是`DataFrame`对象的`groupby`方法。分组之后,我们取“销售额”这个列在分组内进行求和处理,代码和结果如下所示。
```Python
df.groupby('销售区域').销售额.sum()
```
输出:
```
销售区域
上海 11610489
北京 12477717
南京 1767301
安徽 895463
广东 1617949
江苏 537079
浙江 687862
福建 10178227
Name: 销售额, dtype: int64
```
如果我们要统计每个月的销售总额,我们可以将“销售日期”作为groupby`方法的参数,当然这里需要先将“销售日期”处理成月,代码和结果如下所示。
```Python
df.groupby(df['销售日期'].dt.month).销售额.sum()
```
输出:
```
销售日期
1 5409855
2 4608455
3 4164972
4 3996770
5 3239005
6 2817936
7 3501304
8 2948189
9 2632960
10 2375385
11 2385283
12 1691973
Name: 销售额, dtype: int64
```
接下来我们将难度升级,统计每个销售区域每个月的销售总额,这又该如何处理呢?事实上,`groupby`方法的第一个参数可以是一个列表,列表中可以指定多个分组的依据,大家看看下面的代码和输出结果就明白了。
```Python
df.groupby(['销售区域', df['销售日期'].dt.month]).销售额.sum()
```
输出:
```
销售区域 销售日期
上海 1 1679125
2 1689527
3 1061193
4 1082187
5 841199
6 785404
7 863906
8 734937
9 1107693
10 412108
11 825169
12 528041
北京 1 1878234
2 1807787
3 1360666
4 1205989
5 807300
6 1216432
7 1219083
8 645727
9 390077
10 671608
11 678668
12 596146
南京 7 841032
10 710962
12 215307
安徽 4 341308
5 554155
广东 3 388180
8 469390
9 365191
11 395188
江苏 4 537079
浙江 3 248354
8 439508
福建 1 1852496
2 1111141
3 1106579
4 830207
5 1036351
6 816100
7 577283
8 658627
9 769999
10 580707
11 486258
12 352479
Name: 销售额, dtype: int64
```
如果希望统计出每个区域的销售总额以及每个区域单笔金额的最高和最低,我们可以在`DataFrame`或`Series`对象上使用`agg`方法并指定多个聚合函数,代码和结果如下所示。
```Python
df.groupby('销售区域').销售额.agg(['sum', 'max', 'min'])
```
输出:
```
sum max min
销售区域
上海 11610489 116303 948
北京 12477717 133411 690
南京 1767301 87527 1089
安徽 895463 68502 1683
广东 1617949 120807 990
江苏 537079 114312 3383
浙江 687862 90909 3927
福建 10178227 87527 897
```
如果希望自定义聚合后的列的名字,可以使用如下所示的方法。
```Python
df.groupby('销售区域').销售额.agg(销售总额='sum', 单笔最高='max', 单笔最低='min')
```
输出:
```
销售总额 单笔最高 单笔最低
销售区域
上海 11610489 116303 948
北京 12477717 133411 690
南京 1767301 87527 1089
安徽 895463 68502 1683
广东 1617949 120807 990
江苏 537079 114312 3383
浙江 687862 90909 3927
福建 10178227 87527 897
```
如果需要对多个列使用不同的聚合函数,例如“统计每个销售区域销售额的平均值以及销售数量的最低值和最高值”,我们可以按照下面的方式来操作。
```Python
df.groupby('销售区域')[['销售额', '销售数量']].agg({
'销售额': 'mean', '销售数量': ['max', 'min']
})
```
输出:
```
销售额 销售数量
mean max min
销售区域
上海 20622.538188 100 10
北京 20125.350000 100 10
南京 22370.898734 100 11
安徽 26337.147059 98 16
广东 32358.980000 98 10
江苏 29837.722222 98 15
浙江 27514.480000 95 20
福建 18306.163669 100 10
```
##### 透视表和交叉表
上面的例子中,“统计每个销售区域每个月的销售总额”会产生一个看起来很长的结果,在实际工作中我们通常把那些行很多列很少的表成为“窄表”,如果我们不想得到这样的一个“窄表”,可以使用`DataFrame`的`pivot_table`方法或者是`pivot_table`函数来生成透视表。透视表的本质就是对数据进行分组聚合操作,**根据 A 列对 B 列进行统计**,如果大家有使用 Excel 的经验,相信对透视表这个概念一定不会陌生。例如,我们要“统计每个销售区域的销售总额”,那么“销售区域”就是我们的 A 列,而“销售额”就是我们的 B 列,在`pivot_table`函数中分别对应`index`和`values`参数,这两个参数都可以是单个列或者多个列。
```Python
pd.pivot_table(df, index='销售区域', values='销售额', aggfunc='sum')
```
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106180912.png" style="zoom:50%">
> **注意**:上面的结果操作跟之前用`groupby`的方式得到的结果有一些区别,`groupby`操作后,如果对单个列进行聚合,得到的结果是一个`Series`对象,而上面的结果是一个`DataFrame` 对象。
如果要统计每个销售区域每个月的销售总额,也可以使用`pivot_table`函数,代码如下所示。
```Python
pd.pivot_table(df, index=['销售区域', df['销售日期'].dt.month], values='销售额', aggfunc='sum')
```
上面的操作结果是一个`DataFrame`,但也是一个长长的“窄表”,如果希望做成一个行比较少列比较多的“宽表”,可以将`index`参数中的列放到`columns`参数中,代码如下所示。
```Python
pd.pivot_table(
df, index='销售区域', columns=df['销售日期'].dt.month,
values='销售额', aggfunc='sum', fill_value=0
)
```
> **说明**:`pivot_table`函数的`fill_value=0`会将空值处理为`0`。
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106104551.png" style="zoom:50%">
使用`pivot_table`函数时,还可以通过添加`margins`和`margins_name`参数对分组聚合的结果做一个汇总,具体的操作和效果如下所示。
```Python
df['月份'] = df['销售日期'].dt.month
pd.pivot_table(
df, index='销售区域', columns='月份',
values='销售额', aggfunc='sum', fill_value=0,
margins=True, margins_name='总计'
)
```
输出:
![image-20211106181707655](https://gitee.com/jackfrued/mypic/raw/master/20211106181707.png)
交叉表就是一种特殊的透视表,它不需要先构造一个`DataFrame`对象,而是直接通过数组或`Series`对象指定两个或多个因素进行运算得到统计结果。例如,我们要统计每个销售区域的销售总额,也可以按照如下所示的方式来完成,我们先准备三组数据。
```Python
sales_area, sales_month, sales_amount = df['销售区域'], df['月份'], df['销售额']
```
使用`crosstab`函数生成交叉表。
```Python
pd.crosstab(
index=sales_area, columns=sales_month, values=sales_amount, aggfunc='sum'
).fillna(0).applymap(int)
```
> **说明**:上面的代码使用了`DataFrame`对象的`fillna`方法将空值处理为0,再使用`applymap`方法将数据类型处理成整数。
#### 数据可视化
一图胜千言,我们对数据进行透视的结果,最终要通过图表的方式呈现出来,因为图表具有极强的表现力,能够让我们迅速的解读数据中隐藏的价值。和`Series`一样,`DataFrame`对象提供了`plot`方法来支持绘图,底层仍然是通过`matplotlib`库实现图表的渲染。关于`matplotlib`的内容,我们在下一个章节进行详细的探讨,这里我们只简单的讲解`plot`方法的用法。
例如,我们想通过一张柱状图来比较“每个销售区域的销售总额”,可以直接在透视表上使用`plot`方法生成柱状图。我们先导入`matplotlib.pyplot`模块,通过修改绘图的参数使其支持中文显示。
```Python
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'FZJKai-Z03S'
```
> **说明**:上面的`FZJKai-Z03S`是我电脑上已经安装的一种支持中文的字体的名称,字体的名称可以通过查看用户主目录下`.matplotlib`文件夹下名为`fontlist-v330.json`的文件来获得,而这个文件在执行上面的命令后就会生成。
使用魔法指令配置生成矢量图。
```Python
%config InlineBackend.figure_format = 'svg'
```
绘制“每个销售区域销售总额”的柱状图。
```Python
temp = pd.pivot_table(df, index='销售区域', values='销售额', aggfunc='sum')
temp.plot(figsize=(8, 4), kind='bar')
plt.xticks(rotation=0)
plt.show()
```
> **说明**:上面的第3行代码会将横轴刻度上的文字旋转到0度,第4行代码会显示图像。
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106195040.png" style="zoom:50%">
如果要绘制饼图,可以修改`plot`方法的`kind`参数为`pie`,然后使用定制饼图的参数对图表加以定制,代码如下所示。
```Python
temp.sort_values(by='销售额', ascending=False).plot(
figsize=(6, 6), kind='pie', y='销售额',
autopct='%.2f%%', pctdistance=0.8,
wedgeprops=dict(linewidth=1, width=0.35)
)
plt.legend(loc='center')
plt.show()
```
输出:
<img src="https://gitee.com/jackfrued/mypic/raw/master/20211106201550.png" style="zoom:50%">
......@@ -398,9 +398,9 @@ Python在以下领域都有用武之地。
#### Day88 - [深度学习入门](./Day81-90/88.深度学习入门.md)
#### Day89 - [Tensorflow概述](./Day81-90/89.Tensorflow概述.md)
#### Day89 - [PyTorch概述](./Day81-90/89.PyTorch概述.md)
#### Day90 - [Tensorflow实战](./Day81-90/90.Tensorflow实战.md)
#### Day90 - [PyTorch实战](./Day81-90/90.PyTorch实战.md)
### Day91~100 - [团队项目开发](./Day91-100)
......
## Zen of Python(Python之禅)
1. Beautiful is better than ugly. (优美比丑陋好)
2. Explicit is better than implicit.(清晰比晦涩好)
3. Simple is better than complex.(简单比复杂好)
4. Complex is better than complicated.(复杂比错综复杂好)
5. Flat is better than nested.(扁平比嵌套好)
6. Sparse is better than dense.(稀疏比密集好)
7. Readability counts.(可读性很重要)
8. Special cases aren't special enough to break the rules.(特殊情况也不应该违反这些规则)
9. Although practicality beats purity.(但现实往往并不那么完美)
10. Errors should never pass silently.(异常不应该被静默处理)
11. Unless explicitly silenced.(除非你希望如此)
12. In the face of ambiguity, refuse the temptation to guess.(遇到模棱两可的地方,不要胡乱猜测)
13. There should be one-- and preferably only one --obvious way to do it.(肯定有一种通常也是唯一一种最佳的解决方案)
14. Although that way may not be obvious at first unless you're Dutch.(虽然这种方案并不是显而易见的,因为你不是那个荷兰人[^1])
15. Now is better than never.(现在开始做比不做好)
16. Although never is often better than \*right\* now.(不做比盲目去做好[^2])
17. If the implementation is hard to explain, it's a bad idea.(如果一个实现方案难于理解,它通常不是一个好的方案)
18. If the implementation is easy to explain, it may be a good idea.(如果一个实现方案易于理解,它很有可能是一个好的方案)
19. Namespaces are one honking great idea -- let's do more of those!(命名空间非常有用,我们应当多加利用)
1. Beautiful is better than ugly. (优美比丑陋好)<br>
2. Explicit is better than implicit.(清晰比晦涩好)<br>
3. Simple is better than complex.(简单比复杂好)<br>
4. Complex is better than complicated.(复杂比错综复杂好)<br>
5. Flat is better than nested.(扁平比嵌套好)<br>
6. Sparse is better than dense.(稀疏比密集好)<br>
7. Readability counts.(可读性很重要)<br>
8. Special cases aren't special enough to break the rules.(特殊情况也不应该违反这些规则)<br>
9. Although practicality beats purity.(但现实往往并不那么完美)<br>
10. Errors should never pass silently.(异常不应该被静默处理)<br>
11. Unless explicitly silenced.(除非你希望如此)<br>
12. In the face of ambiguity, refuse the temptation to guess.(遇到模棱两可的地方,不要胡乱猜测)<br>
13. There should be one-- and preferably only one --obvious way to do it.(肯定有一种通常也是唯一一种最佳的解决方案)<br>
14. Although that way may not be obvious at first unless you're Dutch.(虽然这种方案并不是显而易见的,因为你不是那个荷兰人[^1])<br>
15. Now is better than never.(现在开始做比不做好)<br>
16. Although never is often better than \*right\* now.(不做比盲目去做好[^2])<br>
17. If the implementation is hard to explain, it's a bad idea.(如果一个实现方案难于理解,它通常不是一个好的方案)<br>
18. If the implementation is easy to explain, it may be a good idea.(如果一个实现方案易于理解,它很有可能是一个好的方案)<br>
19. Namespaces are one honking great idea -- let's do more of those!(命名空间非常有用,我们应当多加利用)<br>
[^1]:这里指的是 Python 之父 Guido van Rossumm。
[^2]:极限编程中的YAGNI原则
......
......@@ -26,9 +26,9 @@
1. I: Can you tell me a little bit about yourself? (介绍下自己)
原则:不要谈私生活和奇怪的癖好(英雄联盟干到钻石),因为别人更想知道的是你的专业技能(qulifications)和工作经验(experience),所以重点在你之前的公司(company name)、职位(title)、时间(years)和主要职责(major responsibilities)
原则:不要谈私生活和奇怪的癖好(王者荣耀打到星耀并不值得在这里说),因为别人更想知道的是你的专业技能(qulifications)和工作经验(experience),所以重点在你之前的公司(company name)、职位(title)、时间(years)和主要职责(major responsibilities)
C: Thank you for having me. My name is Dachui WANG. I'm 25 years old, and I'm single. I have a Bachelor's Degree of Computer Science from Tsinghua University. I was a Junior Java Programmer for ABC Technologies during my college life. Then I become an intermediate Java engineer for XYZ Corporation in last two years. Programming is my everyday life and programming is where my passion is. I think I have a good knowledge of Java enterprise application developement using light-weight frameworks like Spring, Guice, Hibernate and other open source middle-ware like Dubbo, Mycat, rocketmq and so on and so forth. I love reading, travelling and playing basketball in my spare time. That's all! Thank you!
C: Thank you for having me. My name is Dachui WANG. I'm 22 years old, and I'm single. I have a Bachelor's Degree of Computer Science from Tsinghua University. I was a Junior Java Programmer for ABC Technologies during my college life. Then I become an intermediate Java engineer for XYZ Corporation in last two years. Programming is my everyday life and programming is where my passion is. I think I have a good knowledge of Java enterprise application developement using light-weight frameworks like Spring, Guice, Hibernate and other open source middle-ware like Dubbo, Mycat, rocketmq and so on and so forth. I love reading, travelling and playing basketball in my spare time. That's all! Thank you!
2. I: How would you describe your personality? (你的性格)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册