mysql数据库基础

文章目录

一、关系型和非关系型数据库

关系型数据库有 mysql /oracle/sql server/sqlite

1.首先了解一下 什么是关系型数据库?
关系型数据库是由二维表及其之间的联系组成的数据库。
优点:
1、易于维护:都是使用表结构,格式一致;
2、使用方便:SQL语言通用,可用于复杂查询;
3、复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写;
2、固定的表结构,灵活度稍欠;
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

非关系型数据库有 redis / hbase /mongoDB /CouchDB /Neo4J

非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等

优点:
1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
2、速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
3、高扩展性;
4、成本低:nosql数据库部署简单,基本都是开源软件。

缺点:
1、不提供sql支持,学习和使用成本较高;
2、无事务处理;
3、数据结构相对复杂,复杂查询方面稍欠。

二、mysql简介

1. MySQL介绍

MySQL是一个小型的开源的关系型数据库管理系统,与其他大型数据库管理系统例如Oracle、DB2、SQL Server等相比,MySQL规模小,功能有限,但是它体积小、速度快、成本低,且它提供的功能对稍微复杂的应用已经够用,这些特性使得MySQL成为世界上受欢迎的开放源代码数据库之一。

2. MySQL版本介绍

针对不同的用户,MySQL分为两个不同的版本:

●MySQL Community Server(社区版):该版本完全免费,但是官方不提供技术支持。用户可以自由下载使用。

●MySQL Enterprise Server(企业版服务器):为企业提供数据库应用,支持ACID事务处理,提供完整的提交、回滚、崩溃恢复和行政锁定功能。需要付费使用,官方提供技术支持。

三、sql语句分类

结构化查询语言(Structured Query Language)简称SQL。

SQL语言通常分为五类:

  1. DDL(数据定义语言):create、alter、drop等;
  2. DQL(数据查询语言):select等;
  3. DML(数据操纵语言):insert、delete 、update等;
  4. TCL(事务控制语言):commit、rollback等;
  5. DCL(数据控制语言):grant、revoke等。

1.DDL(数据定义语言)

数据定义语言(Data Definition Language)简称DDL。是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。

DDL用来创建数据库中的各种对象,创建、删除、修改表的结构,比如表、视图、索引、同义词、聚簇等。其主要功能是定义数据库对象,核心指令为create、drop、alter。

和DML相比,DML是修改数据库表中的数据,而 DDL 是修改数据中表的结构。

2.DQL(数据查询语言)

数据查询语言(Data Query Language)简称DQL。是SQL语言中,负责进行数据查询而不会对数据本身进行修改的语句,这是最基本的SQL语句。

DQL的核心指令为select。通常与关键字from、where、group by、having、order by等一起使用,组成查询语句。

3.DML(数据操纵语言)

数据操纵语言(Data Manipulation Language)简称DML。过它可以实现对数据库的基本操作,对数据库其中的对象和数据运行访问工作的语言。

DML的主要功能是访问数据,因此其语法都是以读写数据库为主。

DML的核心指令为insert、update、delete。

4.TCL(事务控制语言)

事务控制语言(Transaction Control Language)简称TCL。用于管理数据库中的事务。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。

TCL经常被用于快速原型开发、脚本编程、GUI和测试等方面。

TCL的核心指令为commit、rollback。

5.DCL(数据控制语言)

数据控制语言 (Data Control Language)简称DCL。是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。

DCL用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等。

DCL的核心指令为grant、revoke。

四、1.数据库三范式

设计关系型数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。

目前关系型数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。一般来说,数据库只需要满足第三范式就行了。

1、第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据线;也就是说:每列的值具有原子性,不可再分割。
2、第二范式(2NF)是在第一范式(1NF)的基础上建立起来得,满足第二范式(2NF)必须先满足第一范式(1NF)。如果表是单主键,那么主键以外的列必须完全依赖于主键;如果表是复合主键,那么主键以外的列必须完全依赖于主键,不能仅依赖主键的一部分。
3、第三范式(3NF)是在第二范式的基础上建立起来的,即满足第三范式必须要先满足第二范式。第三范式(3NF)要求:表中的非主键列必须和主键直接相关而不能间接相关;也就是说:非主键列之间不能相关依赖。

第一范式:保证每列的原子性

第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库满足了第一范式。

第一范式需要根据系统的实际需求来定,比如有一张用户信息表:

在这里插入图片描述

一般来说"住址"设计成一个字段就行,但是如果经常访问"住址"中城市的部分,那么就非要将"住址"这个属性重新拆分为"省份"、“城市”、"地址"等多个部分进行存储,这样在对"住址"中某一部分进行操作的时候将非常方便。这么设计才算满足了数据库的第一范式,修改之后的表结构如图:

在这里插入图片描述

第二范式:保证一张表只描述一件事情

在这里插入图片描述

上表满足第一范式,即每个字段不可再分,但是这张表设计得并不好,或者说,这张表的设计并不满足第二范式。因为这张表里面描述了两件事情:学生信息、课程信息,“学分"完全依赖于"课程名称”、“姓名"与"年龄"完全依赖于"学号”。这么做的后果是:

1、数据冗余:同一门课程由n个学生选修,"学分"重复n-1次;同一个学生选修了m门课程,姓名和年龄重复m-1次

2、更新异常:若调整了某门课程的学分,数据表中所有行的"学分"值都需要更新,否则会出现同一门课程学分不同的情况

3、插入异常:假设要开一门新课程,暂时没有人选修,那么由于没有"学号"关键字,"课程"与"学分"也无法记录入数据库

4、删除异常:假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,"课程"和"学分"也被删除了,显然,这最终可能会导致插入异常

所以,此表的结构必须修改,修改后如下:
在这里插入图片描述

第三范式----保证每列都和主键直接相关

第三范式又和第二范式相关,用第三范式的定义描述第三范式就是,数据库表中如果不存在非关键字段任一候选关键字段的传递函数依赖则符合第三范式,所谓传递函数依赖指的是如果存在"A–>B–>C"的决定关系,则C传递函数依赖于A。也就是说表中的字段和主键直接对应不依靠其他中间字段,说白了就是,决定某字段值的必须是主键

举个例子,看一下如下的表结构:
在这里插入图片描述

第三范式和第二范式有点像,从这张数据库表结构中可以看出,“姓名”、“年龄”、“学院"和主键"学号"直接关联,但是"学院地点”、“学院电话"却不直接和主键"学号"相关联,和"学院电话"直接相关联的是"学院”,如果表结构这么设计,同样会造成和第二范式一样的数据冗余、更新异常、插入异常、删除异常的问题。

修改之后的表结构如下图:

在这里插入图片描述

2.数据库设计概述

无论是创建动态网站,还是创建桌面窗口应用程序,数据库设计的重要性都不言而喻。只有优良的数据库设计,才能提高系统的性能,提供更好的服务。糟糕的数据库设计会出现很多问题,影响我们的工作效率,服务效率和用户的使用效率。

糟糕的数据库设计表现在以下几个方面:

  • 访问数据效率低下
  • 存在大量的数据冗余,浪费存储空间
  • 更新和检索数据时会出现许多问题

良好的数据库设计表现在以下几方面:

  • 访问效率高
  • 减少数据冗余,节省存储空间,便于进一步扩展
  • 可以使应用程序的开发变得更容易

数据库中的表设计也是决定数据库系统效率的重要因素。表设计就是对数据库中的数据实体及数据实体之间的关系,进行规划和结构化的过程。

3.数据库设计步骤

在了解数据库设计步骤之前,我们先来了解一下软件项目的开发周期,如下:

  • 需求分析
  • 概要设计
  • 逻辑设计/详细设计
  • 代码编写
  • 软件测试
  • 安装部署

其中,项目开始的第一步都是根据客户需求去设计数据库。代码编写阶段就是根据选择的数据库进行物理实现,编写代码实现应用。

按照规范设计的方法,考虑数据库及其应用系统开发全过程,将数据库设计分为以下 6 个阶段:

3.1 需求分析

需求分析是数据库设计的第一步,是最困难、最耗费时间的一步,也是整个设计过程的基础。

本阶段的主要任务是对现实世界中要处理的对象(公司、部门及企业,也可以理解成客户)进行详细调查,然后通过分析,逐步明确客户/用户对系统的需求,包括数据需求和业务处理需求。

需求分析是否做的充分和准确,直接决定了在其上构建数据库大厦的速度与质量。需求分析做的不好,会导致整个数据库设计返工重做。

3.2 概要设计

概要设计是数据库设计的关键,通过综合、归纳与抽象用户需求,形成一个具体 DBMS 的概念模型,也就是绘制数据库的 E-R 图。

E-R 图主要用于在项目团队内部,设计人员和客户之间进行沟通,确认需求信息的正确性和完整性。

3.3 逻辑结构设计

将 E-R 图转换为多张表,进行逻辑设计,确认各表的主外键,并应用数据库设计的三大范式进行审核,对其优化。
在这阶段,E-R 图非常重要。大家要学会根据各个实体定义的属性来画出总体的 E-R 图。

3.4 物理设计阶段

经项目组开会讨论确定 E-R 图后,根据项目的技术实现,团队开发能力及项目的成本预算,选择具体的数据库(如 MySQL 或 Oracle 等)进行物理实现。

3.5 数据库实施阶段

运用 DBMS 提供的数据语言(例如 SQL)、工具及宿主语言(例如 Java),根据逻辑设计和物理设计的结果建立数据库,编制与调试应用程序,组织数据入库,并进行试运行。

3.6 数据库运行和维护阶段

数据库应用系统经过试运行后即可投入正式运行。在运行过程中必须不断地对其进行评价、调整与修改。

总之设计一个完善的数据库应用系统是不可能一蹴而就的,它往往是上述 6 个阶段的不断反复。

五、数据类型和存储引擎

MySQL的数据类型有大概可以分为5种,分别是 整数类型、浮点数类型和定点数类型、日期和时间类型、字符串类型、二进制类型。现在可以来看看你对这5种类型的熟悉程度,哪个看起来懵逼了,那就说明自己哪个不熟悉,不理解。

注意:整数类型和浮点数类型可以统称为数值数据类型,这不难理解。

数值数据类型

整数类型:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT

浮点数类型:FLOAT、DOUBLE

定点小数:DECIMAL

日期/时间类型

YEAR、TIME、DATE、DATETIME、TIMESTAMP

字符串类型

CHAR、VARCHAR、TEXT、ENUM、SET等

二进制类型

BIT、BINARY、VARBINARY、BLOB

枚举类型

​ enum、set

1.整数类型

在这里插入图片描述

2.浮点数类型和定点数类型

在MySQL数据库中使用浮点数和定点数来存储小数。浮点数的类型有两种:单精度浮点数类型(FLOAT)和双精度浮点数类型(DOUBLE)。而定点数类型只有一种即DECIMAL类型。下图列举了 MySQL中浮点数和定点数类型所对应的字节大小及其取值范围:

在这里插入图片描述

3.字符串类型

在MySQL中常用CHAR 和 VARCHAR 表示字符串。两者不同的是:VARCHAR存储可变长度的字符串。
当数据为CHAR(M)类型时,不管插入值的长度是实际是多少它所占用的存储空间都是M个字节;而VARCHAR(M)所对应的数据所占用的字节数为实际长度加1

在这里插入图片描述

3.1、CHAR和VARCHAR

CHAR(M):为固定长度字符串,固定长度的意思就是M的值为多少,那么该M的值就是其实际存储空间的值,就算插入的数据少于M位,其存储空间还是那么大,多余的用空格补齐。在输出时,空格将被删除不输出。M最大为255,比如char(4),如果插入abc,则存储的值为’abc '后面多了一个空格,输出还是‘abc’, 存储空间还是占4个字节。M最大为255

VARCHAR(M):长度可变的字符串,跟CHAR相反,会根据实际的大小值来确定存储空间的大小,比如 VARCHAR(4),插入’ab’,则存储空间为3字节,看上面图就知道VARCHAR会多一个字节用来存储长度,M最大为65535.
在这里插入图片描述

在这里插入图片描述

5.日期与时间类型

MySQL提供的表示日期和时间的数据类型分别是 :YEAR、DATE、TIME、DATETIME 和 TIMESTAMP。下图列举了日期和时间数据类型所对应的字节数、取值范围、日期格式以及零值:

在这里插入图片描述

5.1 YEAR类型

YEAR类型用于表示年份,在MySQL中,可以使用以下三种格式指定YEAR类型 的值。
1、使用4位字符串或数字表示,范围为’1901’—'2155’或1901—2155。例如,输人 ‘2019’或2019插人到数据库中的值均为2019。
2、使用两位字符串表示,范围为’00’—‘99’。其中,‘00’—'69’范围的值会被转换为 2000—2069范围的YEAR值,‘70’—'99’范围的值会被转换为1970—1999范围的YEAR 值。例如,输人’19’插人到数据库中的值为2019。
3、使用两位数字表示,范围为1—99。其中,1—69范围的值会被转换为2001— 2069范围的YEAR值,70—99范围的值会被转换为1970—1999范围的YEAR值。例 如,输人19插入到数据库中的值为2019。
请注意:当使用YEAR类型时,一定要区分’0’和0。因为字符串格式的’0’表示的YEAR值是2000而数字格式的0表示的YEAR值是0000。

5.2 TIME类型

TIME类型用于表示时间值,它的显示形式一般为HH:MM:SS,其中,HH表示小时, MM表示分,SS表示秒。在MySQL中,可以使用以下3种格式指定TIME类型的值。
1、以’D HH:MM:SS’字符串格式表示。其中,D表示日可取0—34之间的值, 插人数据时,小时的值等于(DX24+HH)。例如,输入’2 11:30:50’插人数据库中的日期为59:30:50。
2、以’HHMMSS’字符串格式或者HHMMSS数字格式表示。 例如,输人’115454’或115454,插入数据库中的日期为11:54:54
3、使用CURRENT_TIME或NOW()输人当前系统时间。

5.3 DATETIME类型

DATETIME类型用于表示日期和时间,它的显示形式为’YYYY-MM-DD HH: MM:SS’,其中,YYYY表示年,MM表示月,DD表示日,HH表示小时,MM表示分,SS 表示秒。在MySQL中,可以使用以下4种格式指定DATETIME类型的值。
以’YYYY-MM-DD HH:MM:SS’或者’YYYYMMDDHHMMSS’字符串格式表示的日期和时间,取值范围为’1000-01-01 00:00:00’—‘9999-12-3 23:59:59’。例如,输人’2019-01-22 09:01:23’或 ‘20140122_0_90123’插人数据库中的 DATETIME 值都为 2019-01-22 09:01:23。
1、以’YY-MM-DD HH:MM:SS’或者’YYMMDDHHMMSS’字符串格式表示的日期和时间,其中YY表示年,取值范围为’00’—‘99’。与DATE类型中的YY相同,‘00’— '69’范围的值会被转换为2000—2069范围的值,‘70’—'99’范围的值会被转换为1970—1999范围的值。
2、以YYYYMMDDHHMMSS或者YYMMDDHHMMSS数字格式表示的日期 和时间。例如,插入20190122090123或者190122090123,插人数据库中的DATETIME值都 为 2019-01-22 09:01:23。
3、使用NOW来输人当前系统的日期和时间。

5.4 TIMESTAMP类型

TIMESTAMP类型用于表示日期和时间,它的显示形式与DATETIME相同但取值范围比DATETIME小。在此,介绍几种TIMESTAMP类型与DATATIME类型不同的形式:
1、使用CURRENT_TIMESTAMP输人系统当前日期和时间。
2、输人NULL时系统会输入系统当前日期和时间。
3、无任何输人时系统会输入系统当前日期和时间。

6.二进制类型

在MySQL中常用BLOB存储二进制类型的数据,例如:图片、PDF文档等。BLOB类型分为如下四种:

在这里插入图片描述

7.mysql存储引擎

数据库存储引擎是数据库底层软件组件,数据库管理系统使用数据引擎进行创建、查询、更新和删除数据操作。

简而言之,存储引擎就是指表的类型。数据库的存储引擎决定了表在计算机中的存储方式。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎还可以获得特定的功能。 现在许多数据库管理系统都支持多种不同的存储引擎。MySQL的核心就是存储引擎。

MySQL 提供了多个不同的存储引擎,包括处理事务安全表的引擎和处理非事务安全表的引擎。在 MySQL 中,不需要在整个服务器中使用同一种存储引擎,针对具体的要求,可以对每一个表使用不同的存储引擎。

MySQL 5.7 支持的存储引擎有 InnoDB、MyISAM、Memory、Merge、Archive、CSV、BLACKHOLE 等。可以使用SHOW ENGINES;语句查看系统所支持的引擎类型,结果如图所示。
在这里插入图片描述

Support 列的值表示某种引擎是否能使用,YES表示可以使用,NO表示不能使用,DEFAULT表示该引擎为当前默认的存储引擎。

在这里插入图片描述

在使用 MySQL 数据库管理系统时,选择一个合适的存储引擎是一个非常复杂的问题。

不同的存储引擎都有各自的特性、优势和使用的场合,正确的选择存储引擎可以提高应用的使用效率。 为了能够正确地选择存储引擎,必须掌握各种存储引擎的特性。下面重点介绍几种常用的存储引擎,它们对各种特性的支持如下表所示。

在这里插入图片描述

表中主要介绍了 MyISAM、InnoDB 和 MEMORY 三种存储引擎特性的对比。下面详细介绍这 3 个存储引擎的应用场合并给出相应的建议。

1) MyISAM

在 MySQL 5.1 版本及之前的版本,MyISAM 是默认的存储引擎。MyISAM 存储引擎不支持事务和外键,所以访问速度比较快。如果应用主要以读取和写入为主,只有少量的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择 MyISAM 存储引擎是非常适合的。

MyISAM 是在 Web 数据仓储和其他应用环境下最常使用的存储引擎之一。

2) InnoDB

MySQL 5.5 版本之后默认的事务型引擎修改为 InnoDB。

InnoDB 存储引擎在事务上具有优势,即支持具有提交、回滚和崩溃恢复能力的事务安装,所以比 MyISAM 存储引擎占用更多的磁盘空间。

如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包括很多的更新、删除操作,那么 InnoDB 存储引擎是比较合适的选择。

InnoDB 存储引擎除了可以有效地降低由于删除和更新导致的锁定,还可以确保事务的完整提交(Commit)和回滚(Rollback),对于类似计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB 都是合适的选择。

3) MEMORY

MEMORY 存储引擎将所有数据保存在 RAM 中,所以该存储引擎的数据访问速度快,但是安全上没有保障。

MEMORY 对表的大小有限制,太大的表无法缓存在内存中。由于使用 MEMORY 存储引擎没有安全保障,所以要确保数据库异常终止后表中的数据可以恢复。

如果应用中涉及数据比较少,且需要进行快速访问,则适合使用 MEMORY 存储引擎。

4)修改数据表的存储引擎

MySQL 中修改数据表的存储引擎的语法格式如下:

ALTER TABLE <表名> ENGINE=<存储引擎名>;

ENGINE 关键字用来指明新的存储引擎。

下面将数据表 student 的存储引擎修改为 MyISAM。

在修改存储引擎之前,先使用 SHOW CREATE TABLE 语句查看 student 表当前的存储引擎,结果如下。

mysql> SHOW CREATE TABLE student \G
*************************** 1. row ***************************
       Table: student
Create Table: CREATE TABLE `student` (
  `stuId` int(4) DEFAULT NULL,
  `id` int(4) DEFAULT NULL,
  `name` varchar(20) DEFAULT NULL,
  `stuno` int(11) DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `age` int(4) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

可以看到,student 表当前的存储引擎为 InnoDB。

下面将 student 表的存储引擎修改为 MyISAM 类型,SQL 语句为:

ALTER TABLE student ENGINE=MyISAM;

使用 SHOW CREATE TABLE 语句再次查看 student 表的存储引擎,会发现student 表的存储引擎变成了“MyISAM”,SQL 语句和运行结果如下:

mysql> SHOW CREATE TABLE student \G;
*************************** 1. row ***************************
       Table: student
Create Table: CREATE TABLE `student` (
  `stuId` int(4) DEFAULT NULL,
  `id` int(4) DEFAULT NULL,
  `name` varchar(20) DEFAULT NULL,
  `stuno` int(11) DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `age` int(4) DEFAULT NULL,
  `stuId2` int(4) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

以上这种方法适用于修改单个表的存储引擎,如果希望修改默认的存储引擎,就需要修改 my.cnf 配置文件。在 my.cnf 配置文件的 [mysqld] 后面加入以下语句:

default-storage-engine=存储引擎名称

然后保存就可以了。

5)总结

不同应用的特点是千差万别的,选择适应存储引擎才是最佳方案也不是绝对的,这需要根据实际应用进行测试,从而得到最适合的结果。

六、mysql约束、函数、运算符

在 MySQL 中,约束是指对表中数据的一种约束,能够帮助数据库管理员更好地管理数据库,并且能够确保数据库中数据的正确性和有效性。 例如,在数据表中存放年龄的值时,如果存入 200、300 这些无效的值就毫无意义了。因此,使用约束来限定表中的数据范围是很有必要的。 在 MySQL 中,主要支持以下 6 种约束:

1.主键约束

主键(PRIMARY KEY)的完整称呼是“主键约束”,是 MySQL中使用最为频繁的约束。一般情况下,为了便于 DBMS 更快的查找到表中的记录,都会在表中设置一个主键。主键分为单字段主键和多字段联合主键,本节将分别讲解这两种主键约束的创建、修改和删除。

使用主键应注意以下几点:

  • 每个表只能定义一个主键。
  • 主键值必须唯一标识表中的每一行,且不能为 NULL,即表中不可能存在有相同主键值的两行数据。这是唯一性原则。
  • 一个字段名只能在联合主键字段表中出现一次。
  • 联合主键不能包含不必要的多余字段。当把联合主键的某一字段删除后,如果剩下的字段构成的主键仍然满足唯一性原则,那么这个联合主键是不正确的。这是最小化原则。

在创建表时设置主键约束

1.1设置单字段主键

在 CREATE TABLE 语句中,通过 PRIMARY KEY 关键字来指定主键。 在定义字段的同时指定主键,语法格式如下:

<字段名> <数据类型> PRIMARY KEY [默认值]

例 1
在 test_db 数据库中创建 tb_emp3 数据表,其主键为 id,SQL 语句和运行结果如下。

mysql> CREATE TABLE tb_emp3
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT
    -> );
Query OK, 0 rows affected (0.37 sec)
mysql> DESC tb_emp3;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.14 sec)

或者是在定义完所有字段之后指定主键,语法格式如下:
[CONSTRAINT <约束名>] PRIMARY KEY [字段名]

例 2
在 test_db 数据库中创建 tb_emp4 数据表,其主键为 id,SQL 语句和运行结果如下。

mysql> CREATE TABLE tb_emp4
    -> (
    -> id INT(11),
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT,
    -> PRIMARY KEY(id)
    -> );
Query OK, 0 rows affected (0.37 sec)
mysql> DESC tb_emp4;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.14 sec)

1.2设置联合主键

所谓的联合主键,就是这个主键是由一张表中多个字段组成的。 比如,设置学生选课数据表时,使用学生编号做主键还是用课程编号做主键呢?如果用学生编号做主键,那么一个学生就只能选择一门课程。如果用课程编号做主键,那么一门课程只能有一个学生来选。显然,这两种情况都是不符合实际情况的。

实际上设计学生选课表,要限定的是一个学生只能选择同一课程一次。因此,学生编号和课程编号可以放在一起共同作为主键,这也就是联合主键了。

主键由多个字段联合组成,语法格式如下:

PRIMARY KEY [字段1,字段2,…,字段n]
mysql> CREATE TABLE tb_emp5
    -> (
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT,
    -> PRIMARY KEY(name,deptId)
    -> );
Query OK, 0 rows affected (0.37 sec)
mysql> DESC tb_emp5;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| name   | varchar(25) | NO   | PRI | NULL    |       |
| deptId | int(11)     | NO   | PRI | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.14 sec)

在修改表时设置主键约束

主键约束不仅可以在创建表的同时创建,也可以在修改表时添加。但是需要注意的是,设置成主键约束的字段中不允许有空值。 在修改数据表时添加主键约束的语法格式如下:

ALTER TABLE <数据表名> ADD PRIMARY KEY(<字段名>);

查看 tb_emp2 数据表的表结构,SQL 语句和运行结果如下所示。

mysql> DESC tb_emp2;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(30) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.14 sec)

修改数据表 tb_emp2,将字段 id 设置为主键,SQL 语句和运行结果如下。

mysql> ALTER TABLE tb_emp2
    -> ADD PRIMARY KEY(id);
Query OK, 0 rows affected (0.94 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> DESC tb_emp2;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(30) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.12 sec)

通常情况下,当在修改表时要设置表中某个字段的主键约束时,要确保设置成主键约束的字段中值不能够有重复的,并且要保证是非空的。否则,无法设置主键约束。

1.3删除主键约束

删除主键约束的语法格式如下所示:

ALTER TABLE <数据表名> DROP PRIMARY KEY;

删除 tb_emp2 表中的主键约束,SQL 语句和运行结果如下。

mysql> ALTER TABLE tb_emp2
    -> DROP PRIMARY KEY;
Query OK, 0 rows affected (0.94 sec)
Records: 0  Duplicates: 0  Warnings: 0

1.4主键自增长

在 MySQL 中,当主键定义为自增长后,这个主键的值就不再需要用户输入数据了,而由数据库系统根据定义自动赋值。每增加一条记录,主键会自动以相同的步长进行增长。

通过给字段添加 AUTO_INCREMENT属性来实现主键自增长。语法格式如下:

字段名 数据类型 AUTO_INCREMENT
  • 默认情况下,AUTO_INCREMENT 的初始值是 1,每新增一条记录,字段值自动加 1。
  • 一个表中只能有一个字段使用 AUTO_INCREMENT 约束,且该字段必须有唯一索引,以避免序号重复(即为主键或主键的一部分)。
  • AUTO_INCREMENT 约束的字段必须具备 NOT NULL 属性。
  • AUTO_INCREMENT 约束的字段只能是整数类型(TINYINT、SMALLINT、INT、BIGINT 等)。
  • AUTO_INCREMENT 约束字段的最大值受该字段的数据类型约束,如果达到上限,AUTO_INCREMENT 就会失效。

定义数据表 tb_student,指定表中 id 字段递增,SQL 语句和运行结果如下:

mysql> CREATE TABLE tb_student(
    -> id INT(4) PRIMARY KEY AUTO_INCREMENT,
    -> name VARCHAR(25) NOT NULL
    -> );
Query OK, 0 rows affected (0.07 sec)

上述语句执行成功后,会创建名为 tb_student 的数据表。其中,id 为主键,每插入一条新记录,id 的值就会在前一条记录的基础上自动加 1。name 为非空字段,该字段的值不能为空值(NULL)。

2.外键约束

是表的一个特殊字段,经常与主键约束一起使用。对于两个具有关联关系的表而言,相关联字段中主键所在的表就是主表(父表),外键所在的表就是从表(子表)。

外键用来建立主表与从表的关联关系,为两个表的数据建立连接,约束两个表中数据的一致性和完整性。比如,一个水果摊,只有苹果、桃子、李子、西瓜等 4 种水果,那么,你来到水果摊要买水果就只能选择苹果、桃子、李子和西瓜,其它的水果都是不能购买的。

主表删除某条记录时,从表中与之对应的记录也必须有相应的改变。一个表可以有一个或多个外键,外键可以为空值,若不为空值,则每一个外键的值必须等于主表中主键的某个值。

定义外键时,需要遵守下列规则:

  • 主表必须已经存在于数据库中,或者是当前正在创建的表。如果是后一种情况,则主表与从表是同一个表,这样的表称为自参照表,这种结构称为自参照完整性。
  • 必须为主表定义主键。
  • 主键不能包含空值,但允许在外键中出现空值。也就是说,只要外键的每个非空值出现在指定的主键中,这个外键的内容就是正确的。
  • 在主表的表名后面指定列名或列名的组合。这个列或列的组合必须是主表的主键或候选键。
  • 外键中列的数目必须和主表的主键中列的数目相同。
  • 外键中列的数据类型必须和主表主键中对应列的数据类型相同。

2.1在创建表时设置外键约束

在 CREATE TABLE 语句中,通过 FOREIGN KEY 关键字来指定外键,

具体的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I0IZXe5C-1684250726471)(mysql.assets/1682077099388.png)]

为了展现表与表之间的外键关系,本例在 test_db 数据库中创建一个部门表 tb_dept1,表结构如下表所示。

字段名称数据类型备注
idINT(11)部门编号
nameVARCHAR(22)部门名称
locationVARCHAR(22)部门位置

创建 tb_dept1 的 SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_dept1
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(22) NOT NULL,
    -> location VARCHAR(50)
    -> );
Query OK, 0 rows affected (0.37 sec)

创建数据表 tb_emp6,并在表 tb_emp6 上创建外键约束,让它的键 deptId 作为外键关联到表 tb_dept1 的主键 id,SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_emp6
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT,
    -> CONSTRAINT fk_emp_dept1
    -> FOREIGN KEY(deptId) REFERENCES tb_dept1(id)
    -> );
Query OK, 0 rows affected (0.37 sec)

mysql> DESC tb_emp6;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  | MUL | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (1.33 sec)

以上语句执行成功之后,在表 tb_emp6 上添加了名称为 fk_emp_dept1 的外键约束,外键名称为 deptId,其依赖于表 tb_dept1 的主键 id。

注意:从表的外键关联的必须是主表的主键,且主键和外键的数据类型必须一致。例如,两者都是 INT 类型,或者都是 CHAR 类型。如果不满足这样的要求,在创建从表时,就会出现“ERROR 1005(HY000): Can’t create table”错误。

2.2在修改表时添加外键约束

外键约束也可以在修改表时添加,但是添加外键约束的前提是:从表中外键列中的数据必须与主表中主键列中的数据一致或者是没有数据。

在修改数据表时添加外键约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-63JzKpZR-1684250726472)(mysql.assets/1682077338509.png)]

修改数据表 tb_emp2,将字段 deptId 设置为外键,与数据表 tb_dept1 的主键 id 进行关联,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp2
    -> ADD CONSTRAINT fk_tb_dept1
    -> FOREIGN KEY(deptId)
    -> REFERENCES tb_dept1(id);
Query OK, 0 rows affected (1.38 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE tb_emp2\G
*************************** 1. row ***************************
       Table: tb_emp2
Create Table: CREATE TABLE `tb_emp2` (
  `id` int(11) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `deptId` int(11) DEFAULT NULL,
  `salary` float DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_tb_dept1` (`deptId`),
  CONSTRAINT `fk_tb_dept1` FOREIGN KEY (`deptId`) REFERENCES `tb_dept1` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.12 sec)

注意:在为已经创建好的数据表添加外键约束时,要确保添加外键约束的列的值全部来源于主键列,并且外键列不能为空。

2.3删除外键约束

当一个表中不需要外键约束时,就需要从表中将其删除。外键一旦删除,就会解除主表和从表间的关联关系。

删除外键约束的语法格式如下所示:

ALTER TABLE <表名> DROP FOREIGN KEY <外键约束名>;

删除数据表 tb_emp2 中的外键约束 fk_tb_dept1,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp2
    -> DROP FOREIGN KEY fk_tb_dept1;
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE tb_emp2\G
*************************** 1. row ***************************
       Table: tb_emp2
Create Table: CREATE TABLE `tb_emp2` (
  `id` int(11) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `deptId` int(11) DEFAULT NULL,
  `salary` float DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_tb_dept1` (`deptId`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.00 sec)

可以看到,tb_emp2 中已经不存在 FOREIGN KEY,原有的名称为 fk_emp_dept 的外键约束删除成功。

3.唯一约束

MySQL

唯一约束(Unique Key)

是指所有记录中字段的值不能重复出现。例如,为 id 字段加上唯一性约束后,每条记录的 id 值都是唯一的,不能出现重复的情况。如果其中一条记录的 id 值为‘0001’,那么该表中就不能出现另一条记录的 id 值也为‘0001’。

唯一约束与主键约束相似的是它们都可以确保列的唯一性。不同的是,唯一约束在一个表中可有多个,并且设置唯一约束的列允许有空值,但是只能有一个空值。而主键约束在一个表中只能有一个,且不允许有空值。比如,在用户信息表中,为了避免表中用户名重名,可以把用户名设置为唯一约束。

3.1在创建表时设置唯一约束

唯一约束可以在创建表时直接设置,通常设置在除了主键以外的其它列上。

在定义完列之后直接使用 UNIQUE关键字指定唯一约束,语法格式如下:

<字段名> <数据类型> UNIQUE

创建数据表 tb_dept2,指定部门的名称唯一,SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_dept2
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(22) UNIQUE,
    -> location VARCHAR(50)
    -> );
Query OK, 0 rows affected (0.37 sec)

mysql> DESC tb_dept2;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id       | int(11)     | NO   | PRI | NULL    |       |
| name     | varchar(40) | YES  | UNI | NULL    |       |
| location | varchar(50) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.08 sec)

3.2在修改表时添加唯一约束

在修改表时添加唯一约束的语法格式为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILgsqQ8f-1684250726473)(mysql.assets/1682078516282.png)]

修改数据表 tb_dept1,指定部门的名称唯一,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept1
    -> ADD CONSTRAINT unique_name UNIQUE(name);
Query OK, 0 rows affected (0.63 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept1;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id       | int(11)     | NO   | PRI | NULL    |       |
| name     | varchar(22) | NO   | UNI | NULL    |       |
| location | varchar(50) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

3.3删除唯一约束

在 MySQL 中删除唯一约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PO7Oo0sd-1684250726473)(mysql.assets/1682078544274.png)]

删除数据表 tb_dept1 中的唯一约束 unique_name,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept1
    -> DROP INDEX unique_name;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept1;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id       | int(11)     | NO   | PRI | NULL    |       |
| name     | varchar(22) | NO   |     | NULL    |       |
| location | varchar(50) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

4.默认值约束

默认值约束(Default Constraint)用来指定某列的默认值。在表中插入一条新记录时,如果没有为某个字段赋值,系统就会自动为这个字段插入默认值。

例如,员工信息表中,部门位置在北京的较多,那么部门位置就可以默认为“北京”,系统就会自动为这个字段赋值为“北京”。

默认值约束通常用在已经设置了非空约束的列,这样能够防止数据表在录入数据时出现错误。

4.1在创建表时设置默认值约束

创建表时可以使用

DEFAULT

关键字设置默认值约束,具体的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RP3X8q9z-1684250726473)(mysql.assets/1682078762060.png)]

其中,“默认值”为该字段设置的默认值,如果是字符类型的,要用单引号括起来。

创建数据表 tb_dept3,指定部门位置默认为 Beijing,SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_dept3
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(22),
    -> location VARCHAR(50) DEFAULT 'Beijing'
    -> );
Query OK, 0 rows affected (0.37 sec)

mysql> DESC tb_dept3;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id       | int(11)     | NO   | PRI | NULL    |       |
| name     | varchar(22) | YES  |     | NULL    |       |
| location | varchar(50) | YES  |     | Beijing |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.06 sec)

以上语句执行成功之后,表 tb_dept3 上的字段 location 拥有了一个默认值 Beijing,新插入的记录如果没有指定部门位置,则默认都为 Beijing

注意:在创建表时为列添加默认值,可以一次为多个列添加默认值,需要注意不同列的数据类型。

4.2在修改表时添加默认值约束

修改表时添加默认值约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tkAyigt-1684250726474)(mysql.assets/1682078798795.png)]

修改数据表 tb_dept3,将部门位置的默认值修改为 Shanghai,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept3
    -> CHANGE COLUMN location
    -> location VARCHAR(50) DEFAULT 'Shanghai';
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept3;
+----------+-------------+------+-----+----------+-------+
| Field    | Type        | Null | Key | Default  | Extra |
+----------+-------------+------+-----+----------+-------+
| id       | int(11)     | NO   | PRI | NULL     |       |
| name     | varchar(22) | YES  |     | NULL     |       |
| location | varchar(50) | YES  |     | Shanghai |       |
+----------+-------------+------+-----+----------+-------+
3 rows in set (0.00 sec)

4.3删除默认值约束

当一个表中的列不需要设置默认值时,就需要从表中将其删除。

修改表时删除默认值约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4Qk5qRy-1684250726474)(mysql.assets/1682078826305.png)]

修改数据表 tb_dept3,将部门位置的默认值约束删除,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept3
    -> CHANGE COLUMN location
    -> location VARCHAR(50) DEFAULT NULL;
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept3;
+----------+-------------+------+-----+----------+-------+
| Field    | Type        | Null | Key | Default  | Extra |
+----------+-------------+------+-----+----------+-------+
| id       | int(11)     | NO   | PRI | NULL     |       |
| name     | varchar(22) | YES  |     | NULL     |       |
| location | varchar(50) | YES  |     | NULL     |       |
+----------+-------------+------+-----+----------+-------+
3 rows in set (0.00 sec)

5.检查约束

检查约束(CHECK)是用来检查数据表中字段值有效性的一种手段,可以通过 CREATE TABLE 或 ALTER TABLE 语句实现。设置检查约束时要根据实际情况进行设置,这样能够减少无效数据的输入。

默认值约束和非空约束也可看作是特殊的检查约束。

检查约束使用 CHECK 关键字,具体的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g6y9DoTD-1684250726474)(mysql.assets/1682079262064.png)]

其中,“表达式”指的就是 SQL 表达式,用于指定需要检查的限定条件。若将 CHECK 约束子句置于表中某个列的定义之后,则这种约束也称为基于列的 CHECK 约束。

在更新表数据的时候,系统会检查更新后的数据行是否满足 CHECK 约束中的限定条件。MySQL 可以使用简单的表达式来实现 CHECK 约束,也允许使用复杂的表达式作为限定条件,例如在限定条件中加入子查询。

注意:若将 CHECK 约束子句置于所有列的定义以及主键约束和外键定义之后,则这种约束也称为基于表的 CHECK 约束。该约束可以同时对表中多个列设置限定条件。

5.1在创建表时设置检查约束

一般情况下,如果系统的表结构已经设计完成,那么在创建表时就可以为字段设置检查约束了。

创建表时设置检查约束的语法格式如下:

CHECK <表达式>

在 test_db 数据库中创建 tb_emp7 数据表,要求 salary 字段值大于 0 且小于 10000,SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_emp7
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT,
    -> CHECK(salary>0 AND salary<100),
    -> FOREIGN KEY(deptId) REFERENCES tb_dept1(id)
    -> );
Query OK, 0 rows affected (0.37 sec)

5.2在修改表时添加检查约束

如果一个表创建完成,可以通过修改表的方式为表添加检查约束。

修改表时设置检查约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2jhPT7m-1684250726475)(mysql.assets/1682079359407.png)]

修改 tb_emp7 数据表,要求 id 字段值大于 0,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp7
    -> ADD CONSTRAINT check_id
    -> CHECK(id>0);
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

5.3删除检查约束

修改表时删除检查约束的语法格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RD97DTnH-1684250726475)(mysql.assets/1682079382718.png)]

删除 tb_emp7 表中的 check_id 检查约束,SQL 语句和运行结果如下所示:

mysql> ALTER TABLE tb_emp7
    -> DROP CONSTRAINT check_id;
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

6.非空约束

非空约束(NOT NULL)

指字段的值不能为空。对于使用了非空约束的字段,如果用户在添加数据时没有指定值,数据库系统就会报错。可以通过 CREATE TABLE 或 ALTER TABLE 语句实现。在表中某个列的定义后加上关键字 NOT NULL 作为限定词,来约束该列的取值不能为空。

比如,在用户信息表中,如果不添加用户名,那么这条用户信息就是无效的,这时就可以为用户名字段设置非空约束。

6.1 在创建表时设置非空约束

创建数据表 tb_dept4,指定部门名称不能为空,SQL 语句和运行结果如下所示。

mysql> CREATE TABLE tb_dept4
    -> (
    -> id INT(11) PRIMARY KEY,
    -> name VARCHAR(22) NOT NULL,
    -> location VARCHAR(50)
    -> );
Query OK, 0 rows affected (0.37 sec)

mysql> DESC tb_dept3;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id       | int(11)     | NO   | PRI | NULL    |       |
| name     | varchar(22) | NO   |     | NULL    |       |
| location | varchar(50) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.06 sec)

6.2在修改表时添加非空约束

如果在创建表时忘记了为字段设置非空约束,也可以通过修改表进行非空约束的添加。

修改表时设置非空约束的语法格式如下:

<字段名> <数据类型> NOT NULL;

修改数据表 tb_dept4,指定部门位置不能为空,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept4
    -> CHANGE COLUMN location
    -> location VARCHAR(50) NOT NULL;
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept4;
+----------+-------------+------+-----+----------+-------+
| Field    | Type        | Null | Key | Default  | Extra |
+----------+-------------+------+-----+----------+-------+
| id       | int(11)     | NO   | PRI | NULL     |       |
| name     | varchar(22) | NO   |     | NULL     |       |
| location | varchar(50) | NO   |     | NULL     |       |
+----------+-------------+------+-----+----------+-------+
3 rows in set (0.00 sec)

6.3删除非空约束

修改表时删除非空约束的语法规则如下:

ALTER TABLE <数据表名>
CHANGE COLUMN <字段名> <字段名> <数据类型> NULL;

修改数据表 tb_dept4,将部门位置的非空约束删除,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_dept4
    -> CHANGE COLUMN location
    -> location VARCHAR(50) NULL;
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_dept4;
+----------+-------------+------+-----+----------+-------+
| Field    | Type        | Null | Key | Default  | Extra |
+----------+-------------+------+-----+----------+-------+
| id       | int(11)     | NO   | PRI | NULL     |       |
| name     | varchar(22) | NO   |     | NULL     |       |
| location | varchar(50) | YES  |     | NULL     |       |
+----------+-------------+------+-----+----------+-------+
3 rows in set (0.00 sec)

7.运算符

7.1算术运算符

在这里插入图片描述

7.2逻辑运算符

逻辑运算符又称为布尔运算符,用来确定表达式的真和假。MySQL中支持的逻辑运算符如下表所示。
运算符 作用
在这里插入图片描述

逻辑非运算(NOT 或者 !)

NOT和!都是逻辑非运算符,返回和操作数相反的结果,具体语法规则为:
当操作数为 0(假)时,返回值为 1;
当操作数为非零值时,返回值为 0;
当操作数为 NULL 时,返回值为 NULL。

mysql> SELECT NOT 10,NOT(1-1),NOT-5,NOT NULL,NOT 1+1;
+--------+----------+-------+----------+---------+
| NOT 10 | NOT(1-1) | NOT-5 | NOT NULL | NOT 1+1 |
+--------+----------+-------+----------+---------+
|      0 |        1 |     0 |     NULL |       0 |
+--------+----------+-------+----------+---------+
1 row in set (0.00 sec)

mysql> SELECT !10,!(1-1),!-5,!NULL,!1+1;
+-----+--------+-----+-------+------+
| !10 | !(1-1) | !-5 | !NULL | !1+1 |
+-----+--------+-----+-------+------+
|   0 |      1 |   0 |  NULL |    1 |
+-----+--------+-----+-------+------+
1 row in set (0.00 sec)

mysql> SELECT !1+1;
+------+
| !1+1 |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

由运行结果可以看出,NOT 1+1和! 1+1的返回值不同,这是因为 NOT 与 ! 的优先级不同:
NOT 的优先级低于 +,因此NOT 1+1相当于NOT(1+1),先计算1+1,然后再进行 NOT 运算,由于操作数不为 0,因此NOT 1+1的结果是 0;
相反,! 的优先级别要高于 +,因此! 1+1相当于(!1)+1,先计算!1结果为 0,再加 1,最后结果为 1。

在使用运算符运算时,一定要注意运算符的优先级,如果不能确定计算顺序,最好使用括号,以保证运算结果的正确。

逻辑与运算符(AND 或者 &&)

AND 和 && 都是逻辑与运算符,具体语法规则为:

  • 当所有操作数都为非零值并且不为 NULL 时,返回值为 1;

  • 当一个或多个操作数为 0 时,返回值为 0;

  • 操作数中有任何一个为 NULL 时,返回值为 NULL。

逻辑或运算符(OR 或者 ||)

OR 和 || 都是逻辑或运算符,具体语法规则为:

  • 当两个操作数都为非 NULL 值时,如果有任意一个操作数为非零值,则返回值为 1,否则结果为 0;
  • 当有一个操作数为 NULL 时,如果另一个操作数为非零值,则返回值为 1,否则结果为NULL;
  • 假如两个操作数均为 NULL 时,则返回值为 NULL。
异或运算(XOR 运算符)

XOR 表示逻辑异或,具体语法规则为:

  • 当任意一个操作数为 NULL 时,返回值为 NULL;
  • 对于非 NULL 的操作数,如果两个操作数都是非 0 值或者都是 0 值,则返回值为 0;
  • 如果一个为0值,另一个为非 0 值,返回值为 1。

7.3比较运算符

当使用 SELECT 语句进行查询时,MySQL 允许用户对表达式的左边操作数和右边操作数进行比较,比较结果为真,则返回 1,为假则返回 0,比较结果不确定则返回 NULL。MySQL 支持的比较运算符如下表所示。
在这里插入图片描述

7.4位运算符

所谓位运算,就是按照内存中的比特位(Bit)进行操作,这是计算机能够支持的最小单位的运算。程序中所有的数据在内存中都是以二进制形式存储的,位运算就是对这些二进制数据进行操作。

位运算一般用于操作整数,对整数进行位运算才有实际的意义。整数在内存中是以补码形式存储的,正数的补码形式和原码形式相同,而负数的补码形式和它的原码形式是不一样的,这一点大家要特别注意;这意味着,对负数进行位运算时,操作的是它的补码,而不是它的原码。
在这里插入图片描述

7.5 IN 和 NOT IN

MySQL 中的 IN 运算符用来判断表达式的值是否位于给出的列表中;如果是,返回值为 1,否则返回值为 0。

NOT IN 的作用和 IN 恰好相反,NOT IN 用来判断表达式的值是否不存在于给出的列表中;如果不是,返回值为 1,否则返回值为 0。

8.函数

MySQL函数是 MySQL 数据库提供的内部函数,这些内部函数可以帮助用户更加方便地处理表中的数据。函数就像预定的公式一样存放在数据库里,每个用户都可以调用已经存在的函数来完成某些功能。

可以对 MySQL 常用函数进行简单的分类,大概包括数值型函数、字符串型函数、日期时间函数、聚合函数等。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七、数据库的基本操作

1.创建数据库

在 MySQL 中,可以使用 CREATE DATABASE 语句创建数据库,语法格式如下:

CREATE DATABASE [IF NOT EXISTS] <数据库名>
[[DEFAULT] CHARACTER SET <字符集名>] 
[[DEFAULT] COLLATE <校对规则名>];

语法说明如下:

[]中的内容是可选的。语法说明如下:

  • <数据库名>:创建数据库的名称。MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。注意在 MySQL 中不区分大小写。
  • IF NOT EXISTS:在创建数据库之前进行判断,只有该数据库目前尚不存在时才能执行操作。此选项可以用来避免数据库已经存在而重复创建的错误。
  • [DEFAULT] CHARACTER SET:指定数据库的字符集。指定字符集的目的是为了避免在数据库中存储的数据出现乱码的情况。如果在创建数据库时不指定字符集,那么就使用系统的默认字符集。
  • [DEFAULT] COLLATE:指定字符集的默认校对规则。

2.修改数据库

在 MySQL 中,可以使用 ALTER DATABASE 来修改已经被创建或者存在的数据库的相关参数。修改数据库的语法格式为:

ALTER DATABASE [数据库名] { 
[ DEFAULT ] CHARACTER SET <字符集名> |
[ DEFAULT ] COLLATE <校对规则名>}

语法说明如下:

  • ALTER DATABASE 用于更改数据库的全局特性。
  • 使用 ALTER DATABASE 需要获得数据库 ALTER 权限。
  • 数据库名称可以忽略,此时语句对应于默认数据库。
  • CHARACTER SET 子句用于更改默认的数据库字符集。

3.删除数据库

在 MySQL中,当需要删除已创建的数据库时,可以使用 DROP DATABASE 语句。其语法格式为:

DROP DATABASE [ IF EXISTS ] <数据库名>

语法说明如下:

  • <数据库名>:指定要删除的数据库名。
  • IF EXISTS:用于防止当数据库不存在时发生错误。
  • DROP DATABASE:删除数据库中的所有表格并同时删除数据库。使用此语句时要非常小心,以免错误删除。如果要使用 DROP DATABASE,需要获得数据库 DROP 权限。

注意:MySQL 安装后,系统会自动创建名为 information_schema 和 mysql 的两个系统数据库,系统数据库存放一些和数据库相关的信息,如果删除了这两个数据库,MySQL 将不能正常工作。

4.选择数据库

在 MySQL 中就有很多系统自带的数据库,那么在操作数据库之前就必须要确定是哪一个数据库。 在 MySQL 中,USE 语句用来完成一个数据库到另一个数据库的跳转。 当用 CREATE DATABASE 语句创建数据库之后,该数据库不会自动成为当前数据库,需要用 USE 来指定当前数据库。其语法格式为:

USE <数据库名>

5.mysql注释

5.1MySQL 单行注释

  1. 单行注释可以使用#注释符,#注释符后直接加注释内容。格式如下:
#注释内容
  1. 单行注释可以使用--注释符,--注释符后需要加一个空格,注释才能生效。格式如下:
-- 注释内容

5.2MySQL 多行注释

多行注释使用/* */注释符。/*用于注释内容的开头,*/用于注释内容的结尾。多行注释格式如下:

/*
  第一行注释内容
  第二行注释内容
*/

6.sql语句大小写规则

SQL 语句的大小写规则与语句组成元素、引用内容和服务器所使用的操作系统有关。

下表总结了 SQL 元素在 Windows 和 Linux 系统是否区分大小写。

在这里插入图片描述

在 Linux 服务器下创建数据库和表时,应该认真考虑大小写的问题,比如它们以后是否会迁移到 Windows 服务器上。

假设你在 Linux 服务器上创建了 abc 和 ABC 两个表,当把这两个表迁移到 Windows 服务器上时,就会出现问题;因为 Windows 系统并不区分大小写,abc 和 ABC 无差别。如果把表从 Linux 服务器迁移到 Windows 服务器时,也同样会遇到问题。 想要避免大小写问题,可以先选定一种大小写方案,然后一直按照该方案去创建数据库和表。

一般建议统一使用小写字母,并且 InnoDB 引擎在其内部都是以小写字母方式来存储数据库名和表名的。这样可以有效的防止 MySQL 产生大小写问题。

八、数据表的基本操作

1.创建数据表

在 MySQL中,可以使用 CREATE TABLE 语句创建表。其语法格式为:

CREATE TABLE <表名> ([表定义选项])[表选项][分区选项];

其中,

[表定义选项]

的格式为:

<列名1> <类型1> [,…] <列名n> <类型n>

CREATE TABLE 命令语法比较多,其主要是由表创建定义(create-definition)、表选项(table-options)和分区选项(partition-options)所组成的。

这里首先描述一个简单的新建表的例子,然后重点介绍 CREATE TABLE 命令中的一些主要的语法知识点。

CREATE TABLE 语句的主要语法及使用说明如下:

  • CREATE TABLE:用于创建给定名称的表,必须拥有表CREATE的权限。
  • <表名>:指定要创建表的名称,在 CREATE TABLE 之后给出,必须符合标识符命名规则。表名称被指定为 db_name.tbl_name,以便在特定的数据库中创建表。无论是否有当前数据库,都可以通过这种方式创建。在当前数据库中创建表时,可以省略 db-name。如果使用加引号的识别名,则应对数据库和表名称分别加引号。例如,‘mydb’.‘mytbl’ 是合法的,但 ‘mydb.mytbl’ 不合法。
  • <表定义选项>:表创建定义,由列名(col_name)、列的定义(column_definition)以及可能的空值说明、完整性约束或表索引组成。
  • 默认的情况是,表被创建到当前的数据库中。若表已存在、没有当前数据库或者数据库不存在,则会出现错误。

提示:使用 CREATE TABLE 创建表时,必须指定以下信息:

  • 要创建的表的名称不区分大小写,不能使用SQL语言中的关键字,如DROP、ALTER、INSERT等。
  • 数据表中每个列(字段)的名称和数据类型,如果创建多个列,要用逗号隔开。

1.1在指定的数据库中创建表

数据表属于数据库,在创建数据表之前,应使用语句“USE<数据库>”指定操作在哪个数据库中进行,如果没有选择数据库,就会抛出 No database selected 的错误。

创建员工表 tb_emp1,结构如下表所示。

字段名称数据类型备注
idINT(ll)员工编号
nameVARCHAR(25)员工名称
deptldINT(ll)所在部门编号
salaryFLOAT工资

选择创建表的数据库 test_db,创建 tb_emp1 数据表,输入的 SQL 语句和运行结果如下所示。

mysql> USE test_db;
Database changed
mysql> CREATE TABLE tb_emp1
    -> (
    -> id INT(11),
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT
    -> );
Query OK, 0 rows affected (0.37 sec)

语句执行后,便创建了一个名称为 tb_emp1 的数据表,使用 SHOW TABLES;语句查看数据表是否创建成功,如下所示。

mysql> SHOW TABLES;
+--------------------+
| Tables_in_test_db  |
+--------------------+
| tb_emp1            |
+--------------------+
1 rows in set (0.00 sec)

2.修改数据表

在 MySQL 中可以使用

ALTER TABLE

语句来改变原有表的结构,例如增加或删减列、更改原有列类型、重新命名列或表等。

其语法格式如下:

ALTER TABLE <表名> [修改选项]

修改选项的语法格式如下:

{ ADD COLUMN <列名> <类型>
| CHANGE COLUMN <旧列名> <新列名> <新列类型>
| ALTER COLUMN <列名> { SET DEFAULT <默认值> | DROP DEFAULT }
| MODIFY COLUMN <列名> <类型>
| DROP COLUMN <列名>
| RENAME TO <新表名>
| CHARACTER SET <字符集名>
| COLLATE <校对规则名> }

2.1修改表名

MySQL 通过 ALTER TABLE 语句来实现表名的修改,语法规则如下:

ALTER TABLE <旧表名> RENAME [TO] <新表名>;

其中,TO 为可选参数,使用与否均不影响结果。

使用 ALTER TABLE 将数据表 student 改名为 tb_students_info,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE student RENAME TO tb_students_info;
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW TABLES;
+------------------+
| Tables_in_test   |
+------------------+
| tb_students_info |
+------------------+
1 row in set (0.00 sec)

提示:修改表名并不修改表的结构,因此修改名称后的表和修改名称前的表的结构是相同的。用户可以使用 DESC 命令查看修改后的表结构,

2.2修改表字符集

MySQL 通过 ALTER TABLE 语句来实现表字符集的修改,语法规则如下:

ALTER TABLE 表名 [DEFAULT] CHARACTER SET <字符集名> [DEFAULT] COLLATE <校对规则名>;

其中,DEFAULT 为可选参数,使用与否均不影响结果。

使用 ALTER TABLE 将数据表 tb_students_info 的字符集修改为 gb2312,校对规则修改为 gb2312_chinese_ci。SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_students_info CHARACTER SET gb2312  DEFAULT COLLATE gb2312_chinese_ci;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE tb_students_info \G
*************************** 1. row ***************************
       Table: tb_students_info
Create Table: CREATE TABLE `tb_students_info` (
  `id` int(11) NOT NULL,
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=gb2312
1 row in set (0.00 sec)

3.添加、修改、删除字段

MySQL数据表是由行和列构成的,通常把表的“列”称为字段(Field),把表的“行”称为记录(Record)。随着业务的变化,可能需要在已有的表中添加新的字段。

MySQL 允许在开头、中间和结尾处添加字段。

3.1在末尾添加字段

一个完整的字段包括字段名、数据类型和约束条件。MySQL 添加字段的语法格式如下:

ALTER TABLE <表名> ADD <新字段名><数据类型>[约束条件];

对语法格式的说明如下:

  • <表名> 为数据表的名字;
  • <新字段名> 为所要添加的字段的名字;
  • <数据类型> 为所要添加的字段能存储数据的数据类型;
  • [约束条件] 是可选的,用来对添加的字段进行约束。

这种语法格式默认在表的最后位置(最后一列的后面)添加新字段。

注意:本节我们只添加新的字段,不关注它的约束条件。

在 test 数据库中新建 student 数据表,SQL 语句和运行结果如下:

mysql> USE test;
Database changed
mysql> CREATE TABLE student (
    -> id INT(4),
    -> name VARCHAR(20),
    -> sex CHAR(1));
Query OK, 0 rows affected (0.09 sec)                           

使用 DESC 查看 student 表结构,SQL 语句和运行结果如下:

mysql> DESC student;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(4)      | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| sex   | char(1)     | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)

使用 ALTER TABLE 语句添加一个 INT 类型的字段 age,SQL 语句和运行结果如下:

mysql> ALTER TABLE student ADD age INT(4);
Query OK, 0 rows affected (0.16 sec)
Records: 0  Duplicates: 0  Warnings: 0

使用 DESC 查看 student 表结构,检验 age 字段是否添加成功。SQL 语句和运行结果如下:

mysql> DESC student;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(4)      | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| sex   | char(1)     | YES  |     | NULL    |       |
| age   | int(4)      | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

由运行结果可以看到,student 表已经添加了 age 字段,且该字段在表的最后一个位置,添加字段成功。

3.2在开头添加字段

MySQL 默认在表的最后位置添加新字段,如果希望在开头位置(第一列的前面)添加新字段,那么可以使用 FIRST 关键字,语法格式如下:

ALTER TABLE <表名> ADD <新字段名> <数据类型> [约束条件] FIRST;

FIRST 关键字一般放在语句的末尾。

使用 ALTER TABLE 语句在表的第一列添加 INT 类型的字段 stuId,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE student ADD stuId INT(4) FIRST;
Query OK, 0 rows affected (0.14 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC student;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| stuId | int(4)      | YES  |     | NULL    |       |
| id    | int(4)      | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| sex   | char(1)     | YES  |     | NULL    |       |
| age   | int(4)      | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

由运行结果可以看到,student 表中已经添加了 stuId 字段,且该字段在表中的第一个位置,添加字段成功。

3.3在中间位置添加字段

MySQL 除了允许在表的开头位置和末尾位置添加字段外,还允许在中间位置(指定的字段之后)添加字段,此时需要使用 AFTER 关键字,语法格式如下:

ALTER TABLE <表名> ADD <新字段名> <数据类型> [约束条件] AFTER <已经存在的字段名>;

AFTER 的作用是将新字段添加到某个已有字段后面。

注意,只能在某个已有字段的后面添加新字段,不能在它的前面添加新字段。

使用 ALTER TABLE 语句在 student 表中添加名为 stuno,数据类型为 INT 的字段,stuno 字段位于 name 字段的后面。SQL 语句和运行结果如下:

mysql> ALTER TABLE student ADD stuno INT(11) AFTER name;
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> DESC student;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| stuId | int(4)      | YES  |     | NULL    |       |
| id    | int(4)      | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| stuno | int(11)     | YES  |     | NULL    |       |
| sex   | char(1)     | YES  |     | NULL    |       |
| age   | int(4)      | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

3.4修改字段名称

MySQL 中修改表字段名的语法规则如下:

ALTER TABLE <表名> CHANGE <旧字段名> <新字段名> <新数据类型>;

其中:

  • 旧字段名:指修改前的字段名;
  • 新字段名:指修改后的字段名;
  • 新数据类型:指修改后的数据类型,如果不需要修改字段的数据类型,可以将新数据类型设置成与原来一样,但数据类型不能为空

使用 ALTER TABLE 修改表 tb_emp1 的结构,将 col1 字段名称改为 col3,同时将数据类型变为 CHAR(30),SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp1
    -> CHANGE col1 col3 CHAR(30);
Query OK, 0 rows affected (0.76 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> DESC tb_emp1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| col3   | char(30)    | YES  |     | NULL    |       |
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(30) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float        | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
5 rows in set (0.01 sec)

CHANGE 也可以只修改数据类型,实现和 MODIFY 同样的效果,方法是将 SQL 语句中的“新字段名”和“旧字段名”设置为相同的名称,只改变“数据类型”。

提示:由于不同类型的数据在机器中的存储方式及长度并不相同,修改数据类型可能会影响数据表中已有的数据记录,因此,当数据表中已经有数据时,不要轻易修改数据类型。

3.5修改字段数据类型

修改字段的数据类型就是把字段的数据类型转换成另一种数据类型。在 MySQL 中修改字段数据类型的语法规则如下:

ALTER TABLE <表名> MODIFY <字段名> <数据类型>

其中:

  • 表名:指要修改数据类型的字段所在表的名称;
  • 字段名:指需要修改的字段;
  • 数据类型:指修改后字段的新数据类型。

使用 ALTER TABLE 修改表 tb_emp1 的结构,将 name 字段的数据类型由 VARCHAR(22) 修改成 VARCHAR(30),SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp1
    -> MODIFY name VARCHAR(30);
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> DESC tb_emp1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| col1   | int(11)     | YES  |     | NULL    |       |
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(30) | YES  |     | NULL    |       |
| col2   | int(11)     | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float        | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

语句执行后,发现表 tb_emp1 中 name 字段的数据类型已经修改成 VARCHAR(30),修改成功。

3.6删除字段

删除字段是将数据表中的某个字段从表中移除,语法格式如下:

ALTER TABLE <表名> DROP <字段名>;

其中,“字段名”指需要从表中删除的字段的名称。

使用 ALTER TABLE 修改表 tb_emp1 的结构,删除 col2 字段,SQL 语句和运行结果如下所示。

mysql> ALTER TABLE tb_emp1
    -> DROP col2;
Query OK, 0 rows affected (0.53 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC tb_emp1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| col1   | int(11)     | YES  |     | NULL    |       |
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(30) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float        | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

4.删除数据表

在 MySQL数据库中,对于不再需要的数据表,我们可以将其从数据库中删除。

在删除表的同时,表的结构和表中所有的数据都会被删除,因此在删除数据表之前最好先备份,以免造成无法挽回的损失。

下面我们来了解一下 MySQL 数据库中数据表的删除方法。

使用 DROP TABLE语句可以删除一个或多个数据表,语法格式如下:

DROP TABLE [IF EXISTS] 表名1 [ ,表名2, 表名3 …]

对语法格式的说明如下:

  • 表名1, 表名2, 表名3 ...表示要被删除的数据表的名称。DROP TABLE 可以同时删除多个表,只要将表名依次写在后面,相互之间用逗号隔开即可。
  • IF EXISTS 用于在删除数据表之前判断该表是否存在。如果不加 IF EXISTS,当数据表不存在时 MySQL 将提示错误,中断 SQL 语句的执行;加上 IF EXISTS 后,当数据表不存在时 SQL 语句可以顺利执行,但是会发出警告(warning)。

两点注意:

  • 用户必须拥有执行 DROP TABLE 命令的权限,否则数据表不会被删除。
  • 表被删除时,用户在该表上的权限不会自动删除。

选择数据库 test_db,创建 tb_emp3 数据表,输入的 SQL 语句和运行结果如下所示。

mysql> USE test_db;
Database changed
mysql> CREATE TABLE tb_emp3
    -> (
    -> id INT(11),
    -> name VARCHAR(25),
    -> deptId INT(11),
    -> salary FLOAT
    -> );
Query OK, 0 rows affected (0.27 sec)
mysql> SHOW TABLES;
+--------------------+
| Tables_in_test_db  |
+--------------------+
| tb_emp2            |
| tb_emp3            |
+--------------------+
2 rows in set (0.00 sec)

由运行结果可以看出,test_tb 数据库中有 tb_emp2 和 tb_emp3 两张数据表。

我们来删除数据表 tb_emp3,输入的 SQL 语句和运行结果如下所示:

mysql> DROP TABLE tb_emp3;
Query OK, 0 rows affected (0.22 sec)
mysql> SHOW TABLES;
+--------------------+
| Tables_in_test_db  |
+--------------------+
| tb_emp2            |
+--------------------+
1 rows in set (0.00 sec)

5.删除关联的主表

数据表之间经常存在外键关联的情况,这时如果直接删除父表,会破坏数据表的完整性,也会删除失败。

删除父表有以下两种方法:

  • 先删除与它关联的子表,再删除父表;但是这样会同时删除两个表中的数据。
  • 将关联表的外键约束取消,再删除父表;适用于需要保留子表的数据,只删除父表的情况。

下面介绍了如何取消关联表的外键约束并删除主表,也就是上面所说的删除父表的第二种方法。

在数据库中创建两个关联表。创建表 tb_emp4 的 SQL 语句如下:

CREATE TABLE tb_emp4
(
id INT(11) PRIMARY KEY,
name VARCHAR(22),
location VARCHAR (50)
);

接下来创建表 tb_emp5,SQL 语句如下:

CREATE TABLE tb_emp5
(
id INT(11) PRIMARY KEY,
name VARCHAR(25),
deptId INT(11),
salary FLOAT,
CONSTRAINT fk_emp4_emp5 FOREIGN KEY (deptId) REFERENCES tb_emp4(id)
);

使用 SHOW CREATE TABLE 命令查看表 tb_ emp5 的外键约束,SQL 语句和运行结果如下:

mysql> SHOW CREATE TABLE tb_emp5\G;
*************************** 1. row ***************************
       Table: tb_emp5
Create Table: CREATE TABLE `tb_emp5` (
  `id` int(11) NOT NULL,
  `name` varchar(25) DEFAULT NULL,
  `deptId` int(11) DEFAULT NULL,
  `salary` float DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_emp4_emp5` (`deptId`),
  CONSTRAINT `fk_emp4_emp5` FOREIGN KEY (`deptId`) REFERENCES `tb_emp4` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

由运行结果可以看出,tb_emp5 表为子表,具有名称为 fk_emp4_emp5 的外键约束;tb_emp4 为父表,其主键 id 被子表 tb_ emp5 所关联。

删除被数据表 tb_emp5 关联的数据表 tb_emp4,SQL 语句如下:

mysql> DROP TABLE tb_emp4;
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails

由运行结果可以看出,当主表在存在外键约束时,不能被直接删除。

下面解除子表 tb_emp5 的外键约束,SQL语句和运行结果如下:

mysql> ALTER TABLE tb_emp5 DROP FOREIGN KEY fk_emp4_emp5;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

语句成功执行后,会取消表 tb_emp4 和表 tb_emp5 之间的关联关系。

解除关联关系后,可以使用 DROP TABLE 语句直接删除父表 tb_emp4,SQL 语句如下:

DROP TABLE tb_emp4;

最后通过 SHOW TABLES 命令查看数据表列表,如下所示:

mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| tb_emp5        |
| temp           |
+----------------+
2 rows in set (0.00 sec)

可以发现,数据库列表中已经不存在名称为 tb_emp4 的表,删除成功。

6.查看表结构

创建完数据表之后,经常需要查看表结构(表信息)。在 MySQL中,可以使用 DESCRIBE 和 SHOW CREATE TABLE 命令来查看数据表的结构。

6.1DESCRIBE:以表格的形式展示表结构

DESCRIBE/DESC 语句会以表格的形式来展示表的字段信息,包括字段名、字段数据类型、是否为主键、是否有默认值等,语法格式如下:

DESCRIBE <表名>;

或简写成:

DESC <表名>;

【实例1】分别使用 DESCRIBE 和 DESC 查看表 tb_emp1 的表结构,SQL 语句和运行结果如下:

mysql> DESCRIBE tb_emp1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.14 sec)

mysql> DESC tb_emp1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.14 sec)

其中,各个字段的含义如下:

  • Null:表示该列是否可以存储 NULL 值。
  • Key:表示该列是否已编制索引。PRI 表示该列是表主键的一部分,UNI 表示该列是 UNIQUE 索引的一部分,MUL 表示在列中某个给定值允许出现多次。
  • Default:表示该列是否有默认值,如果有,值是多少。
  • Extra:表示可以获取的与给定列有关的附加信息,如 AUTO_INCREMENT 等。

6.2SHOW CREATE TABLE:以SQL语句的形式展示表结构

SHOW CREATE TABLE 命令会以 SQL 语句的形式来展示表信息。和 DESCRIBE 相比,SHOW CREATE TABLE 展示的内容更加丰富,它可以查看表的存储引擎和字符编码;另外,你还可以通过

\g

或者

\G

参数来控制展示格式。

SHOW CREATE TABLE 的语法格式如下:

SHOW CREATE TABLE <表名>;

在 SHOW CREATE TABLE 语句的结尾处(分号前面)添加

\g

或者

\G

参数可以改变展示形式。

【实例2】使用 SHOW CREATE TABLE 语句查看表 tb_emp1 的详细信息,一次使用

\g

结尾,一次不使用:

mysql> SHOW CREATE TABLE tb_emp1;
+---------+------------------------------------------------+
| Table   | Create Table                                   |
+---------+------------------------------------------------+
| tb_emp1 | CREATE TABLE `tb_emp1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(25) DEFAULT NULL,
  `salary` float DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gb2312 |
+---------+------------------------------------------------+
1 row in set (0.01 sec)

mysql> SHOW CREATE TABLE tb_emp1 \g;
+---------+------------------------------------------------+
| Table   | Create Table                                   |
+---------+------------------------------------------------+
| tb_emp1 | CREATE TABLE `tb_emp1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(25) DEFAULT NULL,
  `salary` float DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gb2312 |
+---------+------------------------------------------------+
1 row in set (0.00 sec)

SHOW CREATE TABLE 使用

\G

结尾的 SQL 语句和运行结果如下:

mysql> SHOW CREATE TABLE tb_emp1\G
*************************** 1. row ***************************
       Table: tb_emp1
Create Table: CREATE TABLE `tb_emp1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(25) DEFAULT NULL,
  `deptId` int(11) DEFAULT NULL,
  `salary` float DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.03 sec)

九、操作表中数据

1.查询数据

在 MySQL 中,可以使用 SELECT 语句来查询数据。查询数据是指从数据库中根据需求,使用不同的查询方式来获取不同的数据,是使用频率最高、最重要的操作。

SELECT 的语法格式如下:

SELECT{* | <字段列名>}[FROM <表 1>, <表 2>…[WHERE <表达式>[GROUP BY <group by definition>[HAVING <expression> [{<operator> <expression>}…]][ORDER BY <order by definition>][LIMIT[<offset>,] <row count>]]

其中,各条子句的含义如下:

  • {*|<字段列名>}包含星号通配符的字段列表,表示所要查询字段的名称。
  • <表 1>,<表 2>…,表 1 和表 2 表示查询数据的来源,可以是单个或多个。
  • WHERE <表达式>是可选项,如果选择该项,将限定查询数据必须满足该查询条件。
  • GROUP BY< 字段 >,该子句告诉 MySQL 如何显示查询出来的数据,并按照指定的字段分组。
  • [ORDER BY< 字段 >],该子句告诉 MySQL 按什么样的顺序显示查询出来的数据,可以进行的排序有升序(ASC)和降序(DESC),默认情况下是升序。
  • [LIMIT[<offset>,]<row count>],该子句告诉 MySQL 每次显示查询出来的数据条数。

下面先介绍一些简单的 SELECT 语句,关于 WHERE、GROUP BY、ORDER BY 和 LIMIT 等限制条件,后面我们会一一讲解。

1.1查询表中所有字段

查询所有字段是指查询表中所有字段的数据。MySQL 提供了以下 2 种方式查询表中的所有字段。

  • 使用“*”通配符查询所有字段
  • 列出表的所有字段

1.2使用“*”查询表的所有字段

SELECT 可以使用“*”查找表中所有字段的数据,语法格式如下:

SELECT * FROM 表名;

使用“*”查询时,只能按照数据表中字段的顺序进行排列,不能改变字段的排列顺序。

从 tb_students_info 表中查询所有字段的数据,SQL 语句和运行结果如下所示。

mysql> use test_db;
Database changed
mysql> SELECT * FROM tb_students_info;
+----+--------+---------+------+------+--------+------------+
| id | name   | dept_id | age  | sex  | height | login_date |
+----+--------+---------+------+------+--------+------------+
|  1 | Dany   |       1 |   25 | F    |    160 | 2015-09-10 |
|  2 | Green  |       3 |   23 | F    |    158 | 2016-10-22 |
|  3 | Henry  |       2 |   23 | M    |    185 | 2015-05-31 |
|  4 | Jane   |       1 |   22 | F    |    162 | 2016-12-20 |
|  5 | Jim    |       1 |   24 | M    |    175 | 2016-01-15 |
|  6 | John   |       2 |   21 | M    |    172 | 2015-11-11 |
|  7 | Lily   |       6 |   22 | F    |    165 | 2016-02-26 |
|  8 | Susan  |       4 |   23 | F    |    170 | 2015-10-01 |
|  9 | Thomas |       3 |   22 | M    |    178 | 2016-06-07 |
| 10 | Tom    |       4 |   23 | M    |    165 | 2016-08-05 |
+----+--------+---------+------+------+--------+------------+
10 rows in set (0.26 sec)

结果显示,使用“*”通配符时,将返回所有列,数据列按照创建表时的顺序显示。

注意:一般情况下,除非需要使用表中所有的字段数据,否则最好不要使用通配符“”。虽然使用通配符可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。使用“”的优势是,当不知道所需列的名称时,可以通过“*”获取它们。

1.3列出表的所有字段

SELECT 关键字后面的字段名为需要查找的字段,因此可以将表中所有字段的名称跟在 SELECT 关键字后面。如果忘记了字段名称,可以使用 DESC 命令查看表的结构。

有时,由于表的字段比较多,不一定能记得所有字段的名称,因此该方法很不方便,不建议使用。

查询 tb_students_info 表中的所有数据,SQL 语句还可以书写如下:

SELECT id,name,dept_id,age,sex,height,login_date FROM tb_students_info;

运行结果和例 1 相同。

这种查询方式比较灵活,如果需要改变字段显示的顺序,只需调整 SELECT 关键字后面的字段列表顺序即可。

虽然列出表的所有字段的方式比较灵活,但是查询所有字段时通常使用“”通配符。使用“”这种方式比较简单,尤其是表中的字段很多的时候,这种方式的优势更加明显。当然,如果需要改变字段显示的顺序,可以选择列出表的所有字段。

1.4查询表中指定的字段

查询表中的某一个字段的语法格式为:

SELECT < 列名 > FROM < 表名 >;

查询 tb_students_info 表中 name 列所有学生的姓名,SQL 语句和运行结果如下所示。

mysql> SELECT name FROM tb_students_info;
+--------+
| name   |
+--------+
| Dany   |
| Green  |
| Henry  |
| Jane   |
| Jim    |
| John   |
| Lily   |
| Susan  |
| Thomas |
| Tom    |
+--------+
10 rows in set (0.00 sec)

输出结果显示了 tb_students_info 表中 name 字段下的所有数据。

使用 SELECT 声明可以获取多个字段下的数据,只需要在关键字 SELECT 后面指定要查找的字段名称,不同字段名称之间用逗号“,”分隔开,最后一个字段后面不需要加逗号,语法格式如下:

SELECT <字段名1>,<字段名2>,…,<字段名n> FROM <表名>;

从 tb_students_info 表中获取 id、name 和 height 三列,SQL 语句和运行结果如下所示。

mysql> SELECT id,name,height
    -> FROM tb_students_info;
+----+--------+--------+
| id | name   | height |
+----+--------+--------+
|  1 | Dany   |    160 |
|  2 | Green  |    158 |
|  3 | Henry  |    185 |
|  4 | Jane   |    162 |
|  5 | Jim    |    175 |
|  6 | John   |    172 |
|  7 | Lily   |    165 |
|  8 | Susan  |    170 |
|  9 | Thomas |    178 |
| 10 | Tom    |    165 |
+----+--------+--------+
10 rows in set (0.00 sec)

输出结果显示了 tb_students_info 表中 id、name 和 height 三个字段下的所有数据。

2.去重

在 MySQL 中使用 SELECT 语句执行简单的数据查询时,返回的是所有匹配的记录。如果表中的某些字段没有唯一性约束,那么这些字段就可能存在重复值。为了实现查询不重复的数据,MySQL 提供了

DISTINCT

关键字。

DISTINCT 关键字的主要作用就是对数据表中一个或多个字段重复的数据进行过滤,只返回其中的一条数据给用户。

DISTINCT 关键字的语法格式为:

SELECT DISTINCT <字段名> FROM <表名>;

其中,“字段名”为需要消除重复记录的字段名称,多个字段时用逗号隔开。

使用 DISTINCT 关键字时需要注意以下几点:

  • DISTINCT 关键字只能在 SELECT 语句中使用。
  • 在对一个或多个字段去重时,DISTINCT 关键字必须在所有字段的最前面。
  • 如果 DISTINCT 关键字后有多个字段,则会对多个字段进行组合去重,也就是说,只有多个字段组合起来完全是一样的情况下才会被去重。

下面通过一个具体的实例来说明如何实现查询不重复数据。

test 数据库中 student 表的表结构和数据如下所示:

mysql> SELECT * FROM test.student;
+----+----------+------+-------+
| id | name     | age  | stuno |
+----+----------+------+-------+
|  1 | zhangsan |   18 |    23 |
|  2 | lisi     |   19 |    24 |
|  3 | wangwu   |   18 |    25 |
|  4 | zhaoliu  |   18 |    26 |
|  5 | zhangsan |   18 |    27 |
|  6 | wangwu   |   20 |    28 |
+----+----------+------+-------+
6 rows in set (0.00 sec)

结果显示,student 表中存在 6 条记录。

下面对 student 表的 age 字段进行去重,SQL 语句和运行结果如下:

mysql> SELECT DISTINCT age FROM student;
+------+
| age  |
+------+
|   18 |
|   19 |
|   20 |
+------+
3 rows in set (0.00 sec)

对 student 表的 name 和 age 字段进行去重,SQL 语句和运行结果如下:

mysql> SELECT DISTINCT name,age FROM student;
+----------+------+
| name     | age  |
+----------+------+
| zhangsan |   18 |
| lisi     |   19 |
| wangwu   |   18 |
| zhaoliu  |   18 |
| wangwu   |   20 |
+----------+------+
5 rows in set (0.00 sec)

对 student 表中的所有字段进行去重,SQL 语句和运行结果如下:

mysql> SELECT DISTINCT * FROM student;
+----+----------+------+-------+
| id | name     | age  | stuno |
+----+----------+------+-------+
|  1 | zhangsan |   18 |    23 |
|  2 | lisi     |   19 |    24 |
|  3 | wangwu   |   18 |    25 |
|  4 | zhaoliu  |   18 |    26 |
|  5 | zhangsan |   18 |    27 |
|  6 | wangwu   |   20 |    28 |
+----+----------+------+-------+
6 rows in set (0.00 sec)

因为 DISTINCT 只能返回它的目标字段,而无法返回其它字段,所以在实际情况中,我们经常使用 DISTINCT 关键字来返回不重复字段的条数。

查询 student 表中对 name 和 age 字段去重之后记录的条数,SQL 语句和运行结果如下:

mysql> SELECT COUNT(DISTINCT name,age) FROM student;
+--------------------------+
| COUNT(DISTINCT name,age) |
+--------------------------+
|                        5 |
+--------------------------+
1 row in set (0.01 sec)

结果显示,student 表中对 name 和 age 字段去重之后有 5 条记录。

3.设置别名

为了查询方便,MySQL 提供了

AS

关键字来为表和字段指定别名。本节主要讲解如何为表和字段指定一个别名。

3.1为表指定别名

当表名很长或者执行一些特殊查询的时候,为了方便操作,可以为表指定一个别名,用这个别名代替表原来的名称。

为表指定别名的基本语法格式为:

<表名> [AS] <别名>

其中各子句的含义如下:

  • <表名>:数据库中存储的数据表的名称。
  • <别名>:查询时指定的表的新名称。
  • AS关键字可以省略,省略后需要将表名和别名用空格隔开。

注意:

表的别名不能与该数据库的其它表同名。字段的别名不能与该表的其它字段同名

。在条件表达式中不能使用字段的别名,否则会出现“ERROR 1054 (42S22): Unknown column”这样的错误提示信息。

下面为 tb_students_info 表指定别名 stu,SQL 语句和运行结果如下。

mysql> SELECT stu.name,stu.height FROM tb_students_info AS stu;
+--------+--------+
| name   | height |
+--------+--------+
| Dany   |    160 |
| Green  |    158 |
| Henry  |    185 |
| Jane   |    162 |
| Jim    |    175 |
| John   |    172 |
| Lily   |    165 |
| Susan  |    170 |
| Thomas |    178 |
| Tom    |    165 |
+--------+--------+
10 rows in set (0.04 sec)

3.2为字段指定别名

在使用 SELECT 语句查询数据时,MySQL 会显示每个 SELECT 后面指定输出的字段。有时为了显示结果更加直观,我们可以为字段指定一个别名。

为字段指定别名的基本语法格式为:

<字段名> [AS] <别名>

其中,各子句的语法含义如下:

  • <字段名>:为数据表中字段定义的名称。
  • <字段别名>:字段新的名称。
  • AS关键字可以省略,省略后需要将字段名和别名用空格隔开。

查询 tb_students_info 表,为 name 指定别名 student_name,为 age 指定别名 student_age,SQL 语句和运行结果如下。

mysql> SELECT name AS student_name, age AS student_age FROM tb_students_info;
+--------------+-------------+
| student_name | student_age |
+--------------+-------------+
| Dany         |          25 |
| Green        |          23 |
| Henry        |          23 |
| Jane         |          22 |
| Jim          |          24 |
| John         |          21 |
| Lily         |          22 |
| Susan        |          23 |
| Thomas       |          22 |
| Tom          |          23 |
+--------------+-------------+
10 rows in set (0.00 sec)

注意:表别名只在执行查询时使用,并不在返回结果中显示。而字段定义别名之后,会返回给客户端显示,显示的字段为字段的别名。

4.限制查询结果条数

当数据表中有上万条数据时,一次性查询出表中的全部数据会降低数据返回的速度,同时给数据库服务器造成很大的压力。这时就可以用

LIMIT

关键字来限制查询结果返回的条数。

LIMIT 是 MySQL 中的一个特殊关键字,用于指定查询结果从哪条记录开始显示,一共显示多少条记录。

LIMIT 关键字有 3 种使用方式,即指定初始位置、不指定初始位置以及与 OFFSET 组合使用。

4.1指定初始位置

LIMIT 关键字可以指定查询结果从哪条记录开始显示,显示多少条记录。

LIMIT 指定初始位置的基本语法格式如下:

LIMIT 初始位置,记录数

其中,“初始位置”表示从哪条记录开始显示;“记录数”表示显示记录的条数。第一条记录的位置是 0,第二条记录的位置是 1。后面的记录依次类推。

注意:

LIMIT 后的两个参数必须都是正整数。

在 tb_students_info 表中,使用 LIMIT 子句返回从第 4 条记录开始的行数为 5 的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info LIMIT 3,5;
+----+-------+---------+------+------+--------+------------+
| id | name  | dept_id | age  | sex  | height | login_date |
+----+-------+---------+------+------+--------+------------+
|  4 | Jane  |       1 |   22 | F    |    162 | 2016-12-20 |
|  5 | Jim   |       1 |   24 | M    |    175 | 2016-01-15 |
|  6 | John  |       2 |   21 | M    |    172 | 2015-11-11 |
|  7 | Lily  |       6 |   22 | F    |    165 | 2016-02-26 |
|  8 | Susan |       4 |   23 | F    |    170 | 2015-10-01 |
+----+-------+---------+------+------+--------+------------+
5 rows in set (0.00 sec)

由结果可以看到,该语句返回的是从第 4 条记录开始的之后的 5 条记录。LIMIT 关键字后的第一个数字“3”表示从第 4 行开始(记录的位置从 0 开始,第 4 行的位置为 3),第二个数字 5 表示返回的行数。

4.2不指定初始位置

LIMIT 关键字不指定初始位置时,记录从第一条记录开始显示。显示记录的条数由 LIMIT 关键字指定。

LIMIT 不指定初始位置的基本语法格式如下:

LIMIT 记录数

其中,“记录数”表示显示记录的条数。如果“记录数”的值小于查询结果的总数,则会从第一条记录开始,显示指定条数的记录。如果“记录数”的值大于查询结果的总数,则会直接显示查询出来的所有记录。

显示 tb_students_info 表查询结果的前 4 行,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info LIMIT 4;
+----+-------+---------+------+------+--------+------------+
| id | name  | dept_id | age  | sex  | height | login_date |
+----+-------+---------+------+------+--------+------------+
|  1 | Dany  |       1 |   25 | F    |    160 | 2015-09-10 |
|  2 | Green |       3 |   23 | F    |    158 | 2016-10-22 |
|  3 | Henry |       2 |   23 | M    |    185 | 2015-05-31 |
|  4 | Jane  |       1 |   22 | F    |    162 | 2016-12-20 |
+----+-------+---------+------+------+--------+------------+
4 rows in set (0.00 sec)

结果中只显示了 4 条记录,说明“LIMIT 4”限制了显示条数为 4。

显示 tb_students_info 表查询结果的前 15 行,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info LIMIT 15;
+----+--------+---------+------+------+--------+------------+
| id | name   | dept_id | age  | sex  | height | login_date |
+----+--------+---------+------+------+--------+------------+
|  1 | Dany   |       1 |   25 | F    |    160 | 2015-09-10 |
|  2 | Green  |       3 |   23 | F    |    158 | 2016-10-22 |
|  3 | Henry  |       2 |   23 | M    |    185 | 2015-05-31 |
|  4 | Jane   |       1 |   22 | F    |    162 | 2016-12-20 |
|  5 | Jim    |       1 |   24 | M    |    175 | 2016-01-15 |
|  6 | John   |       2 |   21 | M    |    172 | 2015-11-11 |
|  7 | Lily   |       6 |   22 | F    |    165 | 2016-02-26 |
|  8 | Susan  |       4 |   23 | F    |    170 | 2015-10-01 |
|  9 | Thomas |       3 |   22 | M    |    178 | 2016-06-07 |
| 10 | Tom    |       4 |   23 | M    |    165 | 2016-08-05 |
+----+--------+---------+------+------+--------+------------+
10 rows in set (0.26 sec)

结果中只显示了 10 条记录。虽然 LIMIT 关键字指定了显示 15 条记录,但是查询结果中只有 10 条记录。因此,数据库系统就将这 10 条记录全部显示出来。

带一个参数的 LIMIT 指定从查询结果的首行开始,唯一的参数表示返回的行数,即“

LIMIT n”与“LIMIT 0,n”返回结果相同。带两个参数的 LIMIT 可返回从任何位置开始指定行数的数据。

LIMIT和OFFSET组合使用

LIMIT 可以和 OFFSET 组合使用,语法格式如下:

LIMIT 记录数 OFFSET 初始位置

参数和 LIMIT 语法中参数含义相同,“初始位置”指定从哪条记录开始显示;“记录数”表示显示记录的条数。

在 tb_students_info 表中,使用 LIMIT OFFSET 返回从第 4 条记录开始的行数为 5 的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info LIMIT 5 OFFSET 3;
+----+-------+---------+------+------+--------+------------+
| id | name  | dept_id | age  | sex  | height | login_date |
+----+-------+---------+------+------+--------+------------+
|  4 | Jane  |       1 |   22 | F    |    162 | 2016-12-20 |
|  5 | Jim   |       1 |   24 | M    |    175 | 2016-01-15 |
|  6 | John  |       2 |   21 | M    |    172 | 2015-11-11 |
|  7 | Lily  |       6 |   22 | F    |    165 | 2016-02-26 |
|  8 | Susan |       4 |   23 | F    |    170 | 2015-10-01 |
+----+-------+---------+------+------+--------+------------+
5 rows in set (0.00 sec)

由结果可以看到,该语句返回的是从第 4 条记录开始的之后的 5 条记录。即“LIMIT 5 OFFSET 3”意思是获取从第 4 条记录开始的后面的 5 条记录,和“LIMIT 3,5”返回的结果相同。

5.结果排序

通过条件查询语句可以查询到符合用户需求的数据,但是查询到的数据一般都是按照数据最初被添加到表中的顺序来显示。为了使查询结果的顺序满足用户的要求,MySQL 提供了

ORDER BY

关键字来对查询结果进行排序。

在实际应用中经常需要对查询结果进行排序,比如,在网上购物时,可以将商品按照价格进行排序;在医院的挂号系统中,可以按照挂号的先后顺序进行排序等。

ORDER BY 关键字主要用来将查询结果中的数据按照一定的顺序进行排序。其语法格式如下:

ORDER BY <字段名> [ASC|DESC]

语法说明如下。

  • 字段名:表示需要排序的字段名称,多个字段时用逗号隔开。
  • ASC|DESC:ASC表示字段按升序排序;DESC表示字段按降序排序。其中ASC为默认值。

使用 ORDER BY 关键字应该注意以下几个方面:

  • ORDER BY 关键字后可以跟子查询(关于子查询后面教程会详细讲解,这里了解即可)。
  • 当排序的字段中存在空值时,ORDER BY 会将该空值作为最小值来对待。
  • ORDER BY 指定多个字段进行排序时,MySQL 会按照字段的顺序从左到右依次进行排序。

5.1单字段排序

下面通过一个具体的实例来说明当 ORDER BY 指定单个字段时,MySQL 如何对查询结果进行排序。

下面查询 tb_students_info 表的所有记录,并对 height 字段进行排序,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info ORDER BY height;
+----+--------+---------+------+------+--------+------------+
| id | name   | dept_id | age  | sex  | height | login_date |
+----+--------+---------+------+------+--------+------------+
|  2 | Green  |       3 |   23 | F    |    158 | 2016-10-22 |
|  1 | Dany   |       1 |   25 | F    |    160 | 2015-09-10 |
|  4 | Jane   |       1 |   22 | F    |    162 | 2016-12-20 |
|  7 | Lily   |       6 |   22 | F    |    165 | 2016-02-26 |
| 10 | Tom    |       4 |   23 | M    |    165 | 2016-08-05 |
|  8 | Susan  |       4 |   23 | F    |    170 | 2015-10-01 |
|  6 | John   |       2 |   21 | M    |    172 | 2015-11-11 |
|  5 | Jim    |       1 |   24 | M    |    175 | 2016-01-15 |
|  9 | Thomas |       3 |   22 | M    |    178 | 2016-06-07 |
|  3 | Henry  |       2 |   23 | M    |    185 | 2015-05-31 |
+----+--------+---------+------+------+--------+------------+
10 rows in set (0.08 sec)

由结果可以看到,MySQL 对查询的 height 字段的数据按数值的大小进行了升序排序。

5.2多字段排序

下面通过一个具体的实例来说明当 ORDER BY 指定多个字段时,MySQL 如何对查询结果进行排序。

查询 tb_students_info 表中的 name 和 height 字段,先按 height 排序,再按 name 排序,SQL 语句和运行结果如下。

mysql> SELECT name,height FROM tb_students_info ORDER BY height,name;
+--------+--------+
| name   | height |
+--------+--------+
| Green  |    158 |
| Dany   |    160 |
| Jane   |    162 |
| Lily   |    165 |
| Tom    |    165 |
| Susan  |    170 |
| John   |    172 |
| Jim    |    175 |
| Thomas |    178 |
| Henry  |    185 |
+--------+--------+
10 rows in set (0.09 sec)

注意:在

对多个字段进行排序时,排序的第一个字段必须有相同的值,才会对第二个字段进行排序。如果第一个字段数据中所有的值都是唯一的,MySQL 将不再对第二个字段进行排序

默认情况下,查询数据按字母升序进行排序(A~Z),但数据的排序并不仅限于此,还可以使用 ORDER BY 中的 DESC 对查询结果进行降序排序(Z~A)。

查询 tb_students_info 表,先按 height 降序排序,再按 name 升序排序,SQL 语句和运行结果如下。

mysql> SELECT name,height FROM tb_student_info ORDER BY height DESC,name ASC;
+--------+--------+
| name   | height |
+--------+--------+
| Henry  |    185 |
| Thomas |    178 |
| Jim    |    175 |
| John   |    172 |
| Susan  |    170 |
| Lily   |    165 |
| Tom    |    165 |
| Jane   |    162 |
| Dany   |    160 |
| Green  |    158 |
+--------+--------+
10 rows in set (0.00 sec)

DESC 关键字只对前面的列进行降序排列,在这里只对 height 字段进行降序。因此,height 按降序排序,而 name 仍按升序排序。如果想在多个列上进行降序排序,必须对每个列指定 DESC 关键字。

6.条件查询

在 MySQL 中,如果需要有条件的从数据表中查询数据,可以使用

WHERE

关键字来指定查询条件。

使用 WHERE 关键字的语法格式如下:

WHERE 查询条件

查询条件可以是:

  • 带比较运算符和逻辑运算符的查询条件
  • 带 BETWEEN AND 关键字的查询条件
  • 带 IS NULL 关键字的查询条件
  • 带 IN 关键字的查询条件
  • 带 LIKE 关键字的查询条件

6.1单一条件的查询语句

单一条件指的是在 WHERE 关键字后只有一个查询条件。

在 tb_students_info 数据表中查询身高为 170cm 的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name,height FROM tb_students_info
    -> WHERE height=170;
+-------+--------+
| name  | height |
+-------+--------+
| Susan |    170 |
+-------+--------+
1 row in set (0.17 sec)

可以看到,查询结果中记录的 height 字段的值等于 170。如果根据指定的条件进行查询时,数据表中没有符合查询条件的记录,系统会提示“Empty set(0.00sec)”。

在 tb_students_info 数据表中查询年龄小于 22 的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name,age FROM tb_students_info
    -> WHERE age<22;
+------+------+
| name | age  |
+------+------+
| John |   21 |
+------+------+
1 row in set (0.05 sec)

可以看到,查询结果中所有记录的 age 字段的值均小于 22 岁,而大于或等于 22 岁的记录没有被返回。

6.2多条件的查询语句

在 WHERE 关键词后可以有多个查询条件,这样能够使查询结果更加精确。多个查询条件时用逻辑运算符 AND(&&)、OR(||)或 XOR 隔开。

  • AND:记录满足所有查询条件时,才会被查询出来。
  • OR:记录满足任意一个查询条件时,才会被查询出来。
  • XOR:记录满足其中一个条件,并且不满足另一个条件时,才会被查询出来。

在 tb_students_info 表中查询 age 大于 21,并且 height 大于等于 175 的学生信息,SQL 语句和运行结果如下。

mysql> SELECT name,age,height FROM tb_students_info 
    -> WHERE age>21 AND height>=175;
+--------+------+--------+
| name   | age  | height |
+--------+------+--------+
| Henry  |   23 |    185 |
| Jim    |   24 |    175 |
| Thomas |   22 |    178 |
+--------+------+--------+
3 rows in set (0.00 sec)

可以看到,查询结果中所有记录的 age 字段都大于 21 且 height 字段都大于等于 175。

在 tb_students_info 表中查询 age 大于 21,或者 height 大于等于 175 的学生信息,SQL 语句和运行结果如下。

mysql> SELECT name,age,height FROM tb_students_info 
    -> WHERE age>21 OR height>=175;
+--------+------+--------+
| name   | age  | height |
+--------+------+--------+
| Dany   |   25 |    160 |
| Green  |   23 |    158 |
| Henry  |   23 |    185 |
| Jane   |   22 |    162 |
| Jim    |   24 |    175 |
| Lily   |   22 |    165 |
| Susan  |   23 |    170 |
| Thomas |   22 |    178 |
| Tom    |   23 |    165 |
+--------+------+--------+
9 rows in set (0.00 sec)

可以看到,查询结果中所有记录的 age 字段都大于 21 或者 height 字段都大于等于 175。

在 tb_students_info 表中查询 age 大于 21,并且 height 小于 175 的学生信息和 age 小于 21,并且 height 大于等于 175 的学生信息,SQL 语句和运行结果如下。

mysql> SELECT name,age,height FROM tb_students_info 
    -> WHERE age>21 XOR height>=175;
+-------+------+--------+
| name  | age  | height |
+-------+------+--------+
| Dany  |   25 |    160 |
| Green |   23 |    158 |
| Jane  |   22 |    162 |
| Lily  |   22 |    165 |
| Susan |   23 |    170 |
| Tom   |   23 |    165 |
+-------+------+--------+
7 rows in set (0.00 sec)

可以看到,查询结果中所有记录的 age 字段都大于 21 且 height 字段都小于 175。tb_students_info 数据表中没有 age 字段小于 21 且 height 字段大于等于 175 的记录。

OR、AND 和 XOR 可以一起使用,但是在使用时要注意运算符的优先级。

查询条件越多,查询出来的记录就会越少。因为,设置的条件越多,查询语句的限制就更多,能够满足所有条件的记录就更少。为了使查询出来的记录正是自己想要的,可以在 WHERE 语句中将查询条件设置的更加具体。

7.LIke模糊查询

在 MySQL 中,

LIKE

关键字主要用于搜索匹配字段中的指定内容。其语法格式如下:

[NOT] LIKE ‘字符串’

其中:

  • NOT :可选参数,字段中的内容与指定的字符串不匹配时满足条件。
  • 字符串:指定用来匹配的字符串。“字符串”可以是一个很完整的字符串,也可以包含通配符。

LIKE 关键字支持百分号“%”和下划线“_”通配符。

通配符是一种特殊语句,主要用来模糊查询。当不知道真正字符或者懒得输入完整名称时,可以使用通配符来代替一个或多个真正的字符。

7.1带有“%”通配符的查询

“%”

是 MySQL 中最常用的通配符,它能

代表任何长度的字符串,字符串的长度可以为 0。例如,

a%b

表示以字母 a 开头,以字母 b 结尾的任意长度的字符串。该字符串可以代表 ab、acb、accb、accrb 等字符串。

在 tb_students_info 表中,查找所有以字母“T”开头的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE name LIKE 'T%';
+--------+
| name   |
+--------+
| Thomas |
| Tom    |
+--------+
2 rows in set (0.12 sec)

可以看到,查询结果中只返回了以字母“T”开头的学生姓名。

注意:

匹配的字符串必须加单引号或双引号。

NOT LIKE

表示字符串不匹配时满足条件。

在 tb_students_info 表中,查找所有不以字母“T”开头的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT NAME FROM tb_students_info
    -> WHERE NAME NOT LIKE 'T%';
+-------+
| NAME  |
+-------+
| Dany  |
| Green |
| Henry |
| Jane  |
| Jim   |
| John  |
| Lily  |
| Susan |
+-------+
8 rows in set (0.00 sec)

可以看到,查询结果中返回了不以字母“T”开头的学生姓名。

在 tb_students_info 表中,查找所有包含字母“e”的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE name LIKE '%e%';
+-------+
| name  |
+-------+
| Green |
| Henry |
| Jane  |
+-------+
3 rows in set (0.00 sec)

可以看到,查询结果中返回了所有包含字母“e”的学生姓名。

7.2带有“_”通配符的查询

“_”只能代表单个字符,字符的长度不能为 0。

例如,

a_b

可以代表 acb、adb、aub 等字符串。

在 tb_students_info 表中,查找所有以字母“y”结尾,且“y”前面只有 4 个字母的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE name LIKE '____y';
+-------+
| name  |
+-------+
| Henry |
+-------+
1 row in set (0.00 sec)

7.3LIKE 区分大小写

默认情况下,LIKE 关键字匹配字符的时候是不区分大小写的。如果需要区分大小写,可以加入

BINARY

关键字。

在 tb_students_info 表中,查找所有以字母“t”开头的学生姓名,区分大小写和不区分大小写的 SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info WHERE name LIKE 't%';
+--------+
| name   |
+--------+
| Thomas |
| Tom    |
+--------+
2 rows in set (0.00 sec)

mysql> SELECT name FROM tb_students_info WHERE name LIKE BINARY 't%';
Empty set (0.01 sec)

由结果可以看到,区分大小写后,“Tom”和“Thomas”等记录就不会被匹配到了。

7.4使用通配符的注意事项和技巧

下面是使用通配符的一些注意事项:

  • 注意大小写。MySQL 默认是不区分大小写的。如果区分大小写,像“Tom”这样的数据就不能被“t%”所匹配到。
  • 注意尾部空格,尾部空格会干扰通配符的匹配。例如,“T% ”就不能匹配到“Tom”。
  • 注意 NULL。“%”通配符可以到匹配任意字符,但是不能匹配 NULL。也就是说 “%”匹配不到 tb_students_info 数据表中值为 NULL 的记录。

下面是一些使用通配符要记住的技巧。

  • 不要过度使用通配符,如果其它操作符能达到相同的目的,应该使用其它操作符。因为 MySQL 对通配符的处理一般会比其他操作符花费更长的时间。
  • 在确定使用通配符后,除非绝对有必要,否则不要把它们用在字符串的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
  • 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

总之,通配符是一种极其重要和有用的搜索工具,以后我们会经常用到它。

7.5拓展

如果查询内容中包含通配符,可以使用“\”转义符

。例如,在 tb_students_info 表中,将学生姓名“Dany”修改为“Dany%”后,查询以“%”结尾的学生姓名,SQL 语句和运行结果如下:

mysql> SELECT NAME FROM test.`tb_students_info` WHERE NAME LIKE '%\%';
+-------+
| NAME  |
+-------+
| Dany% |
+-------+
1 row in set (0.00 sec)

8.范围查询

MySQL 提供了

BETWEEN AND

关键字,用来判断字段的数值是否在指定范围内。

BETWEEN AND 需要两个参数,即范围的起始值和终止值。如果字段值在指定的范围内,则这些记录被返回。如果不在指定范围内,则不会被返回。

使用 BETWEEN AND 的基本语法格式如下:

[NOT] BETWEEN 取值1 AND 取值2

其中:

  • NOT:可选参数,表示指定范围之外的值。如果字段值不满足指定范围内的值,则这些记录被返回。
  • 取值1:表示范围的起始值。
  • 取值2:表示范围的终止值。

BETWEEN AND 和 NOT BETWEEN AND 关键字在查询指定范围内的记录时很有用。例如,查询学生的年龄段、出生日期,员工的工资水平等。

在表 tb_students_info 中查询年龄在 20 到 23 之间的学生姓名和年龄,SQL 语句和运行结果如下。

mysql> SELECT name,age FROM tb_students_info 
    -> WHERE age BETWEEN 20 AND 23;
+--------+------+
| name   | age  |
+--------+------+
| Green  |   23 |
| Henry  |   23 |
| Jane   |   22 |
| John   |   21 |
| Lily   |   22 |
| Susan  |   23 |
| Thomas |   22 |
| Tom    |   23 |
+--------+------+
8 rows in set (0.00 sec)

查询结果中包含学生年龄为 20 和 23 的记录,这就说明,

在 MySQL 中,BETWEEN AND 能匹配指定范围内的所有值,包括起始值和终止值。

在表 tb_students_info 中查询年龄不在 20 到 23 之间的学生姓名和年龄,SQL 语句和运行结果如下。

mysql> SELECT name,age FROM tb_students_info 
    -> WHERE age NOT BETWEEN 20 AND 23;
+------+------+
| name | age  |
+------+------+
| Dany |   25 |
| Jim  |   24 |
+------+------+
2 rows in set (0.00 sec)

在表 tb_students_info 中查询注册日期在 2015-10-01 和 2016-05-01 之间的学生信息。SQL 语句和运行结果如下。

mysql> SELECT name,login_date FROM tb_students_info
    -> WHERE login_date BETWEEN '2015-10-01' AND '2016-05-01';
+-------+------------+
| name  | login_date |
+-------+------------+
| Jim   | 2016-01-15 |
| John  | 2015-11-11 |
| Lily  | 2016-02-26 |
| Susan | 2015-10-01 |
+-------+------------+
4 rows in set (0.00 sec)

9.空值查询

MySQL 提供了

IS NULL

关键字,用来判断字段的值是否为空值(NULL)。

空值不同于 0,也不同于空字符串。

如果字段的值是空值,则满足查询条件,该记录将被查询出来。如果字段的值不是空值,则不满足查询条件。

使用 IS NULL 的基本语法格式如下:

IS [NOT] NULL

其中,“NOT”是可选参数,表示字段值不是空值时满足条件。

下面使用 IS NULL 关键字来查询 tb_students_info 表中 login_date 字段是 NULL 的记录。

mysql> SELECT `name`,`login_date` FROM tb_students_info 
    -> WHERE login_date IS NULL;
+--------+------------+
| NAME   | login_date |
+--------+------------+
| Dany   | NULL       |
| Green  | NULL       |
| Henry  | NULL       |
| Jane   | NULL       |
| Thomas | NULL       |
| Tom    | NULL       |
+--------+------------+
6 rows in set (0.01 sec)

注意:

IS NULL 是一个整体,不能将 IS 换成“=”

。如果将 IS 换成“=”将不能查询出任何结果,数据库系统会出现“Empty set(0.00 sec)”这样的提示。同理,IS NOT NULL 中的 IS NOT 不能换成“!=”或“<>”。

IS NOT NULL

表示查询字段值不为空的记录。

下面使用 IS NOT NULL 关键字来查询 tb_students_info 表中 login_date 字段不为空的记录。

mysql> SELECT `name`,login_date FROM tb_students_info 
    -> WHERE login_date IS NOT NULL;
+-------+------------+
| name  | login_date |
+-------+------------+
| Jim   | 2016-01-15 |
| John  | 2015-11-11 |
| Lily  | 2016-02-26 |
| Susan | 2015-10-01 |
+-------+------------+
4 rows in set (0.00 sec

10.分组查询

在 MySQL 中,

GROUP BY

关键字可以根据一个或多个字段对查询结果进行分组。

使用 GROUP BY 关键字的语法格式如下:

GROUP BY <字段名>

其中,“字段名”表示需要分组的字段名称,多个字段时用逗号隔开。

10.1GROUP BY单独使用

单独使用 GROUP BY 关键字时,查询结果会只显示每个分组的第一条记录。

下面根据 tb_students_info 表中的 sex 字段进行分组查询,SQL 语句和运行结果如下:

mysql> SELECT `name`,`sex` FROM tb_students_info 
    -> GROUP BY sex;
+-------+------+
| name  | sex  |
+-------+------+
| Henry | 女   |
| Dany  | 男   |
+-------+------+
2 rows in set (0.01 sec)

结果中只显示了两条记录,这两条记录的 sex 字段的值分别为“女”和“男”。

10.2GROUP BY 与 GROUP_CONCAT()

GROUP BY 关键字可以和

GROUP_CONCAT()

函数一起使用。GROUP_CONCAT() 函数会把每个分组的字段值都显示出来。

下面根据 tb_students_info 表中的 sex 字段进行分组查询,使用 GROUP_CONCAT() 函数将每个分组的 name 字段的值都显示出来。SQL 语句和运行结果如下:

mysql> SELECT `sex`, GROUP_CONCAT(name) FROM tb_students_info 
    -> GROUP BY sex;
+------+----------------------------+
| sex  | GROUP_CONCAT(name)         |
+------+----------------------------+
| 女   | Henry,Jim,John,Thomas,Tom  |
| 男   | Dany,Green,Jane,Lily,Susan |
+------+----------------------------+
2 rows in set (0.00 sec)

由结果可以看到,查询结果分为两组,sex 字段值为“女”的是一组,值为“男”的是一组,且每组的学生姓名都显示出来了。

下面根据 tb_students_info 表中的 age 和 sex 字段进行分组查询。SQL 语句和运行结果如下:

mysql> SELECT age,sex,GROUP_CONCAT(name) FROM tb_students_info 
    -> GROUP BY age,sex;
+------+------+--------------------+
| age  | sex  | GROUP_CONCAT(name) |
+------+------+--------------------+
|   21 | 女   | John               |
|   22 | 女   | Thomas             |
|   22 | 男   | Jane,Lily          |
|   23 | 女   | Henry,Tom          |
|   23 | 男   | Green,Susan        |
|   24 | 女   | Jim                |
|   25 | 男   | Dany               |
+------+------+--------------------+
7 rows in set (0.00 sec)

上面实例在分组过程中,先按照 age 字段进行分组,当 age 字段值相等时,再把 age 字段值相等的记录按照 sex 字段进行分组。

多个字段分组查询时,会先按照第一个字段进行分组。如果第一个字段中有相同的值,MySQL 才会按照第二个字段进行分组。如果第一个字段中的数据都是唯一的,那么 MySQL 将不再对第二个字段进行分组。

10.3GROUP BY 与聚合函数

在数据统计时,GROUP BY 关键字经常和聚合函数一起使用。

聚合函数包括 COUNT(),SUM(),AVG(),MAX() 和 MIN()。其中,COUNT() 用来统计记录的条数;SUM() 用来计算字段值的总和;AVG() 用来计算字段值的平均值;MAX() 用来查询字段的最大值;MIN() 用来查询字段的最小值。

下面根据 tb_students_info 表的 sex 字段进行分组查询,使用 COUNT() 函数计算每一组的记录数。SQL 语句和运行结果如下:

mysql> SELECT sex,COUNT(sex) FROM tb_students_info 
    -> GROUP BY sex;
+------+------------+
| sex  | COUNT(sex) |
+------+------------+
| 女   |          5 |
| 男   |          5 |
+------+------------+
2 rows in set (0.00 sec)

结果显示,sex 字段值为“女”的记录是一组,有 5 条记录;sex 字段值为“男”的记录是一组,有 5 条记录。

10.4GROUP BY 与 WITH ROLLUP

WITH POLLUP

关键字用来在所有记录的最后加上一条记录,这条记录是上面所有记录的总和,即统计记录数量。

下面根据 tb_students_info 表中的 sex 字段进行分组查询,并使用 WITH ROLLUP 显示记录的总和。

mysql> SELECT sex,GROUP_CONCAT(name) FROM tb_students_info 
    ->GROUP BY sex WITH ROLLUP;
+------+------------------------------------------------------+
| sex  | GROUP_CONCAT(name)                                   |
+------+------------------------------------------------------+
| 女   | Henry,Jim,John,Thomas,Tom                            |
| 男   | Dany,Green,Jane,Lily,Susan                           |
| NULL | Henry,Jim,John,Thomas,Tom,Dany,Green,Jane,Lily,Susan |
+------+------------------------------------------------------+
3 rows in set (0.00 sec)

查询结果显示,GROUP_CONCAT(name) 显示了每个分组的 name 字段值。同时,最后一条记录的 GROUP_CONCAT(name) 字段的值刚好是上面分组 name 字段值的总和。

11.过滤分组

在 MySQL 中,可以使用

HAVING

关键字对分组后的数据进行过滤。

使用 HAVING 关键字的语法格式如下:

HAVING <查询条件>

HAVING 关键字和 WHERE 关键字都可以用来过滤数据,且 HAVING 支持 WHERE 关键字中所有的操作符和语法。

但是 WHERE 和 HAVING 关键字也存在以下几点差异:

  • 一般情况下,WHERE 用于过滤数据行,而 HAVING 用于过滤分组。
  • WHERE 查询条件中不可以使用聚合函数,而 HAVING 查询条件中可以使用聚合函数。
  • WHERE 在数据分组前进行过滤,而 HAVING 在数据分组后进行过滤 。
  • WHERE 针对数据库文件进行过滤,而 HAVING 针对查询结果进行过滤。也就是说,WHERE 根据数据表中的字段直接进行过滤,而 HAVING 是根据前面已经查询出的字段进行过滤。
  • WHERE 查询条件中不可以使用字段别名,而 HAVING 查询条件中可以使用字段别名。

下面通过实例让大家更直观的了解 WHERE 和 HAVING 关键字的相同点和不同点。

分别使用 HAVING 和 WHERE 关键字查询出 tb_students_info 表中身高大于 150 的学生姓名,性别和身高。SQL 语句和运行结果如下。

mysql> SELECT name,sex,height FROM tb_students_info 
    -> HAVING height>150;
+--------+------+--------+
| name   | sex  | height |
+--------+------+--------+
| Dany   | 男   |    160 |
| Green  | 男   |    158 |
| Henry  | 女   |    185 |
| Jane   | 男   |    162 |
| Jim    | 女   |    175 |
| John   | 女   |    172 |
| Lily   | 男   |    165 |
| Susan  | 男   |    170 |
| Thomas | 女   |    178 |
| Tom    | 女   |    165 |
+--------+------+--------+
10 rows in set (0.00 sec)

mysql> SELECT name,sex,height FROM tb_students_info 
    -> WHERE height>150;
+--------+------+--------+
| name   | sex  | height |
+--------+------+--------+
| Dany   | 男   |    160 |
| Green  | 男   |    158 |
| Henry  | 女   |    185 |
| Jane   | 男   |    162 |
| Jim    | 女   |    175 |
| John   | 女   |    172 |
| Lily   | 男   |    165 |
| Susan  | 男   |    170 |
| Thomas | 女   |    178 |
| Tom    | 女   |    165 |
+--------+------+--------+
10 rows in set (0.00 sec)

上述实例中,因为在 SELECT 关键字后已经查询出了 height 字段,所以 HAVING 和 WHERE 都可以使用。但是如果 SELECT 关键字后没有查询出 height 字段,MySQL 就会报错。

使用 HAVING 和 WHERE 关键字分别查询出 tb_students_info 表中身高大于 150 的学生姓名和性别(与例 1 相比,这次没有查询 height 字段)。SQL 语句和运行结果如下。

mysql> SELECT name,sex FROM tb_students_info 
    -> WHERE height>150;
+--------+------+
| name   | sex  |
+--------+------+
| Dany   | 男   |
| Green  | 男   |
| Henry  | 女   |
| Jane   | 男   |
| Jim    | 女   |
| John   | 女   |
| Lily   | 男   |
| Susan  | 男   |
| Thomas | 女   |
| Tom    | 女   |
+--------+------+
10 rows in set (0.00 sec)

mysql> SELECT name,sex FROM tb_students_info HAVING height>150;
ERROR 1054 (42S22): Unknown column 'height' in 'having clause'

由结果可以看出,如果 SELECT 关键字后没有查询出 HAVING 查询条件中使用的 height 字段,MySQL 会提示错误信息:“having子句”中的列“height”未知”。

根据 height 字段对 tb_students_info 表中的数据进行分组,并使用 HAVING 和 WHERE 关键字分别查询出分组后平均身高大于 170 的学生姓名、性别和身高。SQL 语句和运行结果如下。

mysql> SELECT GROUP_CONCAT(name),sex,height FROM tb_students_info 
    -> GROUP BY height 
    -> HAVING AVG(height)>170;
+--------------------+------+--------+
| GROUP_CONCAT(name) | sex  | height |
+--------------------+------+--------+
| John               | 女   |    172 |
| Jim                | 女   |    175 |
| Thomas             | 女   |    178 |
| Henry              | 女   |    185 |
+--------------------+------+--------+
4 rows in set (0.00 sec)

mysql> SELECT GROUP_CONCAT(name),sex,height FROM tb_students_info WHERE AVG(height)>170 GROUP BY height;
ERROR 1111 (HY000): Invalid use of group function

由结果可以看出,如果在 WHERE 查询条件中使用聚合函数,MySQL 会提示错误信息:无效使用组函数。

12.交叉连接

交叉连接(CROSS JOIN)一般

用来返回连接表的笛卡尔积。

交叉连接的语法格式如下:

SELECT <字段名> FROM <表1> CROSS JOIN <表2> [WHERE子句]

SELECT <字段名> FROM <表1>, <表2> [WHERE子句]

语法说明如下:

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要交叉连接的表名。
  • WHERE 子句:用来设置交叉连接的查询条件。

注意:多个表交叉连接时,在 FROM 后连续使用 CROSS JOIN 或

,

即可。以上两种语法的返回结果是相同的,但是第一种语法才是官方建议的标准写法。

当连接的表之间没有关系时,我们会省略掉 WHERE 子句,这时返回结果就是两个表的笛卡尔积,返回结果数量就是两个表的数据行相乘。需要注意的是,如果每个表有 1000 行,那么返回结果的数量就有 1000×1000 = 1000000 行,数据量是非常巨大的。

交叉连接可以查询两个或两个以上的表,为了让读者更好的理解,下面先讲解两个表的交叉连接查询。

查询学生信息表和科目信息表,并得到一个笛卡尔积。

为了方便观察学生信息表和科目表交叉连接后的运行结果,我们先分别查询出这两个表的数据,再进行交叉连接查询。

1)查询 tb_students_info 表中的数据,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_students_info;
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 | 男   |    160 |         1 |
|  2 | Green  |   23 | 男   |    158 |         2 |
|  3 | Henry  |   23 | 女   |    185 |         1 |
|  4 | Jane   |   22 | 男   |    162 |         3 |
|  5 | Jim    |   24 | 女   |    175 |         2 |
|  6 | John   |   21 | 女   |    172 |         4 |
|  7 | Lily   |   22 | 男   |    165 |         4 |
|  8 | Susan  |   23 | 男   |    170 |         5 |
|  9 | Thomas |   22 | 女   |    178 |         5 |
| 10 | Tom    |   23 | 女   |    165 |         5 |
+----+--------+------+------+--------+-----------+
10 rows in set (0.00 sec)

2)查询 tb_course 表中的数据,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course;
+----+-------------+
| id | course_name |
+----+-------------+
|  1 | Java        |
|  2 | MySQL       |
|  3 | Python      |
|  4 | Go          |
|  5 | C++         |
+----+-------------+
5 rows in set (0.00 sec)

3)使用 CROSS JOIN 查询出两张表中的笛卡尔积,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info;
+----+-------------+----+--------+------+------+--------+-----------+
| id | course_name | id | name   | age  | sex  | height | course_id |
+----+-------------+----+--------+------+------+--------+-----------+
|  1 | Java        |  1 | Dany   |   25 | 男   |    160 |         1 |
|  2 | MySQL       |  1 | Dany   |   25 | 男   |    160 |         1 |
|  3 | Python      |  1 | Dany   |   25 | 男   |    160 |         1 |
|  4 | Go          |  1 | Dany   |   25 | 男   |    160 |         1 |
|  5 | C++         |  1 | Dany   |   25 | 男   |    160 |         1 |
|  1 | Java        |  2 | Green  |   23 | 男   |    158 |         2 |
|  2 | MySQL       |  2 | Green  |   23 | 男   |    158 |         2 |
|  3 | Python      |  2 | Green  |   23 | 男   |    158 |         2 |
|  4 | Go          |  2 | Green  |   23 | 男   |    158 |         2 |
|  5 | C++         |  2 | Green  |   23 | 男   |    158 |         2 |
|  1 | Java        |  3 | Henry  |   23 | 女   |    185 |         1 |
|  2 | MySQL       |  3 | Henry  |   23 | 女   |    185 |         1 |
|  3 | Python      |  3 | Henry  |   23 | 女   |    185 |         1 |
|  4 | Go          |  3 | Henry  |   23 | 女   |    185 |         1 |
|  5 | C++         |  3 | Henry  |   23 | 女   |    185 |         1 |
|  1 | Java        |  4 | Jane   |   22 | 男   |    162 |         3 |
|  2 | MySQL       |  4 | Jane   |   22 | 男   |    162 |         3 |
|  3 | Python      |  4 | Jane   |   22 | 男   |    162 |         3 |
|  4 | Go          |  4 | Jane   |   22 | 男   |    162 |         3 |
|  5 | C++         |  4 | Jane   |   22 | 男   |    162 |         3 |
|  1 | Java        |  5 | Jim    |   24 | 女   |    175 |         2 |
|  2 | MySQL       |  5 | Jim    |   24 | 女   |    175 |         2 |
|  3 | Python      |  5 | Jim    |   24 | 女   |    175 |         2 |
|  4 | Go          |  5 | Jim    |   24 | 女   |    175 |         2 |
|  5 | C++         |  5 | Jim    |   24 | 女   |    175 |         2 |
|  1 | Java        |  6 | John   |   21 | 女   |    172 |         4 |
|  2 | MySQL       |  6 | John   |   21 | 女   |    172 |         4 |
|  3 | Python      |  6 | John   |   21 | 女   |    172 |         4 |
|  4 | Go          |  6 | John   |   21 | 女   |    172 |         4 |
|  5 | C++         |  6 | John   |   21 | 女   |    172 |         4 |
|  1 | Java        |  7 | Lily   |   22 | 男   |    165 |         4 |
|  2 | MySQL       |  7 | Lily   |   22 | 男   |    165 |         4 |
|  3 | Python      |  7 | Lily   |   22 | 男   |    165 |         4 |
|  4 | Go          |  7 | Lily   |   22 | 男   |    165 |         4 |
|  5 | C++         |  7 | Lily   |   22 | 男   |    165 |         4 |
|  1 | Java        |  8 | Susan  |   23 | 男   |    170 |         5 |
|  2 | MySQL       |  8 | Susan  |   23 | 男   |    170 |         5 |
|  3 | Python      |  8 | Susan  |   23 | 男   |    170 |         5 |
|  4 | Go          |  8 | Susan  |   23 | 男   |    170 |         5 |
|  5 | C++         |  8 | Susan  |   23 | 男   |    170 |         5 |
|  1 | Java        |  9 | Thomas |   22 | 女   |    178 |         5 |
|  2 | MySQL       |  9 | Thomas |   22 | 女   |    178 |         5 |
|  3 | Python      |  9 | Thomas |   22 | 女   |    178 |         5 |
|  4 | Go          |  9 | Thomas |   22 | 女   |    178 |         5 |
|  5 | C++         |  9 | Thomas |   22 | 女   |    178 |         5 |
|  1 | Java        | 10 | Tom    |   23 | 女   |    165 |         5 |
|  2 | MySQL       | 10 | Tom    |   23 | 女   |    165 |         5 |
|  3 | Python      | 10 | Tom    |   23 | 女   |    165 |         5 |
|  4 | Go          | 10 | Tom    |   23 | 女   |    165 |         5 |
|  5 | C++         | 10 | Tom    |   23 | 女   |    165 |         5 |
+----+-------------+----+--------+------+------+--------+-----------+
50 rows in set (0.00 sec)

由运行结果可以看出,tb_course 和 tb_students_info 表交叉连接查询后,返回了 50 条记录。可以想象,当表中的数据较多时,得到的运行结果会非常长,而且得到的运行结果也没太大的意义。所以,通过交叉连接的方式进行多表查询的这种方法并不常用,我们应该尽量避免这种查询

查询 tb_course 表中的 id 字段和 tb_students_info 表中的 course_id 字段相等的内容, SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info 
    -> WHERE tb_students_info.course_id = tb_course.id;
+----+-------------+----+--------+------+------+--------+-----------+
| id | course_name | id | name   | age  | sex  | height | course_id |
+----+-------------+----+--------+------+------+--------+-----------+
|  1 | Java        |  1 | Dany   |   25 | 男   |    160 |         1 |
|  2 | MySQL       |  2 | Green  |   23 | 男   |    158 |         2 |
|  1 | Java        |  3 | Henry  |   23 | 女   |    185 |         1 |
|  3 | Python      |  4 | Jane   |   22 | 男   |    162 |         3 |
|  2 | MySQL       |  5 | Jim    |   24 | 女   |    175 |         2 |
|  4 | Go          |  6 | John   |   21 | 女   |    172 |         4 |
|  4 | Go          |  7 | Lily   |   22 | 男   |    165 |         4 |
|  5 | C++         |  8 | Susan  |   23 | 男   |    170 |         5 |
|  5 | C++         |  9 | Thomas |   22 | 女   |    178 |         5 |
|  5 | C++         | 10 | Tom    |   23 | 女   |    165 |         5 |
+----+-------------+----+--------+------+------+--------+-----------+
10 rows in set (0.01 sec)

如果在交叉连接时使用 WHERE 子句,MySQL 会先生成两个表的笛卡尔积,然后再选择满足 WHERE 条件的记录。因此,表的数量较多时,交叉连接会非常非常慢。一般情况下不建议使用交叉连接。

12.1笛卡尔积

笛卡尔积(Cartesian product)是指两个集合 X 和 Y 的乘积。

例如,有 A 和 B 两个集合,它们的值如下:

A = {1,2}
B = {3,4,5}

集合 A×B 和 B×A 的结果集分别表示为:

A×B={(1,3), (1,4), (1,5), (2,3), (2,4), (2,5) };
B×A={(3,1), (3,2), (4,1), (4,2), (5,1), (5,2) };

以上 A×B 和 B×A 的结果就叫做两个集合的笛卡尔积。

并且,从以上结果我们可以看出:

  • 两个集合相乘,不满足交换率,即 A×B≠B×A。
  • A 集合和 B 集合的笛卡尔积是 A 集合的元素个数 × B 集合的元素个数。

多表查询遵循的算法就是以上提到的笛卡尔积,表与表之间的连接可以看成是在做乘法运算。在实际应用中,应避免使用笛卡尔积,因为笛卡尔积中容易存在大量的不合理数据,简单来说就是容易导致查询结果重复、混乱。

13.内连接

内连接(INNER JOIN)

主要通过设置连接条件的方式,来移除查询结果中某些数据行的交叉连接。简单来说,就是利用条件表达式来消除交叉连接的某些数据行。

内连接使用

INNER JOIN

关键字连接两张表,并使用 ON 子句来设置连接条件。

如果没有连接条件,INNER JOIN 和 CROSS JOIN 在语法上是等同的,两者可以互换。

内连接的语法格式如下:

SELECT <字段名> FROM <表1> INNER JOIN <表2> [ON子句]

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要内连接的表名。
  • INNER JOIN :内连接中可以省略 INNER 关键字,只用关键字 JOIN。
  • ON 子句:用来设置内连接的连接条件。

INNER JOIN 也可以使用 WHERE 子句指定连接条件,但是 INNER JOIN … ON 语法是官方的标准写法,而且 WHERE 子句在某些时候会影响查询的性能。

多个表内连接时,在 FROM 后连续使用 INNER JOIN 或 JOIN 即可。

内连接可以查询两个或两个以上的表。为了让大家更好的理解,暂时只讲解两个表的连接查询。

在 tb_students_info 表和 tb_course 表之间,使用内连接查询学生姓名和相对应的课程名称,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name FROM tb_students_info s INNER JOIN tb_course c 
    -> ON s.course_id = c.id;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Green  | MySQL       |
| Henry  | Java        |
| Jane   | Python      |
| Jim    | MySQL       |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
+--------+-------------+
10 rows in set (0.00 sec)

在这里的查询语句中,两个表之间的关系通过 INNER JOIN 指定,连接的条件使用 ON 子句给出。

注意:当对多个表进行查询时,要在 SELECT 语句后面指定字段是来源于哪一张表。因此,在多表查询时,SELECT 语句后面的写法是

表名.列名

。另外,如果表名非常长的话,也可以给表设置别名,这样就可以直接在 SELECT 语句后面写上

表的别名.列名

14.外连接

外连接可以分为左外连接和右外连接,下面根据实例分别介绍左外连接和右外连接。

14.1左连接

左外连接又称为左连接,使用

LEFT OUTER JOIN

关键字连接两个表,并使用 ON 子句来设置连接条件。

左连接的语法格式如下:

SELECT <字段名> FROM <表1> LEFT OUTER JOIN <表2> <ON子句>

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要左连接的表名。
  • LEFT OUTER JOIN:左连接中可以省略 OUTER 关键字,只使用关键字 LEFT JOIN。
  • ON 子句:用来设置左连接的连接条件,不能省略。

上述语法中,“表1”为基表,“表2”为参考表。左连接查询时,可以查询出“表1”中的所有记录和“表2”中匹配连接条件的记录。如果“表1”的某行在“表2”中没有匹配行,那么在返回结果中,“表2”的字段值均为空值(NULL)。

在进行左连接查询之前,我们先查看 tb_course 和 tb_students_info 两张表中的数据。SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_course;
+----+-------------+
| id | course_name |
+----+-------------+
|  1 | Java        |
|  2 | MySQL       |
|  3 | Python      |
|  4 | Go          |
|  5 | C++         |
|  6 | HTML        |
+----+-------------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM tb_students_info;
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 | 男   |    160 |         1 |
|  2 | Green  |   23 | 男   |    158 |         2 |
|  3 | Henry  |   23 | 女   |    185 |         1 |
|  4 | Jane   |   22 | 男   |    162 |         3 |
|  5 | Jim    |   24 | 女   |    175 |         2 |
|  6 | John   |   21 | 女   |    172 |         4 |
|  7 | Lily   |   22 | 男   |    165 |         4 |
|  8 | Susan  |   23 | 男   |    170 |         5 |
|  9 | Thomas |   22 | 女   |    178 |         5 |
| 10 | Tom    |   23 | 女   |    165 |         5 |
| 11 | LiMing |   22 | 男   |    180 |         7 |
+----+--------+------+------+--------+-----------+
11 rows in set (0.00 sec)

在 tb_students_info 表和 tb_course 表中查询所有学生姓名和相对应的课程名称,包括没有课程的学生,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name FROM tb_students_info s LEFT OUTER JOIN tb_course c 
    -> ON s.`course_id`=c.`id`;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Henry  | Java        |
| NULL   | Java        |
| Green  | MySQL       |
| Jim    | MySQL       |
| Jane   | Python      |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
| LiMing | NULL        |
+--------+-------------+
12 rows in set (0.00 sec)

可以看到,运行结果显示了 12 条记录,name 为 LiMing 的学生目前没有课程,因为对应的 tb_course 表中没有该学生的课程信息,所以该条记录只取出了 tb_students_info 表中相应的值,而从 tb_course 表中取出的值为 NULL。

14.2右连接

右外连接又称为右连接,右连接是左连接的反向连接。使用

RIGHT OUTER JOIN

关键字连接两个表,并使用 ON 子句来设置连接条件。

右连接的语法格式如下:

SELECT <字段名> FROM <表1> RIGHT OUTER JOIN <表2> <ON子句>

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要右连接的表名。
  • RIGHT OUTER JOIN:右连接中可以省略 OUTER 关键字,只使用关键字 RIGHT JOIN。
  • ON 子句:用来设置右连接的连接条件,不能省略。

与左连接相反,右连接以“表2”为基表,“表1”为参考表。右连接查询时,可以查询出“表2”中的所有记录和“表1”中匹配连接条件的记录。如果“表2”的某行在“表1”中没有匹配行,那么在返回结果中,“表1”的字段值均为空值(NULL)。

在 tb_students_info 表和 tb_course 表中查询所有课程,包括没有学生的课程,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name FROM tb_students_info s RIGHT OUTER JOIN tb_course c 
    -> ON s.`course_id`=c.`id`;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Green  | MySQL       |
| Henry  | Java        |
| Jane   | Python      |
| Jim    | MySQL       |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
| NULL   | HTML        |
+--------+-------------+
11 rows in set (0.00 sec)

可以看到,结果显示了 11 条记录,名称为 HTML 的课程目前没有学生,因为对应的 tb_students_info 表中并没有该学生的信息,所以该条记录只取出了 tb_course 表中相应的值,而从 tb_students_info 表中取出的值为 NULL。

多个表左/右连接时,在 ON 子句后连续使用 LEFT/RIGHT OUTER JOIN 或 LEFT/RIGHT JOIN 即可。

使用外连接查询时,一定要分清需要查询的结果,是需要显示左表的全部记录还是右表的全部记录,然后选择相应的左连接和右连接。

15.子查询

子查询是 MySQL 中比较常用的查询方法,通过子查询可以实现多表查询。

子查询指将一个查询语句嵌套在另一个查询语句中。

子查询可以在 SELECT、UPDATE 和 DELETE 语句中使用,而且可以进行多层嵌套。在实际开发时,子查询经常出现在 WHERE 子句中。

子查询在 WHERE 中的语法格式如下:

WHERE <表达式> <操作符> (子查询)

其中,操作符可以是比较运算符和 IN、NOT IN、EXISTS、NOT EXISTS 等关键字。

15.1 IN | NOT IN

当表达式与子查询返回的结果集中的某个值相等时,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回值正好相反。

15.2 EXISTS | NOT EXISTS

用于判断子查询的结果集是否为空,若子查询的结果集不为空,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回的值正好相反。

使用子查询在 tb_students_info 表和 tb_course 表中查询学习 Java 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id IN (SELECT id FROM tb_course WHERE course_name = 'Java');
+-------+
| name  |
+-------+
| Dany  |
| Henry |
+-------+
2 rows in set (0.01 sec)

结果显示,学习 Java 课程的只有 Dany 和 Henry。上述查询过程也可以分为以下 2 步执行,实现效果是相同的。

1)首先单独执行内查询,查询出 tb_course 表中课程为 Java 的 id,SQL 语句和运行结果如下。

mysql> SELECT id FROM tb_course 
    -> WHERE course_name = 'Java';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

可以看到,符合条件的 id 字段的值为 1。

2)然后执行外层查询,在 tb_students_info 表中查询 course_id 等于 1 的学生姓名。SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id IN (1);
+-------+
| name  |
+-------+
| Dany  |
| Henry |
+-------+
2 rows in set (0.00 sec)

习惯上,外层的 SELECT 查询称为父查询,圆括号中嵌入的查询称为子查询(

子查询必须放在圆括号内

)。MySQL 在处理上例的 SELECT 语句时,执行流程为:

先执行子查询,再执行父查询。

与例 1 类似,在 SELECT 语句中使用 NOT IN 关键字,查询没有学习 Java 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id NOT IN (SELECT id FROM tb_course WHERE course_name = 'Java');
+--------+
| name   |
+--------+
| Green  |
| Jane   |
| Jim    |
| John   |
| Lily   |
| Susan  |
| Thomas |
| Tom    |
| LiMing |
+--------+
9 rows in set (0.01 sec)

可以看出,运行结果与例 1 刚好相反,没有学习 Java 课程的是除了 Dany 和 Henry 之外的学生。

使用

=

运算符,在 tb_course 表和 tb_students_info 表中查询出所有学习 Python 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE course_id = (SELECT id FROM tb_course WHERE course_name = 'Python');
+------+
| name |
+------+
| Jane |
+------+
1 row in set (0.00 sec)

结果显示,学习 Python 课程的学生只有 Jane。

使用

<>

运算符,在 tb_course 表和 tb_students_info 表中查询出没有学习 Python 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE course_id <> (SELECT id FROM tb_course WHERE course_name = 'Python');
+--------+
| name   |
+--------+
| Dany   |
| Green  |
| Henry  |
| Jim    |
| John   |
| Lily   |
| Susan  |
| Thomas |
| Tom    |
| LiMing |
+--------+
10 rows in set (0.00 sec)

可以看出,运行结果与例 3 刚好相反,没有学习 Python 课程的是除了 Jane 之外的学生。

查询 tb_course 表中是否存在 id=1 的课程,如果存在,就查询出 tb_students_info 表中的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE EXISTS(SELECT course_name FROM tb_course WHERE id=1);
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 | 男   |    160 |         1 |
|  2 | Green  |   23 | 男   |    158 |         2 |
|  3 | Henry  |   23 | 女   |    185 |         1 |
|  4 | Jane   |   22 | 男   |    162 |         3 |
|  5 | Jim    |   24 | 女   |    175 |         2 |
|  6 | John   |   21 | 女   |    172 |         4 |
|  7 | Lily   |   22 | 男   |    165 |         4 |
|  8 | Susan  |   23 | 男   |    170 |         5 |
|  9 | Thomas |   22 | 女   |    178 |         5 |
| 10 | Tom    |   23 | 女   |    165 |         5 |
| 11 | LiMing |   22 | 男   |    180 |         7 |
+----+--------+------+------+--------+-----------+
11 rows in set (0.01 sec)

由结果可以看到,tb_course 表中存在 id=1 的记录,因此 EXISTS 表达式返回 TRUE,外层查询语句接收 TRUE 之后对表 tb_students_info 进行查询,返回所有的记录。

EXISTS 关键字可以和其它查询条件一起使用,条件表达式与 EXISTS 关键字之间用 AND 和 OR 连接。

查询 tb_course 表中是否存在 id=1 的课程,如果存在,就查询出 tb_students_info 表中 age 字段大于 24 的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE age>24 AND EXISTS(SELECT course_name FROM tb_course WHERE id=1);
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  1 | Dany |   25 | 男   |    160 |         1 |
+----+------+------+------+--------+-----------+
1 row in set (0.01 sec)

结果显示,从 tb_students_info 表中查询出了一条记录,这条记录的 age 字段取值为 25。内层查询语句从 tb_course 表中查询到记录,返回 TRUE。外层查询语句开始进行查询。根据查询条件,从 tb_students_info 表中查询 age 大于 24 的记录。

子查询的功能也可以通过表连接完成,但是子查询会使 SQL 语句更容易阅读和编写。

一般来说,表连接(内连接和外连接等)都可以用子查询替换,但反过来却不一定,有的子查询不能用表连接来替换。子查询比较灵活、方便、形式多样,适合作为查询的筛选条件,而表连接更适合于查看连接表的数据。

在完成较复杂的数据查询时,经常会使用到子查询,编写子查询语句时,要注意如下事项。

15.3 子查询语句可以嵌套在 SQL 语句中任何表达式出现的位置

在 SELECT 语句中,子查询可以被嵌套在 SELECT 语句的列、表和查询条件中,即 SELECT 子句,FROM 子句、WHERE 子句、GROUP BY 子句和 HAVING 子句。

前面已经介绍了 WHERE 子句中嵌套子查询的使用方法,下面是子查询在 SELECT 子句和 FROM 子句中的使用语法。

嵌套在 SELECT 语句的 SELECT 子句中的子查询语法格式如下。

SELECT (子查询) FROM 表名;

提示:子查询结果为单行单列,但不必指定列别名。

嵌套在 SELECT 语句的 FROM 子句中的子查询语法格式如下。

SELECT * FROM (子查询) AS 表的别名;

注意:必须为表指定别名。一般返回多行多列数据记录,可以当作一张临时表。

15.4 只出现在子查询中而没有出现在父查询中的表不能包含在输出列中

多层嵌套子查询的最终数据集只包含父查询(即最外层的查询)的 SELECT 子句中出现的字段,而子查询的输出结果通常会作为其外层子查询数据源或用于数据判断匹配。

常见错误如下:

SELECT * FROM (SELECT * FROM result);

这个子查询语句产生语法错误的原因在于主查询语句的 FROM 子句是一个子查询语句,因此应该为子查询结果集指定别名。正确代码如下。

SELECT * FROM (SELECT * FROM result) AS Temp;

16.联合查询

联合查询
联合查询结果是将多个select语句的查询结果合并到一块因为在某种情况下需要将几个select语句查询的结果合并起来显示。比如现在需要查询两个公司的所有员工的信息,这就需要从甲公司查询所有员工信息,再从乙公司查询所有的员工信息,然后将两次的查询结果进行合并。
可以使用union和union all关键字进行操作

语法格式如下:
在这里插入图片描述

其中union选项有两个选项可选
all:表示无论重复都输出
distinct: 去重(整个重复)(默认的)

在这里插入图片描述

17.正则表达式查询

正则表达式主要用来查询和替换符合某个模式(规则)的文本内容。例如,从一个文件中提取电话号码,查找一篇文章中重复的单词、替换文章中的敏感语汇等,这些地方都可以使用正则表达式。正则表达式强大且灵活,常用于非常复杂的查询。

MySQL 中,使用

REGEXP

关键字指定正则表达式的字符匹配模式,其基本语法格式如下:

属性名 REGEXP ‘匹配方式’

其中,“属性名”表示需要查询的字段名称;“匹配方式”表示以哪种方式来匹配查询。“匹配方式”中有很多的模式匹配字符,它们分别表示不同的意思。下表列出了 REGEXP 操作符中常用的匹配方式。

选项说明例子匹配值示例
^匹配文本的开始字符‘^b’ 匹配以字母 b 开头的字符串book、big、banana、bike
$匹配文本的结束字符‘st$’ 匹配以 st 结尾的字符串test、resist、persist
.匹配任何单个字符‘b.t’ 匹配任何 b 和 t 之间有一个字符bit、bat、but、bite
*匹配零个或多个在它前面的字符‘f*n’ 匹配字符 n 前面有任意个字符 ffn、fan、faan、abcn
+匹配前面的字符 1 次或多次‘ba+’ 匹配以 b 开头,后面至少紧跟一个 aba、bay、bare、battle
<字符串>匹配包含指定字符的文本‘fa’ 匹配包含‘fa’的文本fan、afa、faad
[字符集合]匹配字符集合中的任何一个字符‘[xz]’ 匹配 x 或者 zdizzy、zebra、x-ray、extra
[^]匹配不在括号中的任何字符‘[^abc]’ 匹配任何不包含 a、b 或 c 的字符串desk、fox、f8ke
字符串{n,}匹配前面的字符串至少 n 次‘b{2}’ 匹配 2 个或更多的 bbbb、bbbb、bbbbbbb
字符串 {n,m}匹配前面的字符串至少 n 次, 至多 m 次‘b{2,4}’ 匹配最少 2 个,最多 4 个 bbbb、bbbb

17.1查询以特定字符或字符串开头的记录

字符

^

用来匹配以特定字符或字符串开头的记录。

在 tb_students_info 表中,查询 name 字段以“J”开头的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info 
    -> WHERE name REGEXP '^J';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  4 | Jane |   22 | 男   |    162 |         3 |
|  5 | Jim  |   24 | 女   |    175 |         2 |
|  6 | John |   21 | 女   |    172 |         4 |
+----+------+------+------+--------+-----------+
3 rows in set (0.01 sec)

在 tb_students_info 表中,查询 name 字段以“Ji”开头的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Ji';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  5 | Jim  |   24 | 女   |    175 |         2 |
+----+------+------+------+--------+-----------+
1 row in set (0.00 sec)

17.2查询以特定字符或字符串结尾的记录

字符

$

用来匹配以特定字符或字符串结尾的记录。

在 tb_students_info 表中,查询 name 字段以“y”结尾的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'y$';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 | 男   |    160 |         1 |
|  3 | Henry |   23 | 女   |    185 |         1 |
|  7 | Lily  |   22 | 男   |    165 |         4 |
+----+-------+------+------+--------+-----------+
3 rows in set (0.00 sec)

在 tb_students_info 表中,查询 name 字段以“ry”结尾的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'ry$';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  3 | Henry |   23 | 女   |    185 |         1 |
+----+-------+------+------+--------+-----------+
1 row in set (0.00 sec)

17.3替代字符串中的任意一个字符

字符

.

用来替代字符串中的任意一个字符。

在 tb_students_info 表中,查询 name 字段值包含“a”和“y”,且两个字母之间只有一个字母的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'a.y';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  1 | Dany |   25 | 男   |    160 |         1 |
+----+------+------+------+--------+-----------+
1 row in set (0.00 sec)

17.4匹配多个字符

字符

*

+

都可以匹配多个该符号之前的字符。不同的是,

+

表示至少一个字符,而

*

可以表示 0 个字符。

在 tb_students_info 表中,查询 name 字段值包含字母“T”,且“T”后面出现字母“h”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Th*';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  9 | Thomas |   22 | 女   |    178 |         5 |
| 10 | Tom    |   23 | 女   |    165 |         5 |
+----+--------+------+------+--------+-----------+
2 rows in set (0.00 sec)

在 tb_students_info 表中,查询 name 字段值包含字母“T”,且“T”后面至少出现“h”一次的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Th+';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  9 | Thomas |   22 | 女   |    178 |         5 |
+----+--------+------+------+--------+-----------+
1 row in set (0.00 sec)

17.5匹配指定字符串

正则表达式可以匹配字符串。当表中的记录包含这个字符串时,就可以将该记录查询出来。指定多个字符串时,需要用

|

隔开。只要匹配这些字符串中的任意一个即可。

在 tb_students_info 表中,查询 name 字段值包含字符串“an”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'an';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 | 男   |    160 |         1 |
|  4 | Jane  |   22 | 男   |    162 |         3 |
|  8 | Susan |   23 | 男   |    170 |         5 |
+----+-------+------+------+--------+-----------+
3 rows in set (0.00 sec)

在 tb_students_info 表中,查询 name 字段值包含字符串“an”或“en”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'an|en';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 | 男   |    160 |         1 |
|  2 | Green |   23 | 男   |    158 |         2 |
|  3 | Henry |   23 | 女   |    185 |         1 |
|  4 | Jane  |   22 | 男   |    162 |         3 |
|  8 | Susan |   23 | 男   |    170 |         5 |
+----+-------+------+------+--------+-----------+
5 rows in set (0.00 sec)

注意:字符串与

|

之间不能有空格。因为,查询过程中,数据库系统会将空格也当作一个字符,这样就查询不出想要的结果。

17.6匹配指定字符串中的任意一个

使用方括号

[ ]

可以将需要查询的字符组成一个字符集合。只要记录中包含方括号中的任意字符,该记录就会被查询出来。例如,通过“[abc]”可以查询包含 a、b 和 c 等 3 个字母中任意一个的记录。

在 tb_students_info 表中,查询 name 字段值包含字母“i”或“o”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[io]';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  5 | Jim    |   24 | 女   |    175 |         2 |
|  6 | John   |   21 | 女   |    172 |         4 |
|  7 | Lily   |   22 | 男   |    165 |         4 |
|  9 | Thomas |   22 | 女   |    178 |         5 |
| 10 | Tom    |   23 | 女   |    165 |         5 |
| 11 | LiMing |   22 | 男   |    180 |         7 |
+----+--------+------+------+--------+-----------+
6 rows in set (0.00 sec)

从查询结果可以看到,所有返回记录的 name 字段值都包含字母 i 或 o,或者两个都有。

方括号

[ ]

还可以指定集合的区间。例如,“[a-z]”表示从 a~z 的所有字母;“[0-9]”表示从 0~9 的所有数字;“[a-z0-9]”表示包含所有的小写字母和数字;“[a-zA-Z]”表示匹配所有字符。

在 tb_students_info 表中,查询 name 字段值中包含 1、2 或 3 的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[123]';
Empty set (0.00 sec)

匹配集合“[123]”也可以写成“[1-3]”,即指定集合区间。

17.7匹配指定字符以外的字符

[^字符集合]

用来匹配不在指定集合中的任何字符。

在 tb_students_info 表中,查询 name 字段值包含字母 a~t 以外的字符的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[^a-t]' ;
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 | 男   |    160 |         1 |
|  3 | Henry |   23 | 女   |    185 |         1 |
|  7 | Lily  |   22 | 男   |    165 |         4 |
|  8 | Susan |   23 | 男   |    170 |         5 |
+----+-------+------+------+--------+-----------+
4 rows in set (0.00 sec)

17.8使用{n,}或者{n,m}来指定字符串连续出现的次数

字符串{n,}

表示字符串连续出现 n 次;

字符串{n,m}

表示字符串连续出现至少 n 次,最多 m 次。

例如,a{2,} 表示字母 a 连续出现至少 2 次,也可以大于 2 次;a{2,4} 表示字母 a 连续出现最少 2 次,最多不能超过 4 次。

在 tb_students_info 表中,查询 name 字段值出现字母‘e’ 至少 2 次的记录,SQL 语句如下:

mysql> SELECT * FROM tb_students_info WHERE name REGEXP 'e{2,}';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  2 | Green |   23 | 男   |    158 |         2 |
+----+-------+------+------+--------+-----------+
1 row in set (0.00 sec)

在 tb_students_info 表中,查询 name 字段值出现字符串“i” 最少 1 次,最多 3 次的记录,SQL 语句如下:

mysql> SELECT * FROM tb_students_info WHERE name REGEXP 'i{1,3}';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  5 | Jim    |   24 | 女   |    175 |         2 |
|  7 | Lily   |   22 | 男   |    165 |         4 |
| 11 | LiMing |   22 | 男   |    180 |         7 |
+----+--------+------+------+--------+-----------+
3 rows in set (0.00 sec)

18.插入数据

INSERT 语句有两种语法形式,分别是 INSERT…VALUES 语句和 INSERT…SET 语句。

18.1 INSERT…VALUES语句

INSERT VALUES 的语法格式为:

INSERT INTO <表名> [ <列名1> [ , … <列名n>] ]
VALUES (值1) [… , (值n) ];

语法说明如下。

  • <表名>:指定被操作的表名。
  • <列名>:指定需要插入数据的列名。若向表中的所有列插入数据,则全部的列名均可以省略,直接采用 INSERT<表名>VALUES(…) 即可。
  • VALUESVALUE 子句:该子句包含要插入的数据清单。数据清单中数据的顺序要和列的顺序相对应。

18.2 INSERT…SET语句

语法格式为:

INSERT INTO <表名>
SET <列名1> = <值1>,
<列名2> = <值2>,

此语句用于直接给表中的某些列指定对应的列值,即要插入的数据的列名在 SET 子句中指定,col_name 为指定的列名,等号后面为指定的数据,而对于未指定的列,列值会指定为该列的默认值。

由 INSERT 语句的两种形式可以看出:

  • 使用 INSERT…VALUES 语句可以向表中插入一行数据,也可以插入多行数据;
  • 使用 INSERT…SET 语句可以指定插入行中每列的值,也可以指定部分列的值;
  • INSERT…SELECT 语句向表中插入其他表的数据。
  • 采用 INSERT…SET 语句可以向表中插入部分列的值,这种方式更为灵活;
  • INSERT…VALUES 语句可以一次插入多条数据。

在 MySQL 中,用单条 INSERT 语句处理多个插入要比使用多条 INSERT 语句更快。

当使用单条 INSERT 语句插入多行数据的时候,只需要将每行数据用圆括号括起来即可。

18.4向表中的全部字段添加值

在 test_db 数据库中创建一个课程信息表 tb_courses,包含课程编号 course_id、课程名称 course_name、课程学分 course_grade 和课程备注 course_info,输入的 SQL 语句和执行结果如下所示。

mysql> CREATE TABLE tb_courses
    -> (
    -> course_id INT NOT NULL AUTO_INCREMENT,
    -> course_name CHAR(40) NOT NULL,
    -> course_grade FLOAT NOT NULL,
    -> course_info CHAR(100) NULL,
    -> PRIMARY KEY(course_id)
    -> );
Query OK, 0 rows affected (0.00 sec)

向表中所有字段插入值的方法有两种:一种是指定所有字段名;另一种是完全不指定字段名。

【实例 1】在 tb_courses 表中插入一条新记录,course_id 值为 1,course_name 值为“Network”,course_grade 值为 3,info 值为“Computer Network”。

在执行插入操作之前,查看 tb_courses 表的SQL语句和执行结果如下所示。

mysql> SELECT * FROM tb_courses;
Empty set (0.00 sec)

查询结果显示当前表内容为空,没有数据,接下来执行插入数据的操作,输入的 SQL 语句和执行过程如下所示。

mysql> INSERT INTO tb_courses
    -> (course_id,course_name,course_grade,course_info)
    -> VALUES(1,'Network',3,'Computer Network');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
+-----------+-------------+--------------+------------------+
1 row in set (0.00 sec)

可以看到插入记录成功。在插入数据时,指定了 tb_courses 表的所有字段,因此将为每一个字段插入新的值。

INSERT 语句后面的列名称顺序可以不是 tb_courses 表定义时的顺序,即插入数据时,不需要按照表定义的顺序插入,只要保证值的顺序与列字段的顺序相同就可以。

【实例 2】在 tb_courses 表中插入一条新记录,course_id 值为 2,course_name 值为“Database”,course_grade 值为 3,info值为“MySQL”。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> (course_name,course_info,course_id,course_grade)
    -> VALUES('Database','MySQL',2,3);
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
+-----------+-------------+--------------+------------------+
2 rows in set (0.00 sec)

使用 INSERT 插入数据时,允许列名称列表 column_list 为空,此时值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。

【实例 3】在 tb_courses 表中插入一条新记录,course_id 值为 3,course_name 值为“Java”,course_grade 值为 4,info 值为“Jave EE”。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> VLAUES(3,'Java',4,'Java EE');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
+-----------+-------------+--------------+------------------+
3 rows in set (0.00 sec)

INSERT 语句中没有指定插入列表,只有一个值列表。在这种情况下,值列表为每一个字段列指定插入的值,并且这些值的顺序必须和 tb_courses 表中字段定义的顺序相同。

注意:虽然使用 INSERT 插入数据时可以忽略插入数据的列名称,若值不包含列名称,则 VALUES 关键字后面的值不仅要求完整,而且顺序必须和表定义时列的顺序相同。如果表的结构被修改,对列进行增加、删除或者位置改变操作,这些操作将使得用这种方式插入数据时的顺序也同时改变。如果指定列名称,就不会受到表结构改变的影响。

18.5向表中指定字段添加值

为表的指定字段插入数据,是在 INSERT 语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。

【实例 4】在 tb_courses 表中插入一条新记录,course_name 值为“System”,course_grade 值为 3,course_info 值为“Operating System”,输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> (course_name,course_grade,course_info)
    -> VALUES('System',3,'Operation System');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            3 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

可以看到插入记录成功。如查询结果显示,这里的 course_id 字段自动添加了一个整数值 4。这时的 course_id 字段为表的主键,不能为空,系统自动为该字段插入自增的序列值。在插入记录时,如果某些字段没有指定插入值,MySQL 将插入该字段定义时的默认值。

18.6使用 INSERT INTO…FROM 语句复制表数据

INSERT INTO…SELECT…FROM 语句用于快速地从一个或多个表中取出数据,并将这些数据作为行数据插入另一个表中。

SELECT 子句返回的是一个查询到的结果集,INSERT 语句将这个结果集插入指定表中,结果集中的每行数据的字段数、字段的数据类型都必须与被操作的表完全一致。

在数据库 test_db 中创建一个与 tb_courses 表结构相同的数据表 tb_courses_new,创建表的 SQL 语句和执行过程如下所示。

mysql> CREATE TABLE tb_courses_new
    -> (
    -> course_id INT NOT NULL AUTO_INCREMENT,
    -> course_name CHAR(40) NOT NULL,
    -> course_grade FLOAT NOT NULL,
    -> course_info CHAR(100) NULL,
    -> PRIMARY KEY(course_id)
    -> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM tb_courses_new;
Empty set (0.00 sec)

【实例 5】从 tb_courses 表中查询所有的记录,并将其插入 tb_courses_new 表中。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses_new
    -> (course_id,course_name,course_grade,course_info)
    -> SELECT course_id,course_name,course_grade,course_info
    -> FROM tb_courses;
Query OK, 4 rows affected (0.17 sec)
Records: 4  Duplicates: 0  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            3 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

19.修改数据

使用 UPDATE 语句修改单个表,语法格式为:

UPDATE <表名> SET 字段 1=值 1 [,字段 2=值 2… ] [WHERE 子句 ]
[ORDER BY 子句] [LIMIT 子句]

语法说明如下:

  • <表名>:用于指定要更新的表名称。
  • SET 子句:用于指定表中要修改的列名及其列值。其中,每个指定的列值可以是表达式,也可以是该列对应的默认值。如果指定的是默认值,可用关键字 DEFAULT 表示列值。
  • WHERE 子句:可选项。用于限定表中要修改的行。若不指定,则修改表中所有的行。
  • ORDER BY 子句:可选项。用于限定表中的行被修改的次序。
  • LIMIT 子句:可选项。用于限定被修改的行数。

注意:修改一行数据的多个列值时,SET 子句的每个值用逗号分开即可。

19.1修改表中的数据

【实例 1】在 tb_courses_new 表中,更新所有行的 course_grade 字段值为 4,输入的 SQL 语句和执行结果如下所示。

mysql> UPDATE tb_courses_new
    -> SET course_grade=4;
Query OK, 3 rows affected (0.11 sec)
Rows matched: 4  Changed: 3  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            4 | Computer Network |
|         2 | Database    |            4 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            4 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

19.2根据条件修改表中的数据

【实例 2】在 tb_courses 表中,更新 course_id 值为 2 的记录,将 course_grade 字段值改为 3.5,将 course_name 字段值改为“DB”,输入的 SQL 语句和执行结果如下所示。

mysql> UPDATE tb_courses_new
    -> SET course_name='DB',course_grade=3.5
    -> WHERE course_id=2;
Query OK, 1 row affected (0.13 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            4 | Computer Network |
|         2 | DB          |          3.5 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            4 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

注意:保证 UPDATE 以 WHERE 子句结束,通过 WHERE 子句指定被更新的记录所需要满足的条件,如果忽略 WHERE 子句,MySQL 将更新表中所有的行。

20.删除数据

使用 DELETE 语句从单个表中删除数据,语法格式为:

DELETE FROM <表名> [WHERE 子句] [ORDER BY 子句] [LIMIT 子句]

语法说明如下:

  • <表名>:指定要删除数据的表名。
  • ORDER BY 子句:可选项。表示删除时,表中各行将按照子句中指定的顺序进行删除。
  • WHERE 子句:可选项。表示为删除操作限定删除条件,若省略该子句,则代表删除该表中的所有行。
  • LIMIT 子句:可选项。用于告知服务器在控制命令被返回到客户端前被删除行的最大值。

注意:在不使用 WHERE 条件的时候,将删除所有数据。

20.1删除表中的全部数据

【实例 1】删除 tb_courses_new 表中的全部数据,输入的 SQL 语句和执行结果如下所示。

mysql> DELETE FROM tb_courses_new;
Query OK, 3 rows affected (0.12 sec)
mysql> SELECT * FROM tb_courses_new;
Empty set (0.00 sec)

20.2根据条件删除表中的数据

【实例 2】在 tb_courses_new 表中,删除 course_id 为 4 的记录,输入的 SQL 语句和执行结果如下所示。

mysql> DELETE FROM tb_courses
    -> WHERE course_id=4;
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
+-----------+-------------+--------------+------------------+
3 rows in set (0.00 sec)

由运行结果可以看出,course_id 为 4 的记录已经被删除。

21.清空表记录

TRUNCATE

关键字用于完全清空一个表。其语法格式如下:

TRUNCATE [TABLE] 表名

其中,TABLE 关键字可省略。

新建表 tb_student_course,插入数据并查询,SQL 语句和运行结果如下:

mysql> CREATE TABLE `tb_student_course` (
    -> `id` int(4) NOT NULL AUTO_INCREMENT,
    -> `name` varchar(25) NOT NULL,
    -> PRIMARY KEY (`id`)
    -> );
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO tb_student_course(name) VALUES ('Java'),('MySQL'),('Python');
Query OK, 3 rows affected (0.05 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM tb_student_course;
+----+--------+
| id | name   |
+----+--------+
|  1 | Java   |
|  2 | MySQL  |
|  3 | Python |
+----+--------+
3 rows in set (0.00 sec)

使用 TRUNCATE 语句清空 tb_student_course 表中的记录,SQL 语句和运行结果如下:

mysql> TRUNCATE TABLE tb_student_course;
Query OK, 0 rows affected (0.04 sec)

mysql> SELECT * FROM tb_student_course;
Empty set (0.00 sec)

21.1TRUNCATE 和 DELETE 的区别

从逻辑上说,TRUNCATE 语句与 DELETE 语句作用相同,但是在某些情况下,两者在使用上有所区别。

  • DELETE 是 DML 类型的语句;TRUNCATE 是 DDL 类型的语句。它们都用来清空表中的数据。
  • DELETE 是逐行一条一条删除记录的;TRUNCATE 则是直接删除原来的表,再重新创建一个一模一样的新表,而不是逐行删除表中的数据,执行数据比 DELETE 快。因此需要删除表中全部的数据行时,尽量使用 TRUNCATE 语句, 可以缩短执行时间。
  • DELETE 删除数据后,配合事件回滚可以找回数据;TRUNCATE 不支持事务的回滚,数据删除后无法找回。
  • DELETE 删除数据后,系统不会重新设置自增字段的计数器;TRUNCATE 清空表记录后,系统会重新设置自增字段的计数器。
  • DELETE 的使用范围更广,因为它可以通过 WHERE 子句指定条件来删除部分数据;而 TRUNCATE 不支持 WHERE 子句,只能删除整体。
  • DELETE 会返回删除数据的行数,但是 TRUNCATE 只会返回 0,没有任何意义。

21.2总结

当不需要该表时,用 DROP;当仍要保留该表,但要删除所有记录时,用 TRUNCATE;当要删除部分记录时,用 DELETE。

十、索引

1.mysql索引

MySql官方对索引的定义为:索引是帮助MySql高效获取数据的数据结构。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

个人对于MySql索引的理解:在数据之外,数据库系统还维护着满足特定查找算法的数据结构,包括B+树或者Hash表。由于存储引擎表示的是数据在磁盘上面的不同的组织形式,所以索引底层采用哪种数据结构是跟数据库的存储引擎相关的。如果是MyIsam或者是InnoDB存储引擎,那么对应的底层的数据结构为B+树,如果是Memory存储引擎,那么对应的底层的数据结构为Hash表。采用B+树的最根本的原因是由于二叉树的树太高,树太高则直接影响到磁盘IO的次数,影响数据查询的效率,采用B+树的数据结构,可以在某个数据节点里面尽可能多的存储数据,使树的高度尽量的变低,提高效率。日常开发过程中,遇到的比较多的可能就是聚簇索引和联合索引,里面又涉及到了覆盖索引,最左匹配,回表,索引下推等各方面的知识点,在编写SQL语句的时候,我们就可以利用这些点来进行优化,提高数据的查询效率。

1.1为什么要使用索引

索引就是根据表中的一列或若干列按照一定顺序建立的列值与记录行之间的对应关系表,实质上是一张描述索引列的列值与原表中记录行之间一 一对应关系的有序表。

索引是 MySQL 中十分重要的数据库对象,是数据库性能调优技术的基础,常用于实现数据的快速检索。

在 MySQL 中,通常有以下两种方式访问数据库表的行数据:

1) 顺序访问

顺序访问是在表中实行全表扫描,从头到尾逐行遍历,直到在无序的行数据中找到符合条件的目标数据。

顺序访问实现比较简单,但是当表中有大量数据的时候,效率非常低下。例如,在几千万条数据中查找少量的数据时,使用顺序访问方式将会遍历所有的数据,花费大量的时间,显然会影响数据库的处理性能。

2) 索引访问

索引访问是通过遍历索引来直接访问表中记录行的方式。

使用这种方式的前提是对表建立一个索引,在列上创建了索引之后,查找数据时可以直接根据该列上的索引找到对应记录行的位置,从而快捷地查找到数据。索引存储了指定列数据值的指针,根据指定的排序顺序对这些指针排序。

例如,在学生基本信息表 tb_students 中,如果基于 student_id 建立了索引,系统就建立了一张索引列到实际记录的映射表。当用户需要查找 student_id 为 12022 的数据的时候,系统先在 student_id 索引上找到该记录,然后通过映射表直接找到数据行,并且返回该行数据。因为扫描索引的速度一般远远大于扫描实际数据行的速度,所以采用索引的方式可以大大提高数据库的工作效率。

简而言之,不使用索引,MySQL 就必须从第一条记录开始读完整个表,直到找出相关的行。表越大,查询数据所花费的时间就越多。如果表中查询的列有一个索引,MySQL 就能快速到达一个位置去搜索数据文件,而不必查看所有数据,这样将会节省很大一部分时间。

1.2索引的优缺点

索引有其明显的优势,也有其不可避免的缺点。

索引的优点如下:

  • 可以大大加快数据的查询速度,这是使用索引最主要的原因。
  • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
    • 被索引的列会自动进行排序,包括【单列索引】和【组合索引】,只是组合索引的排序要复杂一些。
    • 如果按照索引列的顺序进行排序,对应order by语句来说,效率就会提高很多。

增加索引也有许多不利的方面,主要如下:

  • 索引会占据磁盘空间
  • 索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还有保存或者更新对应的索引文件。

使用索引时,需要综合考虑索引的优点和缺点。索引可以提高查询速度,但是会影响插入记录的速度。因为,向有索引的表中插入记录时,数据库系统会按照索引进行排序,这样就降低了插入记录的速度,插入大量记录时的速度影响会更加明显。这种情况下,最好的办法是先删除表中的索引,然后插入数据,插入完成后,再创建索引。

2索引类型

2.1主键索引(聚簇索引)

索引列中的值必须是唯一的,不允许有空值。

2.2普通索引

MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值。

2.3唯一索引

索引列中的值必须是唯一的,但是允许为空值。

2.4全文索引

只能在文本类型CHAR,VARCHAR,TEXT类型字段上创建全文索引。字段长度比较大时,如果创建普通索引,在进行like模糊查询时效率比较低,这时可以创建全文索引。 MyISAM和InnoDB中都可以使用全文索引。

空间索引
MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。

前缀索引
在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。

其他(按照索引列数量分类)
单列索引

2.5联合索引

组合索引的使用,需要遵循最左前缀匹配原则(最左匹配原则)。一般情况下在条件允许的情况下使用组合索引替代多个单列索引使用。

3.mysql索引实现

这里分析Mysql的两种存储引擎的索引实现:MyISAM索引InnoDB索引

MyIsam索引

以一个简单的user表为例。user表存在两个索引,id列为主键索引,age列为普通索引

MyISAM的数据文件和索引文件是分开存储的。MyISAM使用B+树构建索引树时,叶子节点中存储的键值为索引列的值,数据为索引所在行的磁盘地址

InnoDB索引

每个InnoDB表都有一个聚簇索引 ,聚簇索引使用B+树构建,叶子节点存储的数据是整行记录。一般情况下,聚簇索引等同于主键索引,当一个表没有创建主键索引时,InnoDB会自动创建一个ROWID字段来构建聚簇索引。InnoDB创建索引的具体规则如下:

在表上定义主键PRIMARY KEY,InnoDB将主键索引用作聚簇索引。
如果表没有定义主键,InnoDB会选择第一个不为NULL的唯一索引列用作聚簇索引。
如果以上两个都没有,InnoDB 会使用一个6 字节长整型的隐式字段 ROWID字段构建聚簇索引。该ROWID字段会在插入新行时自动递增。

除聚簇索引之外的所有索引都称为辅助索引。在中InnoDB,辅助索引中的叶子节点存储的数据是该行的主键值。 在检索时,InnoDB使用此主键值在聚簇索引中搜索行记录。

2.创建索引

创建索引是指在某个表的一列或多列上建立一个索引,可以提高对表的访问速度。创建索引对 MySQL数据库的高效运行来说是很重要的。

2.1基本语法

MySQL 提供了三种创建索引的方法:

1) 使用 CREATE INDEX 语句

可以使用专门用于创建索引的 CREATE INDEX 语句在一个已有的表上创建索引,但该语句不能创建主键。

语法格式:

CREATE <索引名> ON <表名> (<列名> [<长度>] [ ASC | DESC])

语法说明如下:

  • <索引名>:指定索引名。一个表可以创建多个索引,但每个索引在该表中的名称是唯一的。
  • <表名>:指定要创建索引的表名。
  • <列名>:指定要创建索引的列名。通常可以考虑将查询语句中在 JOIN 子句和 WHERE 子句里经常出现的列作为索引列。
  • <长度>:可选项。指定使用列前的 length 个字符来创建索引。使用列的一部分创建索引有利于减小索引文件的大小,节省索引列所占的空间。在某些情况下,只能对列的前缀进行索引。索引列的长度有一个最大上限 255 个字节(MyISAM 和 InnoDB 表的最大上限为 1000 个字节),如果索引列的长度超过了这个上限,就只能用列的前缀进行索引。另外,BLOB 或 TEXT 类型的列也必须使用前缀索引。
  • ASC|DESC:可选项。ASC指定索引按照升序来排列,DESC指定索引按照降序来排列,默认为ASC
2) 使用 CREATE TABLE 语句

索引也可以在创建表(CREATE TABLE)的同时创建。在 CREATE TABLE 语句中添加以下语句。语法格式:

CONSTRAINT PRIMARY KEY [索引类型] (<列名>,…)

在 CREATE TABLE 语句中添加此语句,表示在创建新表的同时创建该表的主键。

语法格式:

KEY | INDEX [<索引名>] [<索引类型>] (<列名>,…)

在 CREATE TABLE 语句中添加此语句,表示在创建新表的同时创建该表的索引。

语法格式:

UNIQUE [ INDEX | KEY] [<索引名>] [<索引类型>] (<列名>,…)

在 CREATE TABLE 语句中添加此语句,表示在创建新表的同时创建该表的唯一性索引。

语法格式:

FOREIGN KEY <索引名> <列名>

在 CREATE TABLE 语句中添加此语句,表示在创建新表的同时创建该表的外键。

在使用 CREATE TABLE 语句定义列选项的时候,可以通过直接在某个列定义后面添加 PRIMARY KEY 的方式创建主键。而当主键是由多个列组成的多列索引时,则不能使用这种方法,只能用在语句的最后加上一个 PRIMARY KRY(<列名>,…) 子句的方式来实现。

3) 使用 ALTER TABLE 语句

CREATE INDEX 语句可以在一个已有的表上创建索引,ALTER TABLE 语句也可以在一个已有的表上创建索引。在使用 ALTER TABLE 语句修改表的同时,可以向已有的表添加索引。具体的做法是在 ALTER TABLE 语句中添加以下语法成分的某一项或几项。

语法格式:

ADD INDEX [<索引名>] [<索引类型>] (<列名>,…)

在 ALTER TABLE 语句中添加此语法成分,表示在修改表的同时为该表添加索引。

语法格式:

ADD PRIMARY KEY [<索引类型>] (<列名>,…)

在 ALTER TABLE 语句中添加此语法成分,表示在修改表的同时为该表添加主键。

语法格式:

ADD UNIQUE [ INDEX | KEY] [<索引名>] [<索引类型>] (<列名>,…)

在 ALTER TABLE 语句中添加此语法成分,表示在修改表的同时为该表添加唯一性索引。

语法格式:

ADD FOREIGN KEY [<索引名>] (<列名>,…)

在 ALTER TABLE 语句中添加此语法成分,表示在修改表的同时为该表添加外键。

2.2创建普通索引

创建普通索引时,通常使用 INDEX 关键字。

创建一个表 tb_stu_info,在该表的 height 字段创建普通索引。输入的 SQL 语句和执行过程如下所示。

mysql> CREATE TABLE tb_stu_info
    -> (
    -> id INT NOT NULL,
    -> name CHAR(45) DEFAULT NULL,
    -> dept_id INT DEFAULT NULL,
    -> age INT DEFAULT NULL,
    -> height INT DEFAULT NULL,
    -> INDEX(height)
    -> );
Query OK,0 rows affected (0.40 sec)
mysql> SHOW CREATE TABLE tb_stu_info\G
*************************** 1. row ***************************
       Table: tb_stu_info
Create Table: CREATE TABLE `tb_stu_info` (
  `id` int(11) NOT NULL,
  `name` char(45) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `height` int(11) DEFAULT NULL,
  KEY `height` (`height`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.01 sec)

2.3创建唯一索引

创建唯一索引,通常使用 UNIQUE 参数。

创建一个表 tb_stu_info2,在该表的 id 字段上使用 UNIQUE 关键字创建唯一索引。输入的 SQL 语句和执行过程如下所示。

mysql> CREATE TABLE tb_stu_info2
    -> (
    -> id INT NOT NULL,
    -> name CHAR(45) DEFAULT NULL,
    -> dept_id INT DEFAULT NULL,
    -> age INT DEFAULT NULL,
    -> height INT DEFAULT NULL,
    -> UNIQUE INDEX(height)
    -> );
Query OK,0 rows affected (0.40 sec)
mysql> SHOW CREATE TABLE tb_stu_info2\G
*************************** 1. row ***************************
       Table: tb_stu_info2
Create Table: CREATE TABLE `tb_stu_info2` (
  `id` int(11) NOT NULL,
  `name` char(45) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `height` int(11) DEFAULT NULL,
  UNIQUE KEY `height` (`height`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.00 sec)

2.4查看索引

索引创建完成后,可以利用 SQL 语句查看已经存在的索引。在 MySQL 中,可以使用 SHOW INDEX 语句查看表中创建的索引。

查看索引的语法格式如下:

SHOW INDEX FROM <表名> [ FROM <数据库名>]

语法说明如下:

  • <表名>:指定需要查看索引的数据表名。
  • <数据库名>:指定需要查看索引的数据表所在的数据库,可省略。比如,SHOW INDEX FROM student FROM test; 语句表示查看 test 数据库中 student 数据表的索引。
mysql> SHOW INDEX FROM tb_stu_info2\G
*************************** 1. row ***************************
        Table: tb_stu_info2
   Non_unique: 0
     Key_name: height
 Seq_in_index: 1
  Column_name: height
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment:
Index_comment:
1 row in set (0.03 sec)

其中各主要参数说明如下:

参数说明
Table表示创建索引的数据表名,这里是 tb_stu_info2 数据表。
Non_unique表示该索引是否是唯一索引。若不是唯一索引,则该列的值为 1;若是唯一索引,则该列的值为 0。
Key_name表示索引的名称。
Seq_in_index表示该列在索引中的位置,如果索引是单列的,则该列的值为 1;如果索引是组合索引,则该列的值为每列在索引定义中的顺序。
Column_name表示定义索引的列字段。
Collation表示列以何种顺序存储在索引中。在 MySQL 中,升序显示值“A”(升序),若显示为 NULL,则表示无分类。
Cardinality索引中唯一值数目的估计值。基数根据被存储为整数的统计数据计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL 使用该索引的机会就越大。
Sub_part表示列中被编入索引的字符的数量。若列只是部分被编入索引,则该列的值为被编入索引的字符的数目;若整列被编入索引,则该列的值为 NULL。
Packed指示关键字如何被压缩。若没有被压缩,值为 NULL。
Null用于显示索引列中是否包含 NULL。若列含有 NULL,该列的值为 YES。若没有,则该列的值为 NO。
Index_type显示索引使用的类型和方法(BTREE、FULLTEXT、HASH、RTREE)。
Comment显示评注。

2.5修改、删除索引

删除索引是指将表中已经存在的索引删除掉。不用的索引建议进行删除,因为它们会降低表的更新速度,影响数据库的性能。对于这样的索引,应该将其删除。

在 MySQL中修改索引可以通过删除原索引,再根据需要创建一个同名的索引,从而实现修改索引的操作。

基本语法

当不再需要索引时,可以使用 DROP INDEX 语句或 ALTER TABLE 语句来对索引进行删除。

1) 使用 DROP INDEX 语句

语法格式:

DROP INDEX <索引名> ON <表名>

语法说明如下:

  • <索引名>:要删除的索引名。
  • <表名>:指定该索引所在的表名。
2) 使用 ALTER TABLE 语句

根据 ALTER TABLE 语句的语法可知,该语句也可以用于删除索引。具体使用方法是将 ALTER TABLE 语句的语法中部分指定为以下子句中的某一项。

  • DROP PRIMARY KEY:表示删除表中的主键。一个表只有一个主键,主键也是一个索引。
  • DROP INDEX index_name:表示删除名称为 index_name 的索引。
  • DROP FOREIGN KEY fk_symbol:表示删除外键。

注意:如果删除的列是索引的组成部分,那么在删除该列时,也会将该列从索引中删除;如果组成索引的所有列都被删除,那么整个索引将被删除。

删除索引

【实例 1】删除表 tb_stu_info 中的索引,输入的 SQL 语句和执行结果如下所示。

mysql> DROP INDEX height
    -> ON tb_stu_info;
Query OK, 0 rows affected (0.27 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> SHOW CREATE TABLE tb_stu_info\G
*************************** 1. row ***************************
       Table: tb_stu_info
Create Table: CREATE TABLE `tb_stu_info` (
  `id` int(11) NOT NULL,
  `name` char(45) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `height` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.00 sec)

【实例 2】删除表 tb_stu_info2 中名称为 id 的索引,输入的 SQL 语句和执行结果如下所示。

mysql> ALTER TABLE tb_stu_info2
    -> DROP INDEX height;
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> SHOW CREATE TABLE tb_stu_info2\G
*************************** 1. row ***************************
       Table: tb_stu_info2
Create Table: CREATE TABLE `tb_stu_info2` (
  `id` int(11) NOT NULL,
  `name` char(45) DEFAULT NULL,
  `dept_id` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `height` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gb2312
1 row in set (0.00 sec)

3.索引的数据结构

3.1hash表

哈希索引是一种基于哈希表的索引结构,它是一种需要精确匹配才生效的索引结构。

实现原理:对索引列计算哈希值把记录映射到哈希槽中,然后指向对应记录行的地址。因此,在查询的时候只要正确匹配到索引列,就能在O(1)的时间复杂度内查到记录。以下是一个哈希索引的示例,左边是哈希槽,右边是对应的数据列:

在这里插入图片描述

相比于B-Tree索引而言,哈希索引有不少的局限性:

  • 哈希索引不支持排序
  • 哈希索引不支持部分列索引查找
  • 哈希索引只支持等值查询,无法提供范围查询功能

哈希索引的查找效率是非常高的,大多数时候都能在O(1)的时间内找到记录,除非哈希冲突很高。

3.2二叉查找树

二叉树,我想大家都会在心里有个图。

在这里插入图片描述

二叉树特点:每个节点最多有2个分叉,左子树和右子树数据顺序左小右大。

这个特点就是为了保证每次查找都可以这折半而减少IO次数,但是二叉树就很考验第一个根节点的取值,因为很容易在这个特点下出现“树不分叉”的情况。

3.3平衡二叉树

平衡二叉树是采用二分法思维,平衡二叉查找树除了具备二叉树的特点,最主要的特征是树的左右两个子树的层级最多相差1。在插入删除数据时通过左旋/右旋操作保持二叉树的平衡,不会出现左子树很高、右子树很矮的情况。

在这里插入图片描述

使用平衡二叉查找树查询的性能接近于二分查找法,时间复杂度是 O(log2n)。查询id=6,只需要两次IO。

时间复杂度和树高相关。树有多高就需要检索多少次,每个节点的读取,都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。磁盘每次寻道时间为10ms,在表数据量大时,查询性能就会很差。(1百万的数据量,log2n约等于20次磁盘IO,时间20*10=0.2s)

平衡二叉树不支持范围查询快速查找,范围查询时需要从根节点多次遍历,查询效率不高。

3.4B树:改造二叉树

MySQL的数据是存储在磁盘文件中的,查询处理数据时,需要先把磁盘中的数据加载到内存中,磁盘IO 操作非常耗时,所以我们优化的重点就是尽量减少磁盘 IO 操作。访问二叉树的每个节点就会发生一次IO,如果想要减少磁盘IO操作,就需要尽量降低树的高度。那如何降低树的高度呢?

假如key为bigint=8字节,每个节点有两个指针,每个指针为4个字节,一个节点占用的空间16个字节(8+4*2=16)。

因为在MySQL的InnoDB存储引擎一次IO会读取的一页(默认一页16K)的数据量,而二叉树一次IO有效数据量只有16字节,空间利用率极低。为了最大化利用一次IO空间,一个简单的想法是在每个节点存储多个元素,在每个节点尽可能多的存储数据。每个节点可以存储1000个索引(16k/16=1000),这样就将二叉树改造成了多叉树,通过增加树的叉树,将树从高瘦变为矮胖。构建1百万条数据,树的高度只需要2层就可以(1000*1000=1百万),也就是说只需要2次磁盘IO就可以查询到数据。磁盘IO次数变少了,查询数据的效率也就提高了。

这种数据结构我们称为B树,B树是一种多叉平衡查找树,如下图主要特点:

B树的节点中存储着多个元素,每个内节点有多个分叉。

节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在所有的节点都储存数据。

父节点当中的元素不会出现在子节点中。

所有的叶子结点都位于同一层,叶节点具有相同的深度,叶节点之间没有指针连接。

在这里插入图片描述

假如我们查询值等于10的数据。查询路径磁盘块1->磁盘块2->磁盘块5。

第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,10<15,走左路,到磁盘寻址磁盘块2。

第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<10,到磁盘中寻址定位到磁盘块5。

第三次磁盘IO:将磁盘块5加载到内存中,在内存中从头遍历比较,10=10,找到10,取出data,如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,查询终止。

相比二叉平衡查找树,在整个查找过程中,虽然数据的比较次数并没有明显减少,但是磁盘IO次数会大大减少。同时,由于我们的比较是在内存中进行的,比较的耗时可以忽略不计。B树的高度一般2至3层就能满足大部分的应用场景,所以使用B树构建索引可以很好的提升查询的效率。

3.5B+树:改造B树

B+树,作为B树的升级版,在B树基础上,MySQL在B树的基础上继续改造,使用B+树构建索引。B+树和B树最主要的区别在于非叶子节点是否存储数据的问题

B树:非叶子节点和叶子节点都会存储数据。
B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
在这里插入图片描述

B+树的最底层叶子节点包含了所有的索引项。从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,但是从另一方面来说,由于数据都被放到了叶子节点,所以放索引的磁盘块锁存放的索引数量是会跟这增加的,所以相对于B树来说,B+树的树高理论上情况下是比B树要矮的。也存在索引覆盖查询的情况,在索引中数据满足了当前查询语句所需要的全部数据,此时只需要找到索引即可立刻返回,不需要检索到最底层的叶子节点。

等值查询:
假如我们查询值等于9的数据。查询路径磁盘块1->磁盘块2->磁盘块6。

第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,9<15,走左路,到磁盘寻址磁盘块2。

第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<9<12,到磁盘中寻址定位到磁盘块6。

第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,在第三个索引中找到9,取出data,如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,查询终止。(这里需要区分的是在InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址。)

过程如图:
在这里插入图片描述

范围查询:

假如我们想要查找9和26之间的数据。查找路径是磁盘块1->磁盘块2->磁盘块6->磁盘块7。

首先查找值等于9的数据,将值等于9的数据缓存到结果集。这一步和前面等值查询流程一样,发生了三次磁盘IO。

查找到15之后,底层的叶子节点是一个有序列表,我们从磁盘块6,键值9开始向后遍历筛选所有符合筛选条件的数据。

第四次磁盘IO:根据磁盘6后继指针到磁盘中寻址定位到磁盘块7,将磁盘7加载到内存中,在内存中从头遍历比较,9<25<26,9<26<=26,将data缓存到结果集。

主键具备唯一性(后面不会有<=26的数据),不需再向后查找,查询终止。将结果集返回给用户。
在这里插入图片描述

可以看到B+树可以保证等值和范围查询的快速查找,MySQL的索引就采用了B+树的数据结构。

3.6个人总结

hash索引是基于hash表的索引结构,B+索引是基于B+树的索引结构,相比于B+索引,hash索引不支持order by排序、模糊查询、范围查询和部分联合查询;

hash索引等值查询速率非常快,但是当索引列重复值比较多的时候,可能会遇到hash冲突,查询效率就会降低,所以hash索引一般不会建立在重复值比较多的列上。

B+树是B树的升级,B+树只在叶子节点存储记录数据,非叶子节点只存储键值,所以,理论上,存储相同的数据量时,B+树要比B树要矮,查询所需数据时磁盘IO次数要少,查询速率更快。

4.覆盖索引

联合索引的查询过程:

在这里插入图片描述
在这里插入图片描述

最左匹配原则:

最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。

在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,但是b列和c列是无序的,b列只有在a列值相等的情况下小范围内递增有序,而c列只能在a,b两列相等的情况下小范围内递增有序。

就像上面的查询,B+树会先比较a列来确定下一步应该搜索的方向,往左还是往右。如果a列相同再比较b列。但是如果查询条件没有a列,B+树就不知道第一步应该从哪个节点查起。

可以说创建的idx_abc(a,b,c)索引,相当于创建了(a)、(a,b)(a,b,c)三个索引。、

组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就停止匹配。

覆盖索引

覆盖索引并不是说是索引结构,覆盖索引是一种很常用的优化手段。因为在使用辅助索引的时候,我们只可以拿到主键值,相当于获取数据还需要再根据主键查询主键索引再获取到数据。但是试想下这么一种情况,在上面abc_innodb表中的组合索引查询时,如果我只需要abc字段的,那是不是意味着我们查询到组合索引的叶子节点就可以直接返回了,而不需要回表。这种情况就是覆盖索引。

在这里插入图片描述

看到这里,你是不是对于自己的sql语句里面的索引的有了更多优化想法呢。比如:

避免回表
在InnoDB的存储引擎中,使用辅助索引查询的时候,因为辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引如果需要获取当前记录完整数据就必然需要根据主键值从主键索引继续查询。这个过程我们成位回表。想想回表必然是会消耗性能影响性能。那如何避免呢?

使用索引覆盖,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)

如果在一个场景下,select id,name,sex from user where name =‘zhangsan’;这个语句在业务上频繁使用到,而user表的其他字段使用频率远低于它,在这种情况下,如果我们在建立 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是不是根据辅助索引查询到的结果就可以获取当前语句的完整数据。这样就可以有效地避免了回表再获取sex的数据。

这里就是一个典型的使用覆盖索引的优化策略减少回表的情况。

联合索引的使用
联合索引,在建立索引的时候,尽量在多个单列索引上判断下是否可以使用联合索引。联合索引的使用不仅可以节省空间,还可以更容易的使用到索引覆盖。试想一下,索引的字段越多,是不是更容易满足查询需要返回的数据呢。比如联合索引(a_b_c),是不是等于有了索引:a,a_b,a_b_c三个索引,这样是不是节省了空间,当然节省的空间并不是三倍于(a,a_b,a_b_c)三个索引,因为索引树的数据没变,但是索引data字段的数据确实真实的节省了。

联合索引的创建原则,在创建联合索引的时候因该把频繁使用的列、区分度高的列放在前面,频繁使用代表索引利用率高,区分度高代表筛选粒度大,这些都是在索引创建的需要考虑到的优化场景,也可以在常需要作为查询返回的字段上增加到联合索引中,如果在联合索引上增加一个字段而使用到了覆盖索引,那我建议这种情况下使用联合索引。

联合索引的使用

考虑当前是否已经存在多个可以合并的单列索引,如果有,那么将当前多个单列索引创建为一个联合索引。
当前索引存在频繁使用作为返回字段的列,这个时候就可以考虑当前列是否可以加入到当前已经存在索引上,使其查询语句可以使用到覆盖索引。

4.1个人总结

覆盖索引是一种索引查询优化策略,即尽量使联合索引中就已经包含要经常查询的完整数据,而不需要通过主键索引二次回表获取所需的数据,从而提高查询速率。

5.索引下推

  • 索引条件下推(Index Condition Pushdown),简称ICP。MySQL5.6新添加,用于优化数据的查询。

5.1理论

索引下推简单来说就是把索引当中的所有数据全部用到减少回表的次数

假如现在我们表中有a b c三个字段的联合索引,那么该索引再b+tree中的存储如下

在这里插入图片描述

此时根据最佳做匹配原则

where a = 1a and b = 1b and c = 1c; 会完全用到索引

where a = 1a and c = 1c; 此时会用到a索引,因为b索引断了,索引c索引也会浪费

那么如果没有索引下推,此时mysql会获取到 (1a + 1b + 1c)(1a + 2b + 2c)两个索引
在这里插入图片描述

此时会进行回表获取id=1 和 id = 2 的数据,然后判断c 是否等于1c

最终结果返回id=1的这条数据

但是有了索引下推之后

mysql会获取到 (1a + 1b + 1c)(1a + 2b + 2c)两个索引

接着会判断c 是否等于1c因为c也在索引结构中,此时只会留下(1a + 1b + 1c)这一个索引
在这里插入图片描述

这样就减少了回表次数,提升了查询效率

关闭索引下推,默认是开启

set optimizer_switch=‘index_condition_pushdown=off’;

5.2个人总结

索引下推就是把原来数据库serve层做数据筛选的工作交给存储引擎来完成,从而减少可能的回表次数,提高查询效率。

6.使用explain分析你SQL执行计划

SQL很灵活,一个需求可以很多实现,那哪个最优呢?SQL提供了explain关键字,它可以分析你的SQL执行计划,看它是否最佳。Explain主要看SQL是否使用了索引。

在这里插入图片描述

type:

ALL 全表扫描,没有优化,最慢的方式
index 索引全扫描
range 索引范围扫描,常用语<,<=,>=,between等操作
ref 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中
eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询
const 当查询是对主键或者唯一键进行精确查询,系统会把匹配行中的其他列作为常数处理
null MySQL不访问任何表或索引,直接返回结果
System 表只有一条记录(实际中基本不存在这个情况)

7.索引失效

7.0如果where条件中有 or,即使其中有条件带索引也不会使用,换言之,就是要求使用的所有字段都必须建立索引,建议大家尽量避免使用关键字 or.

如果MySQL估计使用全表扫描要比使用索引块,则不使用索引

7.1 索引列包含NULL值

只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。

7.2 使用短索引

对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

7.3 索引列排序

MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么 order by 中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。

7.4 like语句操作

一般情况下不鼓励使用like操作,如果非用不可,注意正确的使用方式。like '%aaa%'不会使用索引,而 like ‘aaa%’ 可以使用索引.

7.5 不要在索引列上进行计算

7.6 不使用 NOT IN<>!=操作,但是<<==>>=BETWEENIN是可以用到索引的

7.7 索引要建立在经常进行select操作的字段上

这是因为,如果这些列很少用到的话,那么有无索引并不能明显改变查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

7.8 索引要建立在值比较唯一的字段上

7.9 对于那些定义为textimagebit数据类型的别不应该增加索引。因为这些列的数据要么相当大,要么取值很少

7.10 在 wherejoin 中出现的列需要建立索引

7.11 where的查询条件里有不等号(where column !=…),MySQL将无法使用索引

7.12 如果where子句的查询条件里使用了函数(如:where DAY(column)=…),MySQL将无法使用索引

7.13 在join操作中(需要从多个数据表提取数据时),MySQL只有主键和外检的数据类型相同时才使用索引,否则即使建立了索引也不会使用

7.1个人总结

在经常访问的、重复值少的列上使用索引,查询时where子句尽量不要包含or关键字、比较运算符、is null、表达式、函数,模糊查询不要以%开头,字符串要加 ’ ’ …,避免索引失效。

十一、存储过程和触发器

存储过程就是实现某种功能的sql语句的集合,可以被调用。

1.创建存储过程

可以使用

CREATE PROCEDURE

语句创建存储过程,语法格式如下:

CREATE PROCEDURE <过程名> ( [过程参数[,…] ] ) <过程体>
[过程参数[,…] ] 格式
[ IN | OUT | INOUT ] <参数名> <类型>

语法说明如下:

1) 过程名

存储过程的名称,默认在当前数据库中创建。若需要在特定数据库中创建存储过程,则要在名称前面加上数据库的名称,即 db_name.sp_name。

需要注意的是,名称应当尽量避免选取与 MySQL 内置函数相同的名称,否则会发生错误。

2) 过程参数

存储过程的参数列表。其中,

<参数名>

为参数名,

<类型>

为参数的类型(可以是任何有效的 MySQL 数据类型)。当有多个参数时,参数列表中彼此间用逗号分隔。存储过程可以没有参数(此时存储过程的名称后仍需加上一对括号),也可以有 1 个或多个参数。

MySQL 存储过程支持三种类型的参数,即输入参数、输出参数和输入/输出参数,分别用 IN、OUT 和 INOUT 三个关键字标识。其中,输入参数可以传递给一个存储过程,输出参数用于存储过程需要返回一个操作结果的情形,而输入/输出参数既可以充当输入参数也可以充当输出参数。

需要注意的是,参数的取名不要与数据表的列名相同,否则尽管不会返回出错信息,但是存储过程的 SQL 语句会将参数名看作列名,从而引发不可预知的结果。

3) 过程体

存储过程的主体部分,也称为存储过程体,包含在过程调用的时候必须执行的 SQL 语句。这个部分以关键字

BEGIN

开始,以关键字

END

结束。若存储过程体中只有一条 SQL 语句,则可以省略 BEGIN-END 标志。

在存储过程的创建中,经常会用到一个十分重要的 MySQL 命令,即 DELIMITER 命令,特别是对于通过命令行的方式来操作 MySQL 数据库的使用者,更是要学会使用该命令。

在 MySQL 中,服务器处理 SQL 语句默认是以分号作为语句结束标志的。然而,在创建存储过程时,存储过程体可能包含有多条 SQL 语句,这些 SQL 语句如果仍以分号作为语句结束符,那么 MySQL 服务器在处理时会以遇到的第一条 SQL 语句结尾处的分号作为整个程序的结束符,而不再去处理存储过程体中后面的 SQL 语句,这样显然不行。

为解决以上问题,通常使用

DELIMITER

命令将结束命令修改为其他字符。语法格式如下:

DELIMITER $$

语法说明如下:

  • $$ 是用户定义的结束符,通常这个符号可以是一些特殊的符号,如两个“?”或两个“¥”等。
  • 当使用 DELIMITER 命令时,应该避免使用反斜杠“\”字符,因为它是 MySQL 的转义字符。

在 MySQL 命令行客户端输入如下 SQL 语句。

mysql > DELIMITER ??

成功执行这条 SQL 语句后,任何命令、语句或程序的结束标志就换为两个问号“??”了。

若希望换回默认的分号“;”作为结束标志,则在 MySQL 命令行客户端输入下列语句即可:

mysql > DELIMITER ;

注意:DELIMITER 和分号“;”之间一定要有一个空格

。在创建存储过程时,必须具有 CREATE ROUTINE 权限。

创建名称为 ShowStuScore 的存储过程,存储过程的作用是从学生成绩信息表中查询学生的成绩信息,输入的 SQL 语句和执行过程如下所示。

mysql> DELIMITER //
mysql> CREATE PROCEDURE ShowStuScore()
    -> BEGIN
    -> SELECT * FROM tb_students_score;
    -> END //
Query OK, 0 rows affected (0.09 sec)

结果显示 ShowStuScore 存储过程已经创建成功。

创建名称为 GetScoreByStu 的存储过程,输入参数是学生姓名。存储过程的作用是通过输入的学生姓名从学生成绩信息表中查询指定学生的成绩信息,输入的 SQL 语句和执行过程如下所示。

mysql> DELIMITER //
mysql> CREATE PROCEDURE GetScoreByStu
    -> (IN name VARCHAR(30))
    -> BEGIN
    -> SELECT student_score FROM tb_students_score
    -> WHERE student_name=name;
    -> END //
Query OK, 0 rows affected (0.01 sec)

2.查看存储过程的状态

MySQL 中可以通过 SHOW STATUS 语句查看存储过程的状态,其基本语法形式如下:

SHOW PROCEDURE STATUS LIKE 存储过程名;

LIKE 存储过程名

用来匹配存储过程的名称,LIKE 不能省略。

创建数据表 studentinfo,SQL 语句如下:

CREATE TABLE `studentinfo` (
    `ID` int(11) NOT NULL,
    `NAME` varchar(20) DEFAULT NULL,
    `SCORE` decimal(4,2) DEFAULT NULL,
    `SUBJECT` varchar(20) DEFAULT NULL,
    `TEACHER` varchar(20) DEFAULT NULL,
    PRIMARY KEY (`ID`)
);

向数据表 studentinfo 中插入数据,SQL 语句和执行结果如下:

mysql> INSERT INTO studentinfo(id,name,score) VALUES(1,"zhangsan",80),(2,"lisi","70");
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

创建存储过程 showstuscore,SQL 语句和运行结果如下:

mysql> DELIMITER //
mysql> CREATE PROCEDURE showstuscore()
    -> BEGIN
    -> SELECT id,name,score FROM studentinfo;
    -> END //
Query OK, 0 rows affected (0.07 sec)

下面查询名为 showstuscore 的存储过程的状态,SQL 语句和运行结果如下:

mysql> SHOW PROCEDURE STATUS LIKE 'showstuscore' \G
*************************** 1. row ***************************
                  Db: test
                Name: showstuscore
                Type: PROCEDURE
             Definer: root@localhost
            Modified: 2020-02-20 13:34:50
             Created: 2020-02-20 13:34:50
       Security_type: DEFINER
             Comment:
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: latin1_swedish_ci
1 row in set (0.01 sec)

mysql> SHOW PROCEDURE STATUS LIKE 'show%' \G
*************************** 1. row ***************************
                  Db: test
                Name: showstuscore
                Type: PROCEDURE
             Definer: root@localhost
            Modified: 2020-02-21 09:34:50
             Created: 2020-02-21 09:34:50
       Security_type: DEFINER
             Comment:
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: latin1_swedish_ci
1 row in set (0.00 sec)

查询结果显示了存储过程的创建时间、修改时间和字符集等信息。

查看存储过程的定义

MySQL 中可以通过 SHOW CREATE 语句查看存储过程的状态,语法格式如下:

SHOW CREATE PROCEDURE 存储过程名;

下面使用 SHOW CREATE 查询名为 showstuscore 的存储过程的状态,SQL 语句和运行结果如下:

mysql> SHOW CREATE PROCEDURE showstuscore \G
*************************** 1. row ***************************
           Procedure: showstuscore
            sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `showstuscore`()
BEGIN
SELECT id,name,score FROM studentinfo;
END
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: latin1_swedish_ci
1 row in set (0.01 sec)

查询结果显示了存储过程的定义和字符集信息等。

SHOW STATUS 语句只能查看存储过程是操作的哪一个数据库、存储过程的名称、类型、谁定义的、创建和修改时间、字符编码等信息。但是,这个语句不能查询存储过程的集体定义,如果需要查看详细定义,需要使用 SHOW CREATE 语句。

拓展阅读

存储过程的信息都存储在 information_schema 数据库下的 Routines 表中,可以通过查询该表的记录来查询存储过程的信息,SQL 语句如下:

SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME=存储过程名;

在 information_schema 数据库下的 routines 表中,存储着所有存储过程的定义。所以,使用 SELECT 语句查询 routines 表中的存储过程和函数的定义时,一定要使用 routine_name 字段指定存储过程的名称,否则,将查询出所有的存储过程的定义。

3.修改存储过程

在实际开发过程中,业务需求修改的情况时有发生,所以修改

MySQL

中的存储过程是不可避免的。

MySQL 中通过 ALTER PROCEDURE 语句来修改存储过程。本节将详细讲解修改存储过程的方法。

MySQL 中修改存储过程的语法格式如下:

ALTER PROCEDURE 存储过程名 [ 特征 … ]

特征

指定了存储过程的特性,可能的取值有:

  • CONTAINS SQL 表示子程序包含 SQL 语句,但不包含读或写数据的语句。
  • NO SQL 表示子程序中不包含 SQL 语句。
  • READS SQL DATA 表示子程序中包含读数据的语句。
  • MODIFIES SQL DATA 表示子程序中包含写数据的语句。
  • SQL SECURITY { DEFINER |INVOKER } 指明谁有权限来执行。
  • DEFINER 表示只有定义者自己才能够执行。
  • INVOKER 表示调用者可以执行。
  • COMMENT ‘string’ 表示注释信息。

下面修改存储过程 showstuscore 的定义,将读写权限改为 MODIFIES SQL DATA,并指明调用者可以执行,代码如下:

mysql> ALTER PROCEDURE showstuscore MODIFIES SQL DATA SQL SECURITY INVOKER;
Query OK, 0 rows affected (0.01 sec)

执行代码,并查看修改后的信息,运行结果如下:

mysql> SHOW CREATE PROCEDURE showstuscore \G
*************************** 1. row ***************************
           Procedure: showstuscore
            sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `showstuscore`()
    MODIFIES SQL DATA
    SQL SECURITY INVOKER
BEGIN
SELECT id,name,score FROM studentinfo;
END
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: latin1_swedish_ci
1 row in set (0.00 sec)

结果显示,存储过程修改成功。从运行结果可以看到,访问数据的权限已经变成了 MODIFIES SQL DATA,安全类型也变成了 INVOKE。

提示:ALTER PROCEDURE 语句用于修改存储过程的某些特征。如果要修改存储过程的内容,可以先删除原存储过程,再以相同的命名创建新的存储过程;如果要修改存储过程的名称,可以先删除原存储过程,再以不同的命名创建新的存储过程。

4.删除存储过程

存储过程被创建后,就会一直保存在数据库服务器上,直至被删除。当

MySQL

数据库中存在废弃的存储过程时,我们需要将它从数据库中删除。

MySQL 中使用 DROP PROCEDURE 语句来删除数据库中已经存在的存储过程。语法格式如下:

DROP PROCEDURE [ IF EXISTS ] <过程名>

语法说明如下:

  • 过程名:指定要删除的存储过程的名称。
  • IF EXISTS:指定这个关键字,用于防止因删除不存在的存储过程而引发的错误。

注意:存储过程名称后面没有参数列表,也没有括号,在删除之前,必须确认该存储过程没有任何依赖关系,否则会导致其他与之关联的存储过程无法运行。

下面删除存储过程 ShowStuScore,SQL 语句和运行结果如下:

mysql> DROP PROCEDURE ShowStuScore;
Query OK, 0 rows affected (0.08 sec)

删除后,可以通过查询 information_schema 数据库下的 routines 表来确认上面的删除是否成功。SQL 语句和运行结果如下:

mysql> SELECT * FROM information_schema.routines WHERE routine_name='ShowStuScore';
Empty set (0.03 sec)

结果显示,没有查询出任何记录,说明存储过程 ShowStuScore 已经被删除了。

5.存储函数

存储函数和存储过程一样,都是在数据库中定义一些 SQL 语句的集合。存储函数可以通过 return 语句返回函数值,主要用于计算并返回一个值。而存储过程没有直接返回值,主要用于执行操作。

在 MySQL 中,使用

CREATE FUNCTION

语句来创建存储函数,其语法形式如下:

CREATE FUNCTION sp_name ([func_parameter[…]])
RETURNS type
[characteristic …] routine_body

其中:

  • sp_name 参数:表示存储函数的名称;
  • func_parameter:表示存储函数的参数列表;
  • RETURNS type:指定返回值的类型;
  • characteristic 参数:指定存储函数的特性,该参数的取值与存储过程是一样的;
  • routine_body 参数:表示 SQL 代码的内容,可以用 BEGIN…END 来标示 SQL 代码的开始和结束。

注意:在具体创建函数时,函数名不能与已经存在的函数名重名。除了上述要求外,推荐函数名命名(标识符)为 function_xxx 或者 func_xxx。

func_parameter 可以由多个参数组成,其中每个参数由参数名称和参数类型组成,其形式如下:

[IN | OUT | INOUT] param_name type;

其中:

  • IN 表示输入参数,OUT 表示输出参数,INOUT 表示既可以输入也可以输出;
  • param_name 参数是存储函数的参数名称;
  • type 参数指定存储函数的参数类型,该类型可以是 MySQL 数据库的任意数据类型。

使用 CREATE FUNCTION 创建查询 tb_student 表中某个学生姓名的函数,SQL 语句和执行过程如下:

mysql> USE test;
Database changed
mysql> DELIMITER //
mysql> CREATE FUNCTION func_student(id INT(11))
    -> RETURNS VARCHAR(20)
    -> COMMENT '查询某个学生的姓名'
    -> BEGIN
    -> RETURN(SELECT name FROM tb_student WHERE tb_student.id = id);
    -> END //
Query OK, 0 rows affected (0.10 sec)
mysql> DELIMITER ;

上述代码中,创建了 func_student 函数,该函数拥有一个类型为 INT(11) 的参数 id,返回值为 VARCHAR(20) 类型。SELECT 语句从 tb_student 表中查询 id 字段值等于所传入参数 id 值的记录,同时返回该条记录的 name 字段值。

创建函数与创建存储过程一样,需要通过命令

DELIMITER //

将 SQL 语句的结束符由“;”修改为“//”,最后通过命令

DELIMITER ;

将结束符号修改成 SQL 语句中默认的结束符号。

如果在存储函数中的 RETURN 语句返回一个类型不同于函数的 RETURNS 子句中指定类型的值,返回值将被强制为恰当的类型。比如,如果一个函数返回一个 ENUM 或 SET 值,但是 RETURN 语句返回一个整数,对于 SET 成员集的相应的 ENUM 成员,从函数返回的值是字符串。

拓展阅读

由于存储函数和存储过程的查看、修改、删除等操作几乎相同,所以我们不再详细讲解如何操作存储函数了。

查看存储函数的语法如下:

SHOW FUNCTION STATUS LIKE 存储函数名;
SHOW CREATE FUNCTION 存储函数名;
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME=存储函数名;

可以发现,操作存储函数和操作存储过程不同的是将 PROCEDURE 替换成了 FUNCTION。同样,修改存储函数的语法如下:

ALTER FUNCTION 存储函数名 [ 特征 … ]

存储函数的特征与存储过程的基本一样。

删除存储过程的语法如下:

DROP FUNCTION [ IF EXISTS ] <函数名>

6.调用存储过程和函数

存储过程和存储函数都是存储在服务器端的 SQL 语句集合。要想使用这些已经定义好的存储过程和存储函数就必须要通过调用的方式来实现。

存储过程通过 CALL 语句来调用,存储函数的使用方法与 MySQL 内部函数的使用方法相同。执行存储过程和存储函数需要拥有 EXECUTE 权限(EXECUTE 权限的信息存储在 information_schema 数据库下的 USER_PRIVILEGES 表中)。

调用存储过程

MySQL 中使用

CALL

语句来调用存储过程。调用存储过程后,数据库系统将执行存储过程中的 SQL 语句,然后将结果返回给输出值。

CALL 语句接收存储过程的名字以及需要传递给它的任意参数,基本语法形式如下:

CALL sp_name([parameter[…]]);

其中,sp_name 表示存储过程的名称,parameter 表示存储过程的参数。

下面调用《MySQL创建存储过程》一节中创建的存储过程,SQL 语句和执行过程如下:

mysql> DELIMITER ;
mysql> CALL ShowStuScore();
+--------------+---------------+
| student_name | student_score |
+--------------+---------------+
| Dany         |            90 |
| Green        |            99 |
| Henry        |            95 |
| Jane         |            98 |
| Jim          |            88 |
| John         |            94 |
| Lily         |           100 |
| Susan        |            96 |
| Thomas       |            93 |
| Tom          |            89 |
+--------------+---------------+
10 rows in set (0.00 sec)
Query OK, 0 rows affected (0.02 sec)

mysql> CALL GetScoreByStu('Green');
+---------------+
| student_score |
+---------------+
|            99 |
+---------------+
1 row in set (0.03 sec)
Query OK, 0 rows affected (0.03 sec)

因为存储过程实际上也是一种函数,所以存储过程名后需要有

( )

符号,即使不传递参数也需要。

调用存储函数

在 MySQL 中,存储函数的使用方法与 MySQL 内部函数的使用方法是一样的。换言之,用户自己定义的存储函数与 MySQL 内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是 MySQL 开发者定义的。

下面调用《MySQL创建存储函数》一节中创建的存储函数,SQL 语句和执行过程如下:

mysql> SELECT func_student(3);
+-----------------+
| func_student(3) |
+-----------------+
| 王五            |
+-----------------+
1 row in set (0.10 sec)

通过例 1 和例 2 的比较,可以看出虽然存储函数和存储过程的定义稍有不同,但它们都可以实现相同的功能,我们应该在实际应用中灵活选择。

7.变量

在 MySQL 中,除了支持标准的存储过程和函数外,还引入了表达式。表达式与其它高级语言的表达式一样,由变量、运算符和流程控制来构成。

变量是表达式语句中最基本的元素,可以用来临时存储数据。在存储过程和函数中都可以定义和使用变量。用户可以使用 DECLARE 关键字来定义变量,定义后可以为变量赋值。这些变量的作用范围是 BEGIN…END 程序段中。

7.1.定义变量

MySQL 中可以使用

DECLARE

关键字来定义变量,其基本语法如下:

DECLARE var_name[,…] type [DEFAULT value]

其中:

  • DECLARE 关键字是用来声明变量的;
  • var_name 参数是变量的名称,这里可以同时定义多个变量;
  • type 参数用来指定变量的类型;
  • DEFAULT value 子句将变量默认值设置为 value,没有使用 DEFAULT 子句时,默认值为 NULL。
  • 下面定义变量 my_sql,数据类型为 INT 类型,默认值为 10。SQL 语句如下:

DECLARE my_sql INT DEFAULT 10;

7.2为变量赋值

MySQL 中可以使用

SET

关键字来为变量赋值,SET 语句的基本语法如下:

SET var_name = expr[,var_name = expr]…

其中:

  • SET 关键字用来为变量赋值;
  • var_name 参数是变量的名称;
  • expr 参数是赋值表达式。

注意:一个 SET 语句可以同时为多个变量赋值,各个变量的赋值语句之间用逗号隔开。

下面为变量 my_sql 赋值为 30。SQL 语句如下:

SET my_sql=30;

MySQL 中还可以使用

SELECT…INTO

语句为变量赋值。其基本语法如下:

SELECT col_name […] INTO var_name[,…]

FROM table_name WEHRE condition

其中:

  • col_name 参数表示查询的字段名称;
  • var_name 参数是变量的名称;
  • table_name 参数指表的名称;
  • condition 参数指查询条件。

注意:当将查询结果赋值给变量时,该查询语句的返回结果只能是单行。

下面从 tb_student 表中查询 id 为 2 的记录,将该记录的 id 值赋给变量 my_sql。SQL 语句如下:

SELECT id INTO my_sql FROM tb_student WEHRE id=2;

8.定义条件和处理程序

在程序的运行过程中可能会遇到问题,此时我们可以通过定义条件和处理程序来事先定义这些问题。

定义条件是指事先定义程序执行过程中遇到的问题,处理程序定义了在遇到这些问题时应当采取的处理方式和解决办法,保证存储过程和函数在遇到警告或错误时能继续执行,从而增强程序处理问题的能力,避免程序出现异常被停止执行。

8.1定义条件

MySQL 中可以使用

DECLARE

关键字来定义条件。其基本语法如下:

DECLARE condition_name CONDITION FOR condition_value
condition value:
SQLSTATE [VALUE] sqlstate_value | mysql_error_code

其中:

  • condition_name 参数表示条件的名称;
  • condition_value 参数表示条件的类型;
  • sqlstate_value 参数和 mysql_error_code 参数都可以表示 MySQL 的错误。sqlstate_value 表示长度为 5 的字符串类型错误代码,mysql_error_code 表示数值类型错误代码。例如 ERROR 1146(42S02) 中,sqlstate_value 值是 42S02,mysql_error_code 值是 1146。

下面定义“ERROR 1146 (42S02)”这个错误,名称为 can_not_find。 可以用两种不同的方法来定义,代码如下:

//方法一:使用sqlstate_value
DECLARE can_not_find CONDITION FOR SQLSTATE '42S02';

//方法二:使用 mysql_error_code
DECLARE can_not_find CONDITION FOR 1146;

8.2 定义处理程序

MySQL 中可以使用

DECLARE

关键字来定义处理程序。其基本语法如下:

DECLARE handler_type HANDLER FOR condition_value[...] sp_statement
handler_type:
CONTINUE | EXIT | UNDO
condition_value:
SQLSTATE [VALUE] sqlstate_value | condition_name | SQLWARNING | NOT FOUND | SQLEXCEPTION | mysql_error_code

其中,handler_type 参数指明错误的处理方式,该参数有 3 个取值。这 3 个取值分别是 CONTINUE、EXIT 和 UNDO。

  • CONTINUE 表示遇到错误不进行处理,继续向下执行;
  • EXIT 表示遇到错误后马上退出;
  • UNDO 表示遇到错误后撤回之前的操作,MySQL 中暂时还不支持这种处理方式。

注意:通常情况下,执行过程中遇到错误应该立刻停止执行下面的语句,并且撤回前面的操作。但是,MySQL 中现在还不能支持 UNDO 操作。因此,遇到错误时最好执行 EXIT 操作。如果事先能够预测错误类型,并且进行相应的处理,那么可以执行 CONTINUE 操作。

参数指明错误类型,该参数有 6 个取值:

  • sqlstate_value:包含 5 个字符的字符串错误值;
  • condition_name:表示 DECLARE 定义的错误条件名称;
  • SQLWARNING:匹配所有以 01 开头的 sqlstate_value 值;
  • NOT FOUND:匹配所有以 02 开头的 sqlstate_value 值;
  • SQLEXCEPTION:匹配所有没有被 SQLWARNING 或 NOT FOUND 捕获的 sqlstate_value 值;
  • mysql_error_code:匹配数值类型错误代码。

sp_statement 参数为程序语句段,表示在遇到定义的错误时,需要执行的一些存储过程或函数。

下面是定义处理程序的几种方式,代码如下:

//方法一:捕获 sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info='CAN NOT FIND';

//方法二:捕获 mysql_error_code
DECLARE CONTINUE HANDLER FOR 1146 SET @info='CAN NOT FIND';

//方法三:先定义条件,然后调用
DECLARE can_not_find CONDITION FOR 1146;
DECLARE CONTINUE HANDLER FOR can_not_find SET @info='CAN NOT FIND';

//方法四:使用 SQLWARNING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info='ERROR';

//方法五:使用 NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info='CAN NOT FIND';

//方法六:使用 SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info='ERROR';

上述代码是 6 种定义处理程序的方法。

  1. 捕获 sqlstate_value 值。如果遇到 sqlstate_value 值为 42S02,执行 CONTINUE 操作,并且输出“CAN NOT FIND”信息。
  2. 捕获 mysql_error_code 值。如果遇到 mysql_error_code 值为 1146, 执行 CONTINUE 操作,并且输出“CAN NOT FIND”信息。
  3. 先定义条件,然后再调用条件。这里先定义 can_not_find 条件,遇到 1146 错误就执行 CONTINUE 操作。
  4. 使用 SQLWARNING。SQLWARNING 捕获所有以 01 开头的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“ERROR"信息。
  5. 使用 NOT FOUND。NOT FOUND 捕获所有以 02 开头的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“CAN NOT FIND”信息。
  6. 使用 SQLEXCEPTION。 SQLEXCEPTION 捕获所有没有被 SQLWARNING 或 NOT FOUND 捕获的 sqlstate_value 值,然后执行 EXIT 操作,并且输出“ERROR”信息

定义条件和处理顺序,具体的执行过程如下:

mysql> CREATE TABLE t8(s1 INT,PRIMARY KEY(s1));
Query OK, 0 rows affected (0.07 sec)

mysql> DELIMITER //
mysql> CREATE PROCEDURE handlerdemo()
    -> BEGIN
    -> DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @X2=1;
    -> SET @X=1;
    -> INSERT INTO t8 VALUES(1);
    -> SET @X=2;
    -> INSERT INTO t8 VALUES(1);
    -> SET @X=3;
    -> END //
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER ;
mysql> CALL handlerdemo();
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT @X;
+------+
| @X   |
+------+
|    3 |
+------+
1 row in set (0.00 sec)

上述代码中,@X 是一个用户变量,执行结果 @X 等于 3,这表明 MySQL 执行到程序的末尾。

如果

DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @X2=1;

这一行不存在,第二个 INSERT 因 PRIMARY KEY 约束而失败之后,MySQL 可能已经采取 EXIT 策略,且 SELECT @X 可能已经返回 2。

注意:@X 表示用户变量,使用 SET 语句为其赋值,用户变量与连接有关,一个客户端定义的变量不能被其他客户端所使用,当客户端退出时,该客户端连接的所有变量将自动释放。

9.流程控制语句

在存储过程和自定义函数中可以使用流程控制语句来控制程序的流程。MySQL 中流程控制语句有:IF 语句、CASE 语句、LOOP 语句、LEAVE 语句、ITERATE 语句、REPEAT 语句和 WHILE 语句等。

9.1 IF语句

IF 语句

用来进行条件判断,根据是否满足条件(可包含多个条件),来执行不同的语句,是流程控制中最常用的判断语句。其语法的基本形式如下:

IF search_condition THEN statement_list
    [ELSEIF search_condition THEN statement_list]...
    [ELSE statement_list]
END IF

其中,search_condition 参数表示条件判断语句,如果返回值为 TRUE ,相应的 SQL 语句列表(statement_list)被执行;如果返回值为 FALSE,则 ELSE 子句的语句列表被执行。statement_list 可以包括一个或多个语句。

注意:MySQL 中的 IF( ) 函数不同于这里的 IF 语句。

下面是一个使用 IF 语句的示例。代码如下:

IF age>20 THEN SET @count1=@count1+1;
    ELSEIF age=20 THEN @count2=@count2+1;
    ELSE @count3=@count3+1;
END lF;

该示例根据 age 与 20 的大小关系来执行不同的 SET 语句。如果 age 值大于20,那么将 count1 的值加 1;如果 age 值等于 20,那么将 count2 的值加 1;其他情况将 count3 的值加 1。IF 语句都需要使用 END IF 来结束。

9.2 CASE语句

CASE 语句

也是用来进行条件判断的,它提供了多个条件进行选择,可以实现比 IF 语句更复杂的条件判断。CASE 语句的基本形式如下:

CASE case_value
    WHEN when_value THEN statement_list
    [WHEN when_value THEN statement_list]...
    [ELSE statement_list]
END CASE

其中:

  • case_value 参数表示条件判断的变量,决定了哪一个 WHEN 子句会被执行;
  • when_value 参数表示变量的取值,如果某个 when_value 表达式与 case_value 变量的值相同,则执行对应的 THEN 关键字后的 statement_list 中的语句;
  • statement_list 参数表示 when_value 值没有与 case_value 相同值时的执行语句。
  • CASE 语句都要使用 END CASE 结束。

CASE 语句还有另一种形式。该形式的语法如下:

CASE
    WHEN search_condition THEN statement_list
    [WHEN search_condition THEN statement_list] ...
    [ELSE statement_list]
END CASE

其中,search_condition 参数表示条件判断语句;statement_list 参数表示不同条件的执行语句。

与上述语句不同的是,该语句中的 WHEN 语句将被逐个执行,直到某个 search_condition 表达式为真,则执行对应 THEN 关键字后面的 statement_list 语句。如果没有条件匹配,ELSE 子句里的语句被执行。

这里介绍的 CASE 语句与“控制流程函数”里描述的 SQL CASE 表达式的 CASE 语句有轻微的不同。这里的 CASE 语句不能有 ELSE NULL 语句,并且用 END CASE 替代 END 来终止。

下面是一个使用 CASE 语句的示例。代码如下:

CASE age
    WHEN 20 THEN SET @count1=@count1+1;
    ELSE SET @count2=@count2+1;
END CASE;

代码也可以是下面的形式:

CASE
    WHEN age=20 THEN SET @count1=@count1+1;
    ELSE SET @count2=@count2+1;
END CASE;

本示例中,如果 age 值为 20,count1 的值加 1,否则 count2 的值加 1。

9.3 LOOP 语句

LOOP 语句

可以使某些特定的语句重复执行。与 IF 和 CASE 语句相比,LOOP 只实现了一个简单的循环,并不进行条件判断。

LOOP 语句本身没有停止循环的语句,必须使用 LEAVE 语句等才能停止循环,跳出循环过程。LOOP 语句的基本形式如下:

[begin_label:]LOOP
    statement_list
END LOOP [end_label]

其中,begin_label 参数和 end_label 参数分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;statement_list 参数表示需要循环执行的语句。

使用 LOOP 语句进行循环操作。代码如下:

add_num:LOOP
    SET @count=@count+1;
END LOOP add_num;

该示例循环执行 count 加 1 的操作。因为没有跳出循环的语句,这个循环成了一个死循环。LOOP 循环都以 END LOOP 结束。

9.4 LEAVE 语句

LEAVE 语句

主要用于跳出循环控制。其语法形式如下:

LEAVE label

其中,label 参数表示循环的标志,LEAVE 语句必须跟在循环标志前面。

下面是一个 LEAVE 语句的示例。代码如下:

add_num:LOOP
    SET @count=@count+1;
    IF @count=100 THEN
        LEAVE add_num;
END LOOP add num;

该示例循环执行 count 加 1 的操作。当 count 的值等于 100 时,跳出循环。

9.5 ITERATE 语句

ITERATE

是“再次循环”的意思,用来跳出本次循环,直接进入下一次循环。ITERATE 语句的基本语法形式如下:

ITERATE label

其中,label 参数表示循环的标志,ITERATE 语句必须跟在循环标志前面。

下面是一个 ITERATE 语句的示例。代码如下:

add_num:LOOP
    SET @count=@count+1;
    IF @count=100 THEN
        LEAVE add_num;
    ELSE IF MOD(@count,3)=0 THEN
        ITERATE add_num;
    SELECT * FROM employee;
END LOOP add_num;

该示例循环执行 count 加 1 的操作,count 值为 100 时结束循环。如果 count 的值能够整除 3,则跳出本次循环,不再执行下面的 SELECT 语句。

说明:LEAVE 语句和 ITERATE 语句都用来跳出循环语句,但两者的功能是不一样的。LEAVE 语句是跳出整个循环,然后执行循环后面的程序。而 ITERATE 语句是跳出本次循环,然后进入下一次循环。使用这两个语句时一定要区分清楚。

9.6 REPEAT 语句

REPEAT 语句

是有条件控制的循环语句,每次语句执行完毕后,会对条件表达式进行判断,如果表达式返回值为 TRUE,则循环结束,否则重复执行循环中的语句。

REPEAT 语句的基本语法形式如下:

[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]

其中:

  • begin_label 为 REPEAT 语句的标注名称,该参数可以省略;
  • REPEAT 语句内的语句被重复,直至 search_condition 返回值为 TRUE。
  • statement_list 参数表示循环的执行语句;
  • search_condition 参数表示结束循环的条件,满足该条件时循环结束。
  • REPEAT 循环都用 END REPEAT 结束。

下面是一个使用 REPEAT 语句的示例。代码如下:

REPEAT
    SET @count=@count+1;
    UNTIL @count=100
END REPEAT;

该示例循环执行 count 加 1 的操作,count 值为 100 时结束循环。

9.7 WHILE 语句

WHILE 语句

也是有条件控制的循环语句。WHILE 语句和 REPEAT 语句不同的是,WHILE 语句是当满足条件时,执行循环内的语句,否则退出循环。WHILE 语句的基本语法形式如下:

[begin_label:] WHILE search_condition DO
statement list
END WHILE [end label]

其中,search_condition 参数表示循环执行的条件,满足该条件时循环执行;statement_list 参数表示循环的执行语句。WHILE 循环需要使用 END WHILE 来结束。

下面是一个使用 WHILE 语句的示例。代码如下:

WHILE @count<100 DO
    SET @count=@count+1;
END WHILE;

该示例循环执行 count 加 1 的操作,count 值小于 100 时执行循环。如果 count 值等于 100 了,则跳出循环。

10.游标

在 MySQL 中,存储过程或函数中的查询有时会返回多条记录,而使用简单的 SELECT 语句,没有办法得到第一行、下一行或前十行的数据,这时可以使用游标来逐条读取查询结果集中的记录。游标在部分资料中也被称为光标。

关系数据库管理系统实质是面向集合的,在 MySQL 中并没有一种描述表中单一记录的表达形式,除非使用 WHERE 子句来限制只有一条记录被选中。所以有时我们必须借助于游标来进行单条记录的数据处理。

一般通过游标定位到结果集的某一行进行数据修改。

结果集是符合 SQL 语句的所有记录的集合。

个人理解游标就是一个标识,用来标识数据取到了什么地方,如果你了解编程语言,可以把他理解成数组中的下标。

不像多数 DBMS,MySQL 游标只能用于存储过程和函数。

下面介绍游标的使用,主要包括游标的声明、打开、使用和关闭。

10.1 声明游标

MySQL 中使用

DECLARE

关键字来声明游标,并定义相应的 SELECT 语句,根据需要添加 WHERE 和其它子句。其语法的基本形式如下:

DECLARE cursor_name CURSOR FOR select_statement;

其中,cursor_name 表示游标的名称;select_statement 表示 SELECT 语句,可以返回一行或多行数据。

下面声明一个名为 nameCursor 的游标,代码如下:

mysql> DELIMITER //
mysql> CREATE PROCEDURE processnames()
    -> BEGIN
    -> DECLARE nameCursor CURSOR
    -> FOR
    -> SELECT name FROM tb_student;
    -> END//
Query OK, 0 rows affected (0.07 sec)

以上语句定义了 nameCursor 游标,游标只局限于存储过程中,存储过程处理完成后,游标就消失了。

10.2 打开游标

声明游标之后,要想从游标中提取数据,必须首先打开游标。在 MySQL 中,打开游标通过

OPEN

关键字来实现,其语法格式如下:

OPEN cursor_name;

其中,cursor_name 表示所要打开游标的名称。需要注意的是,打开一个游标时,游标并不指向第一条记录,而是指向第一条记录的前边。

在程序中,一个游标可以打开多次。用户打开游标后,其他用户或程序可能正在更新数据表,所以有时会导致用户每次打开游标后,显示的结果都不同。

10.3 使用游标

游标顺利打开后,可以使用

FETCH…INTO

语句来读取数据,其语法形式如下:

FETCH cursor_name INTO var_name [,var_name]…

上述语句中,将游标 cursor_name 中 SELECT 语句的执行结果保存到变量参数 var_name 中。变量参数 var_name 必须在游标使用之前定义。使用游标类似高级语言中的数组遍历,当第一次使用游标时,此时游标指向结果集的第一条记录。

MySQL 的游标是只读的,也就是说,你只能顺序地从开始往后读取结果集,不能从后往前,也不能直接跳到中间的记录。

10.4 关闭游标

游标使用完毕后,要及时关闭,在 MySQL 中,使用

CLOSE

关键字关闭游标,其语法格式如下:

CLOSE cursor_name;

CLOSE 释放游标使用的所有内部内存和资源,因此每个游标不再需要时都应该关闭。

在一个游标关闭后,如果没有重新打开,则不能使用它。但是,使用声明过的游标不需要再次声明,用 OPEN 语句打开它就可以了。

如果你不明确关闭游标,MySQL 将会在到达 END 语句时自动关闭它。游标关闭之后,不能使用 FETCH 来使用该游标。

创建 users 数据表,并插入数据,SQL 语句和运行结果如下:

mysql> CREATE TABLE `users`
    ->  (
    ->  `ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    ->  `user_name` VARCHAR(60),
    ->  `user_pass` VARCHAR(64),
    ->  PRIMARY KEY (`ID`)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> INSERT INTO users VALUES(null,'sheng','sheng123'),
    -> (null,'yu','yu123'),
    -> (null,'ling','ling123');
Query OK, 3 rows affected (0.01 sec)

创建存储过程 test_cursor,并创建游标 cur_test,查询 users 数据表中的第 3 条记录,SQL 语句和执行过程如下:

mysql> DELIMITER //
mysql> CREATE PROCEDURE test_cursor (in param INT(10),out result VARCHAR(90))
    -> BEGIN
    -> DECLARE name VARCHAR(20);
    -> DECLARE pass VARCHAR(20);
    -> DECLARE done INT;
    -> DECLARE cur_test CURSOR FOR SELECT user_name,user_pass FROM users;
    -> DECLARE continue handler FOR SQLSTATE '02000' SET done = 1;
    -> IF param THEN INTO result FROM users WHERE id = param;
    -> ELSE
    -> OPEN cur_test;
    -> repeat
    -> FETCH cur_test into name,pass;
    -> SELECT concat_ws(',',result,name,pass) INTO result;
    -> until done
    -> END repeat;
    -> CLOSE cur_test;
    -> END IF;
    -> END //
Query OK, 0 rows affected (0.10 sec)

mysql> call test_cursor(3,@test)//
Query OK, 1 row affected (0.03 sec)

mysql> select @test//
+-----------+
| @test     |
+-----------+
| ling,ling123 |
+-----------+
1 row in set (0.00 sec)

创建 pro_users() 存储过程,定义 cur_1 游标,将表 users 中的 user_name 字段全部修改为 MySQL,SQL 语句和执行过程如下。

mysql> CREATE PROCEDURE pro_users()
    -> BEGIN
    -> DECLARE result VARCHAR(100);
    -> DECLARE no INT;
    -> DECLARE cur_1 CURSOR FOR SELECT user_name FROM users;
    -> DECLARE CONTINUE HANDLER FOR NOT FOUND SET no=1;
    -> SET no=0;
    -> OPEN cur_1;
    -> WHILE no=0 do
    -> FETCH cur_1 into result;
    -> UPDATE users SET user_name='MySQL'
    -> WHERE user_name=result;
    -> END WHILE;
    -> CLOSE cur_1;
    -> END //
Query OK, 0 rows affected (0.05 sec)

mysql> call pro_users() //
Query OK, 0 rows affected (0.03 sec)

mysql> SELECT * FROM users //
+----+-----------+-----------+
| ID | user_name | user_pass |
+----+-----------+-----------+
|  1 | MySQL     | sheng      |
|  2 | MySQL     | zhang     |
|  3 | MySQL     | ying      |
+----+-----------+-----------+
3 rows in set (0.00 sec)

结果显示,users 表中的 user_name 字段已经全部修改为 MySQL。

11.触发器

MySQL 的触发器和存储过程一样,都是嵌入到 MySQL 中的一段程序,是 MySQL 中管理数据的有力工具。不同的是执行存储过程要使用 CALL 语句来调用,而触发器的执行不需要使用 CALL 语句来调用,也不需要手工启动,而是通过对数据表的相关操作来触发、激活从而实现执行。比如当对 student 表进行操作(INSERT,DELETE 或 UPDATE)时就会激活它执行。

触发器与数据表关系密切,主要用于保护表中的数据。特别是当有多个表具有一定的相互联系的时候,触发器能够让不同的表保持数据的一致性。

在 MySQL 中,只有执行 INSERT、UPDATE 和 DELETE 操作时才能激活触发器,其它 SQL 语句则不会激活触发器。

那么为什么要使用触发器呢?比如,在实际开发项目时,我们经常会遇到以下情况:

  • 在学生表中添加一条关于学生的记录时,学生的总数就必须同时改变。
  • 增加一条学生记录时,需要检查年龄是否符合范围要求。
  • 删除一条学生信息时,需要删除其成绩表上的对应记录。
  • 删除一条数据时,需要在数据库存档表中保留一个备份副本。

虽然上述情况实现的业务逻辑不同,但是它们都需要在数据表发生更改时,自动进行一些处理。这时就可以使用触发器处理。例如,对于第一种情况,可以创建一个触发器对象,每当添加一条学生记录时,就执行一次计算学生总数的操作,这样就可以保证每次添加一条学生记录后,学生总数和学生记录数是一致的。

触发器的优缺点

触发器的优点如下:

  • 触发器的执行是自动的,当对触发器相关表的数据做出相应的修改后立即执行。
  • 触发器可以实施比 FOREIGN KEY 约束、CHECK 约束更为复杂的检查和操作。
  • 触发器可以实现表数据的级联更改,在一定程度上保证了数据的完整性。

触发器的缺点如下:

  • 使用触发器实现的业务逻辑在出现问题时很难进行定位,特别是涉及到多个触发器的情况下,会使后期维护变得困难。
  • 大量使用触发器容易导致代码结构被打乱,增加了程序的复杂性,
  • 如果需要变动的数据量较大时,触发器的执行效率会非常低。

MySQL 支持的触发器

在实际使用中,MySQL 所支持的触发器有三种:INSERT 触发器、UPDATE 触发器和 DELETE 触发器。

11.1 INSERT 触发器

在 INSERT 语句执行之前或之后响应的触发器。

使用 INSERT 触发器需要注意以下几点:

  • 在 INSERT 触发器代码内,可引用一个名为 NEW(不区分大小写)的虚拟表来访问被插入的行。
  • 在 BEFORE INSERT 触发器中,NEW 中的值也可以被更新,即允许更改被插入的值(只要具有对应的操作权限)。
  • 对于 AUTO_INCREMENT 列,NEW 在 INSERT 执行之前包含的值是 0,在 INSERT 执行之后将包含新的自动生成值。

11.2 UPDATE 触发器

在 UPDATE 语句执行之前或之后响应的触发器。

使用 UPDATE 触发器需要注意以下几点:

  • 在 UPDATE 触发器代码内,可引用一个名为 NEW(不区分大小写)的虚拟表来访问更新的值。
  • 在 UPDATE 触发器代码内,可引用一个名为 OLD(不区分大小写)的虚拟表来访问 UPDATE 语句执行前的值。
  • 在 BEFORE UPDATE 触发器中,NEW 中的值可能也被更新,即允许更改将要用于 UPDATE 语句中的值(只要具有对应的操作权限)。
  • OLD 中的值全部是只读的,不能被更新。

注意:当触发器设计对触发表自身的更新操作时,只能使用 BEFORE 类型的触发器,AFTER 类型的触发器将不被允许。

11.3 DELETE 触发器

在 DELETE 语句执行之前或之后响应的触发器。

使用 DELETE 触发器需要注意以下几点:

  • 在 DELETE 触发器代码内,可以引用一个名为 OLD(不区分大小写)的虚拟表来访问被删除的行。
  • OLD 中的值全部是只读的,不能被更新。

总体来说,触发器使用的过程中,MySQL 会按照以下方式来处理错误。

对于事务性表,如果触发程序失败,以及由此导致的整个语句失败,那么该语句所执行的所有更改将回滚;对于非事务性表,则不能执行此类回滚,即使语句失败,失败之前所做的任何更改依然有效。

若 BEFORE 触发程序失败,则 MySQL 将不执行相应行上的操作。

若在 BEFORE 或 AFTER 触发程序的执行过程中出现错误,则将导致调用触发程序的整个语句失败。

仅当 BEFORE 触发程序和行操作均已被成功执行,MySQL 才会执行 AFTER 触发程序。

11.4创建触发器

触发器是与 MySQL数据表有关的数据库对象,在满足定义条件时触发,并执行触发器中定义的语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性。

在 MySQL 5.7 中,可以使用 CREATE TRIGGER 语句创建触发器。

语法格式如下:

CREATE <触发器名> < BEFORE | AFTER >
<INSERT | UPDATE | DELETE >
ON <表名> FOR EACH Row<触发器主体>

语法说明如下。

1) 触发器名

触发器的名称,触发器在当前数据库中必须具有唯一的名称。如果要在某个特定数据库中创建,名称前面应该加上数据库的名称。

2) INSERT | UPDATE | DELETE

触发事件,用于指定激活触发器的语句的种类。

注意:三种触发器的执行时间如下。

  • INSERT:将新行插入表时激活触发器。例如,INSERT 的 BEFORE 触发器不仅能被 MySQL 的 INSERT 语句激活,也能被 LOAD DATA 语句激活。
  • DELETE: 从表中删除某一行数据时激活触发器,例如 DELETE 和 REPLACE 语句。
  • UPDATE:更改表中某一行数据时激活触发器,例如 UPDATE 语句。
3) BEFORE | AFTER

BEFORE 和 AFTER,触发器被触发的时刻,表示触发器是在激活它的语句之前或之后触发。若希望验证新数据是否满足条件,则使用 BEFORE 选项;若希望在激活触发器的语句执行之后完成几个或更多的改变,则通常使用 AFTER 选项。

4) 表名

与触发器相关联的表名,此表必须是永久性表,不能将触发器与临时表或视图关联起来。在该表上触发事件发生时才会激活触发器。同一个表不能拥有两个具有相同触发时刻和事件的触发器。例如,对于一张数据表,不能同时有两个 BEFORE UPDATE 触发器,但可以有一个 BEFORE UPDATE 触发器和一个 BEFORE INSERT 触发器,或一个 BEFORE UPDATE 触发器和一个 AFTER UPDATE 触发器。

5) 触发器主体

触发器动作主体,包含触发器激活时将要执行的 MySQL 语句。如果要执行多个语句,可使用 BEGIN…END 复合语句结构。

6) FOR EACH ROW

一般是指行级触发,对于受触发事件影响的每一行都要激活触发器的动作。例如,使用 INSERT 语句向某个表中插入多行数据时,触发器会对每一行数据的插入都执行相应的触发器动作。

注意:每个表都支持 INSERT、UPDATE 和 DELETE 的 BEFORE 与 AFTER,因此每个表最多支持 6 个触发器。每个表的每个事件每次只允许有一个触发器。单一触发器不能与多个事件或多个表关联。

另外,在 MySQL 中,若需要查看数据库中已有的触发器,则可以使用 SHOW TRIGGERS 语句。

7)创建 BEFORE 类型触发器

在 test_db 数据库中,数据表 tb_emp8 为员工信息表,包含 id、name、deptId 和 salary 字段,数据表 tb_emp8 的表结构如下所示。

mysql> SELECT * FROM tb_emp8;
Empty set (0.07 sec)
mysql> DESC tb_emp8;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(22) | YES  | UNI | NULL    |       |
| deptId | int(11)     | NO   | MUL | NULL    |       |
| salary | float       | YES  |     | 0       |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.05 sec)

【实例 1】创建一个名为 SumOfSalary 的触发器,触发的条件是向数据表 tb_emp8 中插入数据之前,对新插入的 salary 字段值进行求和计算。输入的 SQL 语句和执行过程如下所示。

mysql> CREATE TRIGGER SumOfSalary
    -> BEFORE INSERT ON tb_emp8
    -> FOR EACH ROW
    -> SET @sum=@sum+NEW.salary;
Query OK, 0 rows affected (0.35 sec)

触发器 SumOfSalary 创建完成之后,向表 tb_emp8 中插入记录时,定义的 sum 值由 0 变成了 1500,即插入值 1000 和 500 的和,如下所示。

SET @sum=0;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO tb_emp8
    -> VALUES(1,'A',1,1000),(2,'B',1,500);
Query OK, 2 rows affected (0.09 sec)
Records: 2  Duplicates: 0  Warnings: 0
mysql> SELECT @sum;
+------+
| @sum |
+------+
| 1500 |
+------+
1 row in set (0.03 sec)
8)创建 AFTER 类型触发器

在 test_db 数据库中,数据表 tb_emp6 和 tb_emp7 都为员工信息表,包含 id、name、deptId 和 salary 字段,数据表 tb_emp6 和 tb_emp7 的表结构如下所示。

mysql> SELECT * FROM tb_emp6;
Empty set (0.07 sec)
mysql> SELECT * FROM tb_emp7;
Empty set (0.03 sec)
mysql> DESC tb_emp6;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  | MUL | NULL    |       |
| salary | float       | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> DESC tb_emp7;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   | PRI | NULL    |       |
| name   | varchar(25) | YES  |     | NULL    |       |
| deptId | int(11)     | YES  |     | NULL    |       |
| salary | float       | YES  |     | 0       |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.04 sec)

【实例 2】创建一个名为 double_salary 的触发器,触发的条件是向数据表 tb_emp6 中插入数据之后,再向数据表 tb_emp7 中插入相同的数据,并且 salary 为 tb_emp6 中新插入的 salary 字段值的 2 倍。输入的 SQL 语句和执行过程如下所示。

mysql> CREATE TRIGGER double_salary
    -> AFTER INSERT ON tb_emp6
    -> FOR EACH ROW
    -> INSERT INTO tb_emp7
    -> VALUES (NEW.id,NEW.name,deptId,2*NEW.salary);
Query OK, 0 rows affected (0.25 sec)

触发器 double_salary 创建完成之后,向表 tb_emp6 中插入记录时,同时向表 tb_emp7 中插入相同的记录,并且 salary 字段为 tb_emp6 中 salary 字段值的 2 倍,如下所示。

mysql> INSERT INTO tb_emp6
    -> VALUES (1,'A',1,1000),(2,'B',1,500);
Query OK, 2 rows affected (0.09 sec)
Records: 2  Duplicates: 0  Warnings: 0
mysql> SELECT * FROM tb_emp6;
+----+------+--------+--------+
| id | name | deptId | salary |
+----+------+--------+--------+
|  1 | A    |      1 |   1000 |
|  2 | B    |      1 |    500 |
+----+------+--------+--------+
3 rows in set (0.04 sec)
mysql> SELECT * FROM tb_emp7;
+----+------+--------+--------+
| id | name | deptId | salary |
+----+------+--------+--------+
|  1 | A    |      1 |   2000 |
|  2 | B    |      1 |   1000 |
+----+------+--------+--------+
2 rows in set (0.06 sec)

11.5查看触发器

查看触发器是指查看数据库中已经存在的触发器的定义、状态和语法信息等。MySQL 中查看触发器的方法包括 SHOW TRIGGERS 语句和查询 information_schema 数据库下的 triggers 数据表等。本节将详细介绍这两种查看触发器的方法。

SHOW TRIGGERS语句查看触发器信息

在 MySQL 中,可以通过 SHOW TRIGGERS 语句来查看触发器的基本信息,语法格式如下:

SHOW TRIGGERS;

首先创建一个数据表 account,表中有两个字段,分别是 INT 类型的 accnum 和 DECIMAL 类型的 amount。SQL 语句和运行结果如下:

mysql> CREATE TABLE account(
    -> accnum INT(4),
    -> amount DECIMAL(10,2));
Query OK, 0 rows affected (0.49 sec)

创建一个名为 trigupdate 的触发器,每次 account 表更新数据之后都向 myevent 数据表中插入一条数据。创建数据表 myevent 的 SQL 语句和运行结果如下:

mysql> CREATE TABLE myevent(
    -> id INT(11) DEFAULT NULL,
    -> evtname CHAR(20) DEFAULT NULL);
Query OK, 0 rows affected (0.26 sec)

创建 trigupdate 触发器的 SQL 代码如下:

mysql> CREATE TRIGGER trigupdate AFTER UPDATE ON account
    -> FOR EACH ROW INSERT INTO myevent VALUES(1,'after update');
Query OK, 0 rows affected (0.15 sec)

使用 SHOW TRIGGERS 语句查看触发器(在 SHOW TRIGGERS 命令后添加

\G

,这样显示信息会比较有条理),SQL 语句和运行结果如下:

mysql> SHOW TRIGGERS \G
*************************** 1. row ***************************
             Trigger: trigupdate
               Event: UPDATE
               Table: account
           Statement: INSERT INTO myevent VALUES(1,'after update')
              Timing: AFTER
             Created: 2020-02-24 14:07:15.08
            sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
             Definer: root@localhost
character_set_client: gbk
collation_connection: gbk_chinese_ci
  Database Collation: latin1_swedish_ci
1 row in set (0.09 sec)

由运行结果可以看到触发器的基本信息。对以上显示信息的说明如下:

  • Trigger 表示触发器的名称,在这里触发器的名称为 trigupdate;
  • Event 表示激活触发器的事件,这里的触发事件为更新操作 UPDATE;
  • Table 表示激活触发器的操作对象表,这里为 account 表;
  • Statement 表示触发器执行的操作,这里是向 myevent 数据表中插入一条数据;
  • Timing 表示触发器触发的时间,这里为更新操作之后(AFTER);
  • 还有一些其他信息,比如触发器的创建时间、SQL 的模式、触发器的定义账户和字符集等,这里不再一一介绍。

SHOW TRIGGERS 语句用来查看当前创建的所有触发器的信息。因为该语句无法查询指定的触发器,所以在触发器较少的情况下,使用该语句会很方便。如果要查看特定触发器的信息或者数据库中触发器较多时,可以直接从 information_schema 数据库中的 triggers 数据表中查找。

在triggers表中查看触发器信息

在 MySQL 中,所有触发器的信息都存在 information_schema 数据库的 triggers 表中,可以通过查询命令 SELECT 来查看,具体的语法如下:

SELECT * FROM information_schema.triggers WHERE trigger_name= ‘触发器名’;

其中,

'触发器名'

用来指定要查看的触发器的名称,需要用单引号引起来。这种方式可以查询指定的触发器,使用起来更加方便、灵活。

下面使用 SELECT 命令查看 trigupdate 触发器,SQL 语句如下:

SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME= ‘trigupdate’\G

上述命令通过 WHERE 来指定需要查看的触发器的名称,运行结果如下:

mysql> SELECT * FROM information_schema.triggers WHERE TRIGGER_NAME= 'trigupdate'\G
*************************** 1. row ***************************
           TRIGGER_CATALOG: def
            TRIGGER_SCHEMA: test
              TRIGGER_NAME: trigupdate
        EVENT_MANIPULATION: UPDATE
      EVENT_OBJECT_CATALOG: def
       EVENT_OBJECT_SCHEMA: test
        EVENT_OBJECT_TABLE: account
              ACTION_ORDER: 1
          ACTION_CONDITION: NULL
          ACTION_STATEMENT: INSERT INTO myevent VALUES(1,'after update')
        ACTION_ORIENTATION: ROW
             ACTION_TIMING: AFTER
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
  ACTION_REFERENCE_OLD_ROW: OLD
  ACTION_REFERENCE_NEW_ROW: NEW
                   CREATED: 2020-02-24 16:07:15.08
                  SQL_MODE: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
                   DEFINER: root@localhost
      CHARACTER_SET_CLIENT: gbk
      COLLATION_CONNECTION: gbk_chinese_ci
        DATABASE_COLLATION: latin1_swedish_ci
1 row in set (0.22 sec)

由运行结果可以看到触发器的详细信息。对以上显示信息的说明如下:

  • TRIGGER_SCHEMA 表示触发器所在的数据库;
  • TRIGGER_NAME 表示触发器的名称;
  • EVENT_OBJECT_TABLE 表示在哪个数据表上触发;
  • ACTION_STATEMENT 表示触发器触发的时候执行的具体操作;
  • ACTION_ORIENTATION 的值为 ROW,表示在每条记录上都触发;
  • ACTION_TIMING 表示触发的时刻是 AFTER;
  • 还有一些其他信息,比如触发器的创建时间、SQL 的模式、触发器的定义账户和字符集等,这里不再一一介绍。

上述 SQL 语句也可以不指定触发器名称,这样将查看所有的触发器,SQL 语句如下:

SELECT * FROM information_schema.triggers \G

这个语句会显示 triggers 数据表中所有的触发器信息。

11.6修改和删除触发器

修改触发器可以通过删除原触发器,再以相同的名称创建新的触发器。

与其他 MySQL数据库对象一样,可以使用 DROP 语句将触发器从数据库中删除。

语法格式如下:

DROP TRIGGER [ IF EXISTS ] [数据库名] <触发器名>

语法说明如下:

1) 触发器名

要删除的触发器名称。

2) 数据库名

可选项。指定触发器所在的数据库的名称。若没有指定,则为当前默认的数据库。

3) 权限

执行 DROP TRIGGER 语句需要 SUPER 权限。

4) IF EXISTS

可选项。避免在没有触发器的情况下删除触发器。

注意:删除一个表的同时,也会自动删除该表上的触发器。另外,触发器不能更新或覆盖,为了修改一个触发器,必须先删除它,再重新创建。

删除触发器

使用 DROP TRIGGER 语句可以删除 MySQL 中已经定义的触发器。

【实例】删除 double_salary 触发器,输入的 SQL 语句和执行过程如下所示。

mysql> DROP TRIGGER double_salary;
Query OK, 0 rows affected (0.03 sec)

删除 double_salary 触发器后,再次向数据表 tb_emp6 中插入记录时,数据表 tb_emp7 的数据不再发生变化,如下所示。

mysql> INSERT INTO tb_emp6
    -> VALUES (3,'C',1,200);
Query OK, 1 row affected (0.09 sec)
mysql> SELECT * FROM tb_emp6;
+----+------+--------+--------+
| id | name | deptId | salary |
+----+------+--------+--------+
|  1 | A    |      1 |   1000 |
|  2 | B    |      1 |    500 |
|  3 | C    |      1 |    200 |
+----+------+--------+--------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM tb_emp7;
+----+------+--------+--------+
| id | name | deptId | salary |
+----+------+--------+--------+
|  1 | A    |      1 |   2000 |
|  2 | B    |      1 |   1000 |
+----+------+--------+--------+
2 rows in set (0.00 sec)

十一、mysql事务和字符集

1.事务的概念和特性

数据库的

事务(Transaction)

是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。

在数据库系统上执行并发操作时,事务是作为最小的控制单元来使用的,特别适用于多用户同时操作的数据库系统。例如,航空公司的订票系统、银行、保险公司以及证券交易系统等

事务具有 4 个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID。

1. 原子性

事务是一个完整的操作。事务的各元素是不可分的(原子的)。事务中的所有元素必须作为一个整体提交或回滚。如果事务中的任何元素失败,则整个事务将失败。

以银行转账事务为例,如果该事务提交了,则这两个账户的数据将会更新。如果由于某种原因,事务在成功更新这两个账户之前终止了,则不会更新这两个账户的余额,并且会撤销对任何账户余额的修改,事务不能部分提交。

2. 一致性

当事务完成时,数据必须处于一致状态。也就是说,在事务开始之前,数据库中存储的数据处于一致状态。在正在进行的事务中. 数据可能处于不一致的状态,如数据可能有部分被修改。然而,当事务成功完成时,数据必须再次回到已知的一致状态。通过事务对数据所做的修改不能损坏数据,或者说事务不能使数据存储处于不稳定的状态。

以银行转账事务事务为例。在事务开始之前,所有账户余额的总额处于一致状态。在事务进行的过程中,一个账户余额减少了,而另一个账户余额尚未修改。因此,所有账户余额的总额处于不一致状态。事务完成以后,账户余额的总额再次恢复到一致状态。

3. 隔离性

对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。修改数据的事务可以在另一个使用相同数据的事务开始之前访问这些数据,或者在另一个使用相同数据的事务结束之后访问这些数据。

另外,当事务修改数据时,如果任何其他进程正在同时使用相同的数据,则直到该事务成功提交之后,对数据的修改才能生效。张三和李四之间的转账与王五和赵二之间的转账,永远是相互独立的。

4. 持久性

事务的持久性指不管系统是否发生了故障,事务处理的结果都是永久的。

一个事务成功完成之后,它对数据库所作的改变是永久性的,即使系统出现故障也是如此。也就是说,一旦事务被提交,事务对数据所做的任何变动都会被永久地保留在数据库中。

事务的 ACID 原则保证了一个事务或者成功提交,或者失败回滚,二者必居其一。因此,它对事务的修改具有可恢复性。即当事务失败时,它对数据的修改都会恢复到该事务执行前的状态。

2.事务的语法和流程

MySQL 提供了多种存储引擎来支持事务。支持事务的存储引擎有 InnoDB 和 BDB,其中,InnoDB 存储引擎事务主要通过 UNDO 日志和 REDO 日志实现,MyISAM 存储引擎不支持事务。

拓展:任何一种数据库,都会拥有各种各样的日志,用来记录数据库的运行情况、日常操作、错误信息等,MySQL 也不例外。例如,当用户 root 登录到 MySQL 服务器,就会在日志文件里记录该用户的登录时间、执行操作等。

为了维护 MySQL 服务器,经常需要在 MySQL 数据库中进行日志操作:

  • UNDO 日志:复制事务执行前的数据,用于在事务发生异常时回滚数据。
  • REDO 日志:记录在事务执行中,每条对数据进行更新的操作,当事务提交时,该内容将被刷新到磁盘。

默认设置下,每条 SQL 语句就是一个事务,即执行 SQL 语句后自动提交。为了达到将几个操作做为一个整体的目的,需要使用 BEGIN 或 START TRANSACTION 开启一个事务,或者禁止当前会话的自动提交。

执行事务的语法和流程

SQL 使用下列语句来管理事务。

1) 开始事务

BEGIN;

START TRANSACTION;

这个语句显式地标记一个事务的起始点。

2) 提交事务

MySQL 使用下面的语句来提交事务:

COMMIT;

COMMIT 表示提交事务,即提交事务的所有操作,具体地说,就是将事务中所有对数据库的更新都写到磁盘上的物理数据库中,事务正常结束。

提交事务,意味着将事务开始以来所执行的所有数据都修改成为数据库的永久部分,因此也标志着一个事务的结束。一旦执行了该命令,将不能回滚事务。只有在所有修改都准备好提交给数据库时,才执行这一操作。

3) 回滚(撤销)事务

MySQL 使用以下语句回滚事务:

ROLLBACK;

ROLLBACK 表示撤销事务,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态。这里的操作指对数据库的更新操作。

当事务执行过程中遇到错误时,使用 ROLLBACK 语句使事务回滚到起点或指定的保持点处。同时,系统将清除自事务起点或到某个保存点所做的所有的数据修改,并且释放由事务控制的资源。因此,这条语句也标志着事务的结束。

总结

BEGIN 或 START TRANSACTION 语句后面的 SQL 语句对数据库数据的更新操作都将记录在事务日志中,直至遇到 ROLLBACK 语句或 COMMIT 语句。如果事务中某一操作失败且执行了 ROLLBACK 语句,那么在开启事务语句之后所有更新的数据都能回滚到事务开始前的状态。如果事务中的所有操作都全部正确完成,并且使用了 COMMIT 语句向数据库提交更新数据,则此时的数据又处在新的一致状态。

下面通过两个例子来演示一下 MySQL 事务的具体用法。

下面模拟在张三的账户减少 500 元后,李四的账户还未增加 500 时,有其他会话访问数据表的场景。由于代码需要在两个窗口中执行,为了方便阅读,这里我们称为 A 窗口和 B 窗口。

  1. 在 A 窗口中开启一个事务,并更新 mybank 数据库中 bank 表的数据,SQL 语句和运行结果如下:
mysql> USE mybank;
Database changed
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE bank SET currentMoney = currentMoney-500
    -> WHERE customerName='张三';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0
  1. 在 B 窗口中查询 bank 数据表中的数据,SQL 语句和运行结果如下:
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |      1000.00 |
| 李四         |         1.00 |
+--------------+--------------+
2 rows in set (0.00 sec)

从结果可以看出,虽然 A 窗口中的事务已经更改了 bank 表中的数据,但没有立即更新数据,这时其他会话读取到的仍然是更新前的数据。

  1. 在 A 窗口中继续执行事务并提交事务,SQL 语句和运行结果如下:
mysql> UPDATE bank SET currentMoney = currentMoney+500
    -> WHERE customerName='李四';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> COMMIT;
Query OK, 0 rows affected (0.07 sec)
  1. 在 B 窗口中再次查询 bank 数据表的数据,SQL 语句和运行结果如下:
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |       500.00 |
| 李四         |       501.00 |
+--------------+--------------+
2 rows in set (0.00 sec)

在 A 窗口中执行 COMMIT 提交事务后,对数据所做的更新将一起提交,其他会话读取到的是更新后的数据。从结果可以看出张三和李四的总账户余额和转账前保持一致,这样数据从一个一致性状态更新到另一个一致性状态。

前面提到,当事务在执行中出现问题,也就是不能按正常的流程执行一个完整的事务时,可以使用 ROLLBACK 语句进行回滚,使用数据恢复到初始状态。

在例 1 中,张三的账户余额已经减少到 500 元,如果再转出 1000 元,将会出现余额为负数,因此需要回滚到原始状态。如例 2 所示。

将张三的账户余额减少 1000 元,并让事务回滚,SQL 语句和运行结果如下所示:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
 
mysql> UPDATE bank SET currentMoney = currentMoney-1000 WHERE customerName='张三';
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> ROLLBACK;
Query OK, 0 rows affected (0.07 sec)
 
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |       500.00 |
| 李四         |       501.00 |
+--------------+--------------+
2 rows in set (0.00 sec)

从结果可以看出,执行事务回滚后,账户数据恢复到初始状态,即该事务执行之前的状态。

拓展

在数据库操作中,为了有效保证并发读取数据的正确性,提出了事务的隔离级别。在例 1 和例 2 的演示中,事务的隔离级别为默认隔离级别。在 MySQL 中,事务的默认隔离级别是 REPEATABLE-READ (可重读)隔离级别,即事务未结束时(未执行 COMMIT 或 ROLLBACK),其它会话只能读取到未提交数据。

注意事项

MySQL 事务是一项非常消耗资源的功能,大家在使用过程中要注意以下几点。

1) 事务尽可能简短

事务的开启到结束会在数据库管理系统中保留大量资源,以保证事务的原子性、一致性、隔离性和持久性。如果在多用户系统中,较大的事务将会占用系统的大量资源,使得系统不堪重负,会影响软件的运行性能,甚至导致系统崩溃。

2) 事务中访问的数据量尽量最少

当并发执行事务处理时,事务操作的数据量越少,事务之间对相同数据的操作就越少。

3) 查询数据时尽量不要使用事务

对数据进行浏览查询操作并不会更新数据库的数据,因此应尽量不使用事务查询数据,避免占用过量的系统资源。

4) 在事务处理过程中尽量不要出现等待用户输入的操作

在处理事务的过程中,如果需要等待用户输入数据,那么事务会长时间地占用资源,有可能造成系统阻塞。

3.字符集&校对规则

在讲解字符集和校对规则之前,我们先来简单了解一下字符、字符集和字符编码。

字符(Character)

是计算机中字母、数字、符号的统称,一个字符可以是一个中文汉字、一个英文字母、一个阿拉伯数字、一个标点符号等。

计算机是以二进制的形式来存储数据的。平时我们在显示器上看到的数字、英文、标点符号、汉字等字符都是二进制数转换之后的结果。

字符集(Character set)

定义了字符和二进制的对应关系,为字符分配了唯一的编号。常见的字符集有 ASCII、GBK、IOS-8859-1 等。

字符编码(Character encoding)

也可以称为字集码,规定了如何将字符的编号存储到计算机中。

大部分字符集都只对应一种字符编码,例如:ASCII、IOS-8859-1、GB2312、GBK,都是既表示了字符集又表示了对应的字符编码。所以一般情况下,可以将两者视为同义词。Unicode 字符集除外,Unicode 有三种编码方案,即 UTF-8、UTF-16 和 UTF-32。最为常用的是 UTF-8 编码。

校对规则(Collation)

也可以称为排序规则,是指在同一个字符集内字符之间的比较规则。字符集和校对规则是一对多的关系,每个字符集都有一个默认的校对规则。字符集和校对规则相辅相成,相互依赖关联。

简单来说,字符集用来定义 MySQL 存储字符串的方式,校对规则用来定义 MySQL 比较字符串的方式。

有些数据库并没有清晰的区分开字符集和校对规则。例如,在 SQL Server 中创建数据库时,选择字符集就相当于选定了字符集和校对规则。

而在 MySQL 中,字符集和校对规则是区分开的,必须设置字符集和校对规则。一般情况下,没有特殊需求,只设置其一即可。只设置字符集时,MySQL 会将校对规则设置为字符集中对应的默认校对规则。

可以通过

SHOW VARIABLES LIKE 'character%';

命令查看当前 MySQL 使用的字符集,命令和运行结果如下:

mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+---------------------------------------------------------+
| Variable_name            | Value                                                   |
+--------------------------+---------------------------------------------------------+
| character_set_client     | gbk                                                     |
| character_set_connection | gbk                                                     |
| character_set_database   | latin1                                                  |
| character_set_filesystem | binary                                                  |
| character_set_results    | gbk                                                     |
| character_set_server     | latin1                                                  |
| character_set_system     | utf8                                                    |
| character_sets_dir       | C:\Program Files\MySQL\MySQL Server 5.7\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set, 1 warning (0.01 sec)

上述运行结果说明如下表所示:

名称说明
character_set_clientMySQL 客户端使用的字符集
character_set_connection连接数据库时使用的字符集
character_set_database创建数据库使用的字符集
character_set_filesystemMySQL 服务器文件系统使用的字符集,默认值为 binary,不做任何转换
character_set_results数据库给客户端返回数据时使用的字符集
character_set_serverMySQL 服务器使用的字符集,建议由系统自己管理,不要人为定义
character_set_system数据库系统使用的字符集,默认值为 utf8,不需要设置
character_sets_dir字符集的安装目录

乱码时,不需要关心 character_set_filesystem、character_set_system 和 character_sets_dir 这 3 个系统变量,它们不会影响乱码 。

可以通过

SHOW VARIABLES LIKE 'collation\_%';

命令查看当前 MySQL 使用的校对规则,命令和运行结果如下:

mysql> SHOW VARIABLES LIKE 'collation\_%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | gbk_chinese_ci    |
| collation_database   | latin1_swedish_ci |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set, 1 warning (0.01 sec)

对上述运行结果说明如下:

  • collation_connection:连接数据库时使用的校对规则
  • collation_database:创建数据库时使用的校对规则
  • collation_server:MySQL 服务器使用的校对规则

校对规则命令约定如下:

  • 以校对规则所对应的字符集名开头
  • 以国家名居中(或以 general 居中)
  • 以 ci、cs 或 bin 结尾,ci 表示大小写不敏感,cs 表示大小写敏感,bin 表示按二进制编码值比较。

MySQL字符集的转换过程

MySQL 中字符集的转换过程如下:

1)在命令提示符窗口(cmd 命令行)中执行 MySQL 命令或 sql 语句时,这些命令或语句从“命令提示符窗口字符集”转换为“character_set_client”定义的字符集。

2)使用命令提示符窗口成功连接 MySQL 服务器后,就建立了一条“数据通信链路”,MySQL 命令或 sql 语句沿着“数据链路”传向 MySQL 服务器,由 character_set_client 定义的字符集转换为 character_set_connection 定义的字符集。

3)MySQL 服务实例收到数据通信链路中的 MySQL 命令或 sql 语句后,将 MySQL 命令或 sql 语句从 character_set_connection 定义的字符集转换为 character_set_server 定义的字符集。

4)若 MySQL 命令或 sql 语句针对于某个数据库进行操作,此时将 MySQL 命令或 sql 语句从 character_set_server 定义的字符集转换为 character_set_database 定义的字符集。

5)MySQL 命令或 sql 语句执行结束后,将执行结果设置为 character_set_results 定义的字符集。

6)执行结果沿着打开的数据通信链路原路返回,将执行结果从 character_set_results 定义的字符集转换为 character_set_client 定义的字符集,最终转换为命令提示符窗口字符集,显示到命令提示符窗口中。

4.查看字符集和校对规则

在 MySQL 中,查看可用字符集的命令和执行过程如下:

mysql> SHOW CHARACTER set;
+----------+---------------------------------+---------------------+--------+
| Charset  | Description                     | Default collation   | Maxlen |
+----------+---------------------------------+---------------------+--------+
| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 |
| dec8     | DEC West European               | dec8_swedish_ci     |      1 |
| cp850    | DOS West European               | cp850_general_ci    |      1 |
| hp8      | HP West European                | hp8_english_ci      |      1 |
| koi8r    | KOI8-R Relcom Russian           | koi8r_general_ci    |      1 |
| latin1   | cp1252 West European            | latin1_swedish_ci   |      1 |
| latin2   | ISO 8859-2 Central European     | latin2_general_ci   |      1 |
| swe7     | 7bit Swedish                    | swe7_swedish_ci     |      1 |
| ascii    | US ASCII                        | ascii_general_ci    |      1 |
| ujis     | EUC-JP Japanese                 | ujis_japanese_ci    |      3 |
| sjis     | Shift-JIS Japanese              | sjis_japanese_ci    |      2 |
| hebrew   | ISO 8859-8 Hebrew               | hebrew_general_ci   |      1 |
| tis620   | TIS620 Thai                     | tis620_thai_ci      |      1 |
| euckr    | EUC-KR Korean                   | euckr_korean_ci     |      2 |
| koi8u    | KOI8-U Ukrainian                | koi8u_general_ci    |      1 |
| gb2312   | GB2312 Simplified Chinese       | gb2312_chinese_ci   |      2 |
| greek    | ISO 8859-7 Greek                | greek_general_ci    |      1 |
| cp1250   | Windows Central European        | cp1250_general_ci   |      1 |
| gbk      | GBK Simplified Chinese          | gbk_chinese_ci      |      2 |
| latin5   | ISO 8859-9 Turkish              | latin5_turkish_ci   |      1 |
| armscii8 | ARMSCII-8 Armenian              | armscii8_general_ci |      1 |
| utf8     | UTF-8 Unicode                   | utf8_general_ci     |      3 |
| ucs2     | UCS-2 Unicode                   | ucs2_general_ci     |      2 |
| cp866    | DOS Russian                     | cp866_general_ci    |      1 |
| keybcs2  | DOS Kamenicky Czech-Slovak      | keybcs2_general_ci  |      1 |
| macce    | Mac Central European            | macce_general_ci    |      1 |
| macroman | Mac West European               | macroman_general_ci |      1 |
| cp852    | DOS Central European            | cp852_general_ci    |      1 |
| latin7   | ISO 8859-13 Baltic              | latin7_general_ci   |      1 |
| utf8mb4  | UTF-8 Unicode                   | utf8mb4_general_ci  |      4 |
| cp1251   | Windows Cyrillic                | cp1251_general_ci   |      1 |
| utf16    | UTF-16 Unicode                  | utf16_general_ci    |      4 |
| utf16le  | UTF-16LE Unicode                | utf16le_general_ci  |      4 |
| cp1256   | Windows Arabic                  | cp1256_general_ci   |      1 |
| cp1257   | Windows Baltic                  | cp1257_general_ci   |      1 |
| utf32    | UTF-32 Unicode                  | utf32_general_ci    |      4 |
| binary   | Binary pseudo charset           | binary              |      1 |
| geostd8  | GEOSTD8 Georgian                | geostd8_general_ci  |      1 |
| cp932    | SJIS for Windows Japanese       | cp932_japanese_ci   |      2 |
| eucjpms  | UJIS for Windows Japanese       | eucjpms_japanese_ci |      3 |
| gb18030  | China National Standard GB18030 | gb18030_chinese_ci  |      4 |
+----------+---------------------------------+---------------------+--------+
41 rows in set (0.02 sec)

其中:

  • 第一列(Charset)为字符集名称;
  • 第二列(Description)为字符集描述;
  • 第三列(Default collation)为字符集的默认校对规则;
  • 第四列(Maxlen)表示字符集中一个字符占用的最大字节数。

常用的字符集如下:

  • latin1 支持西欧字符、希腊字符等。
  • gbk 支持中文简体字符。
  • big5 支持中文繁体字符。
  • utf8 几乎支持所有国家的字符。

也可以通过查询 information_schema.character_set 表中的记录,来查看 MySQL 支持的字符集。SQL 语句和执行过程如下:

mysql> SELECT * FROM information_schema.character_sets;
+--------------------+----------------------+---------------------------------+--------+
| CHARACTER_SET_NAME | DEFAULT_COLLATE_NAME | DESCRIPTION                     | MAXLEN |
+--------------------+----------------------+---------------------------------+--------+
| big5               | big5_chinese_ci      | Big5 Traditional Chinese        |      2 |
| dec8               | dec8_swedish_ci      | DEC West European               |      1 |
| cp850              | cp850_general_ci     | DOS West European               |      1 |
| hp8                | hp8_english_ci       | HP West European                |      1 |
......

可以使用

SHOW COLLATION LIKE '***';

命令来查看相关字符集的校对规则。

mysql> SHOW COLLATION LIKE 'gbk%';
+----------------+---------+----+---------+----------+---------+
| Collation      | Charset | Id | Default | Compiled | Sortlen |
+----------------+---------+----+---------+----------+---------+
| gbk_chinese_ci | gbk     | 28 | Yes     | Yes      |       1 |
| gbk_bin        | gbk     | 87 |         | Yes      |       1 |
+----------------+---------+----+---------+----------+---------+
2 rows in set (0.00 sec)

上面运行结果为 GBK 字符集所对应的校对规则,其中 gbk_chinese_ci 是默认的校对规则,对大小写不敏感。而 gbk_bin 按照二进制编码的值进行比较,对大小写敏感。

也可以通过查询 information_schema.COLLATIONS 表中的记录,来查看 MySQL 中可用的校对规则。SQL 语句和执行过程如下:

mysql> SELECT * FROM information_schema.COLLATIONS;
+--------------------------+--------------------+-----+------------+-------------+---------+
| COLLATION_NAME           | CHARACTER_SET_NAME | ID  | IS_DEFAULT | IS_COMPILED | SORTLEN |
+--------------------------+--------------------+-----+------------+-------------+---------+
| big5_chinese_ci          | big5               |   1 | Yes        | Yes         |       1 |
| big5_bin                 | big5               |  84 |            | Yes         |       1 |
| dec8_swedish_ci          | dec8               |   3 | Yes        | Yes         |       1 |
| dec8_bin                 | dec8               |  69 |            | Yes         |       1 |
| cp850_general_ci         | cp850              |   4 | Yes        | Yes         |       1 |
| cp850_bin                | cp850              |  80 |            | Yes         |       1 |
......

例 1

分别指定“A”和“a”按照 gbk_chinese_ci 和 gbk_bin 校对规则进行比较。SQL 语句和运行结果如下:

mysql> SELECT CASE WHEN 'A' COLLATE gbk_chinese_ci = 'a' COLLATE gbk_chinese_ci then 1
    -> else 0 end;
+-------------------------------------------------------------------------------------+
| CASE WHEN 'A' COLLATE gbk_chinese_ci = 'a' COLLATE gbk_chinese_ci then 1
else 0 end |
+-------------------------------------------------------------------------------------+
|                                                                                   1 |
+-------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

mysql> SELECT CASE WHEN 'A' COLLATE gbk_bin = 'a' COLLATE gbk_bin then 1
    -> else 0 end;
+-----------------------------------------------------------------------+
| CASE WHEN 'A' COLLATE gbk_bin = 'a' COLLATE gbk_bin then 1
else 0 end |
+-----------------------------------------------------------------------+
|                                                                     0 |
+-----------------------------------------------------------------------+
1 row in set (0.00 sec)

由于 gbk_chinese_ci 校对规则忽略大小写,所以认为两个“A“和“a”是相同的。 gbk_bin 校对规则不忽略大小写,则认为两个字符是不同的。

在实际应用中,我们应事先确认应用需要按照什么样方式排序,是否区分大小写,然后选择相应的校对规则。

5.用户管理

1.user权限表

MySQL 在安装时会自动创建一个名为 mysql 的数据库,mysql 数据库中存储的都是用户权限表。用户登录以后,MySQL 会根据这些权限表的内容为每个用户赋予相应的权限。

user 表是 MySQL 中最重要的一个权限表,用来记录允许连接到服务器的账号信息。需要注意的是,在 user 表里启用的所有权限都是全局级的,适用于所有数据库。

user 表中的字段大致可以分为 4 类,分别是用户列、权限列、安全列和资源控制列,下面主要介绍这些字段的含义。

用户列

用户列存储了用户连接 MySQL 数据库时需要输入的信息。需要注意的是 MySQL 5.7 版本不再使用 Password 来作为密码的字段,而改成了 authentication_string。

MySQL 5.7 版本的用户列如表 1 所示。

字段名字段类型是否为空默认值说明
Hostchar(60)NO主机名
Userchar(32)NO用户名
authentication_stringtextYES密码

用户登录时,如果这 3 个字段同时匹配,MySQL 数据库系统才会允许其登录。创建新用户时,也是设置这 3 个字段的值。修改用户密码时,实际就是修改 user 表的 authentication_string 字段的值。因此,这 3 个字段决定了用户能否登录。

权限列

权限列的字段决定了用户的权限,用来描述在全局范围内允许对数据和数据库进行的操作。

权限大致分为两大类,分别是高级管理权限和普通权限:

  • 高级管理权限主要对数据库进行管理,例如关闭服务的权限、超级权限和加载用户等;
  • 普通权限主要操作数据库,例如查询权限、修改权限等。

user 表的权限列包括 Select_priv、Insert_ priv 等以 priv 结尾的字段,这些字段值的数据类型为 ENUM,可取的值只有 Y 和 N:Y 表示该用户有对应的权限,N 表示该用户没有对应的权限。从安全角度考虑,这些字段的默认值都为 N。

字段名字段类型是否为空默认值说明
Select_privenum(‘N’,‘Y’)NON是否可以通过SELECT 命令查询数据
Insert_privenum(‘N’,‘Y’)NON是否可以通过 INSERT 命令插入数据
Update_privenum(‘N’,‘Y’)NON是否可以通过UPDATE 命令修改现有数据
Delete_privenum(‘N’,‘Y’)NON是否可以通过DELETE 命令删除现有数据
Create_privenum(‘N’,‘Y’)NON是否可以创建新的数据库和表
Drop_privenum(‘N’,‘Y’)NON是否可以删除现有数据库和表
Reload_privenum(‘N’,‘Y’)NON是否可以执行刷新和重新加载MySQL所用的各种内部缓存的特定命令,包括日志、权限、主机、查询和表
Shutdown_privenum(‘N’,‘Y’)NON是否可以关闭MySQL服务器。将此权限提供给root账户之外的任何用户时,都应当非常谨慎
Process_privenum(‘N’,‘Y’)NON是否可以通过SHOW PROCESSLIST命令查看其他用户的进程
File_privenum(‘N’,‘Y’)NON是否可以执行SELECT INTO OUTFILE和LOAD DATA INFILE命令
Grant_privenum(‘N’,‘Y’)NON是否可以将自己的权限再授予其他用户
References_privenum(‘N’,‘Y’)NON是否可以创建外键约束
Index_privenum(‘N’,‘Y’)NON是否可以对索引进行增删查
Alter_privenum(‘N’,‘Y’)NON是否可以重命名和修改表结构
Show_db_privenum(‘N’,‘Y’)NON是否可以查看服务器上所有数据库的名字,包括用户拥有足够访问权限的数据库
Super_privenum(‘N’,‘Y’)NON是否可以执行某些强大的管理功能,例如通过KILL命令删除用户进程;使用SET GLOBAL命令修改全局MySQL变量,执行关于复制和日志的各种命令。(超级权限)
Create_tmp_table_privenum(‘N’,‘Y’)NON是否可以创建临时表
Lock_tables_privenum(‘N’,‘Y’)NON是否可以使用LOCK TABLES命令阻止对表的访问/修改
Execute_privenum(‘N’,‘Y’)NON是否可以执行存储过程
Repl_slave_privenum(‘N’,‘Y’)NON是否可以读取用于维护复制数据库环境的二进制日志文件
Repl_client_privenum(‘N’,‘Y’)NON是否可以确定复制从服务器和主服务器的位置
Create_view_privenum(‘N’,‘Y’)NON是否可以创建视图
Show_view_privenum(‘N’,‘Y’)NON是否可以查看视图
Create_routine_privenum(‘N’,‘Y’)NON是否可以更改或放弃存储过程和函数
Alter_routine_privenum(‘N’,‘Y’)NON是否可以修改或删除存储函数及函数
Create_user_privenum(‘N’,‘Y’)NON是否可以执行CREATE USER命令,这个命令用于创建新的MySQL账户
Event_privenum(‘N’,‘Y’)NON是否可以创建、修改和删除事件
Trigger_privenum(‘N’,‘Y’)NON是否可以创建和删除触发器
Create_tablespace_privenum(‘N’,‘Y’)NON是否可以创建表空间

如果要修改权限,可以使用 GRANT 语句为用户赋予一些权限,也可以通过 UPDATE 语句更新 user 表的方式来设置权限。

安全列

安全列主要用来判断用户是否能够登录成功,user 表中的安全列如表 3 所示:

字段名字段类型是否为空默认值说明
ssl_typeenum(‘’,‘ANY’,‘X509’,‘SPECIFIED’)NO支持ssl标准加密安全字段
ssl_cipherblobNO支持ssl标准加密安全字段
x509_issuerblobNO支持x509标准字段
x509_subjectblobNO支持x509标准字段
pluginchar(64)NOmysql_native_password引入plugins以进行用户连接时的密码验证,plugin创建外部/代理用户
password_expiredenum(‘N’,‘Y’)NON密码是否过期 (N 未过期,y 已过期)
password_last_changedtimestampYES记录密码最近修改的时间
password_lifetimesmallint(5) unsignedYES设置密码的有效时间,单位为天数
account_lockedenum(‘N’,‘Y’)NON用户是否被锁定(Y 锁定,N 未锁定)

注意:即使 password_expired 为“Y”,用户也可以使用密码登录 MySQL,但是不允许做任何操作。

通常标准的发行版不支持 ssl,读者可以使用 SHOW VARIABLES LIKE “have_openssl” 语句来查看是否具有 ssl 功能。如果 have_openssl 的值为 DISABLED,那么则不支持 ssl 加密功能。

资源控制列

资源控制列的字段用来限制用户使用的资源,user 表中的资源控制列如表 4 所示。

字段名字段类型是否为空默认值说明
max_questionsint(11) unsignedNO0规定每小时允许执行查询的操作次数
max_updatesint(11) unsignedNO0规定每小时允许执行更新的操作次数
max_connectionsint(11) unsignedNO0规定每小时允许执行的连接操作次数
max_user_connectionsint(11) unsignedNO0规定允许同时建立的连接次数

以上字段的默认值为 0,表示没有限制。一个小时内用户查询或者连接数量超过资源控制限制,用户将被锁定,直到下一个小时才可以在此执行对应的操作。可以使用 GRANT 语句更新这些字段的值。

2.其他权限表

在 MySQL 数据库中,权限表除了 user 表外,还有 db 表、tables_priv 表、columns_priv 表和 procs_priv 表。

db表

db 表比较常用,是 MySQL 数据库中非常重要的权限表,表中存储了用户对某个数据库的操作权限。表中的字段大致可以分为两类,分别是用户列和权限列。

用户列

db 表用户列有 3 个字段,分别是 Host、User、Db,标识从某个主机连接某个用户对某个数据库的操作权限,这 3 个字段的组合构成了 db 表的主键。

db 表的用户列如下表所示:

字段名字段类型是否为空默认值说明
Hostchar(60)NO主机名
Dbchar(64)NO数据库名
Userchar(32)NO用户名

权限列

db 表中的权限列和 user 表中的权限列大致相同,只是user 表中的权限是针对所有数据库的,而 db 表中的权限只针对指定的数据库。如果希望用户只对某个数据库有操作权限,可以先将 user 表中对应的权限设置为 N,然后在 db 表中设置对应数据库的操作权限。

tables_priv表和columns_priv表

tables_priv 表用来对单个表进行权限设置,columns_priv 表用来对单个数据列进行权限设置。tables_priv 表结构如下表所示:

字段名字段类型是否为空默认值说明
Hostchar(60)NO主机
Dbchar(64)NO数据库名
Userchar(32)NO用户名
Table_namechar(64)NO表名
Grantorchar(93)NO修改该记录的用户
TimestamptimestampNOCURRENT_TIMESTAMP修改该记录的时间
Table_privset(‘Select’,‘Insert’,‘Update’,‘Delete’,’ Create’,‘Drop’,‘Grant’,‘References’, ‘Index’,‘Alter’,‘Create View’,‘Show view’,‘Trigger’)NO表示对表的操作权限,包括 Select、Insert、Update、Delete、Create、Drop、Grant、References、Index 和 Alter 等
Column_privset(‘Select’,‘Insert’,‘Update’,‘References’)NO表示对表中的列的操作权限,包括 Select、Insert、Update 和 References

columns_priv 表结构如下表所示:

字段名字段类型是否为空默认值说明
Hostchar(60)NO主机
Dbchar(64)NO数据库名
Userchar(32)NO用户名
Table_namechar(64)NO表名
Column_namechar(64)NO数据列名称,用来指定对哪些数据列具有操作权限
TimestamptimestampNOCURRENT_TIMESTAMP修改该记录的时间
Column_privset(‘Select’,‘Insert’,‘Update’,‘References’)NO表示对表中的列的操作权限,包括 Select、Insert、Update 和 References

procs_priv表

procs_priv 表可以对存储过程和存储函数进行权限设置,procs_priv 的表结构如表所示:

字段名字段类型是否为空默认值说明
Hostchar(60)NO主机名
Dbchar(64)NO数据库名
Userchar(32)NO用户名
Routine_namechar(64)NO表示存储过程或函数的名称
Routine_typeenum(‘FUNCTION’,‘PROCEDURE’)NO表示存储过程或函数的类型,Routine_type 字段有两个值,分别是 FUNCTION 和 PROCEDURE。FUNCTION 表示这是一个函数;PROCEDURE 表示这是一个 存储过程。
Grantorchar(93)NO插入或修改该记录的用户
Proc_privset(‘Execute’,‘Alter Routine’,‘Grant’)NO表示拥有的权限,包括 Execute、Alter Routine、Grant 3种
TimestamptimestampNOCURRENT_TIMESTAMP表示记录更新时间

3.创建用户

MySQL 在安装时,会默认创建一个名为 root 的用户,该用户拥有超级权限,可以控制整个 MySQL 服务器。

在对 MySQL 的日常管理和操作中,为了避免有人恶意使用 root 用户控制数据库,我们通常创建一些具有适当权限的用户,尽可能地不用或少用 root 用户登录系统,以此来确保数据的安全访问。

MySQL 提供了以下 3 种方法创建用户。

  1. 使用 CREATE USER 语句创建用户
  2. 在 mysql.user 表中添加用户
  3. 使用 GRANT 语句创建用户

下面根据实例详细讲解这 3 种方法。

1. 使用CREATE USER语句创建用户

可以使用

CREATE USER

语句来创建 MySQL 用户,并设置相应的密码。其基本语法格式如下:

CREATE USER <用户> [ IDENTIFIED BY [ PASSWORD ] ‘password’ ][ ,用户 [ IDENTIFIED BY [ PASSWORD ] ‘password’ ]]

参数说明如下:

1) 用户

指定创建用户账号,格式为

user_name'@'host_name

。这里的

user_name

是用户名,

host_name

为主机名,即用户连接 MySQL 时所用主机的名字。如果在创建的过程中,只给出了用户名,而没指定主机名,那么主机名默认为“%”,表示一组主机,即对所有主机开放权限。

3) IDENTIFIED BY子句

用于指定用户密码。新用户可以没有初始密码,若该用户不设密码,可省略此子句。

2) PASSWORD ‘password’

PASSWORD 表示使用哈希值设置密码,该参数可选。如果密码是一个普通的字符串,则不需要使用 PASSWORD 关键字。‘password’ 表示用户登录时使用的密码,需要用单引号括起来。

使用 CREATE USER 语句时应注意以下几点:

  • CREATE USER 语句可以不指定初始密码。但是从安全的角度来说,不推荐这种做法。
  • 使用 CREATE USER 语句必须拥有 mysql 数据库的 INSERT 权限或全局 CREATE USER 权限。
  • 使用 CREATE USER 语句创建一个用户后,MySQL 会在 mysql 数据库的 user 表中添加一条新记录。
  • CREATE USER 语句可以同时创建多个用户,多个用户用逗号隔开。

新创建的用户拥有的权限很少,它们只能执行不需要权限的操作。如登录 MySQL、使用 SHOW 语句查询所有存储引擎和字符集的列表等。如果两个用户的用户名相同,但主机名不同,MySQL 会将它们视为两个用户,并允许为这两个用户分配不同的权限集合。

例 1

使用 CREATE USER 创建一个用户,用户名是 test1,密码是 test1,主机名是 localhost。SQL 语句和执行过程如下。

mysql> CREATE USER 'test1'@'localhost' IDENTIFIED BY 'test1';
Query OK, 1 rows affected (0.06 sec)

结果显示,创建 test1 用户成功。

在实际应用中,我们应避免明文指定密码,可以通过 PASSWORD 关键字使用密码的哈希值设置密码。

例 2

在 MySQL 中,可以使用 password() 函数获取密码的哈希值,查看 test1 哈希值的 SQL 语句和执行过程如下:

mysql> SELECT password('test1');
+-------------------------------------------+
| password('test1')                         |
+-------------------------------------------+
| *06C0BF5B64ECE2F648B5F048A71903906BA08E5C |
+-------------------------------------------+
1 row in set, 1 warning (0.00 sec)

“*06C0BF5B64ECE2F648B5F048A71903906BA08E5C”就是 test1 的哈希值。下面创建用户 test1,SQL 语句和执行过程如下:

mysql> CREATE USER 'test1'@'localhost'IDENTIFIED BY PASSWORD '*06C0BF5B64ECE2F648B5F048A71903906BA08E5C';
Query OK, 0 rows affected, 1 warning (0.00 sec)

执行成功后就可以使用密码“test1”登录了。

2.使用 INSERT 语句新建用户

可以使用 INSERT 语句将用户的信息添加到 mysql.user 表中,但必须拥有对 mysql.user 表的 INSERT 权限。通常 INSERT 语句只添加 Host、User 和 authentication_string 这 3 个字段的值。

MySQL 5.7 的 user 表中的密码字段从 Password 变成了 authentication_string,如果你使用的是 MySQL 5.7 之前的版本,将 authentication_string 字段替换成 Password 即可。

使用

INSERT

语句创建用户的代码如下:

INSERT INTO mysql.user(Host, User, authentication_string, ssl_cipher, x509_issuer, x509_subject) VALUES (‘hostname’, ‘username’, PASSWORD(‘password’), ‘’, ‘’, ‘’);

由于 mysql 数据库的 user 表中,ssl_cipher、x509_issuer 和 x509_subject 这 3 个字段没有默认值,所以向 user 表插入新记录时,一定要设置这 3 个字段的值,否则 INSERT 语句将不能执行。

例 3

下面使用 INSERT 语句创建名为 test2 的用户,主机名是 localhost,密码也是 test2。SQL 语句和执行过程如下:

mysql> INSERT INTO mysql.user(Host, User, authentication_string, ssl_cipher, x509_issuer, x509_subject) VALUES ('localhost', 'test2', PASSWORD('test2'), '', '', '');
Query OK, 1 row affected, 1 warning (0.02 sec)

结果显示,新建用户成功。但是这时如果通过该账户登录 MySQL 服务器,不会登录成功,因为 test2 用户还没有生效。

可以使用 FLUSH 命令让用户生效,命令如下:

FLUSH PRIVILEGES;

使用以上命令可以让 MySQL 刷新系统权限相关表。执行 FLUSH 命令需要 RELOAD 权限。

注意:user 表中的 User 和 Host 字段区分大小写,创建用户时要指定正确的用户名称或主机名。

3. 使用GRANT语句新建用户

虽然 CREATE USER 和 INSERT INTO 语句都可以创建普通用户,但是这两种方式不便授予用户权限。于是 MySQL 提供了 GRANT 语句。

使用

GRANT

语句创建用户的基本语法形式如下:

GRANT priv_type ON database.table TO user [IDENTIFIED BY [PASSWORD] ‘password’]

其中:

  • priv_type 参数表示新用户的权限;
  • database.table 参数表示新用户的权限范围,即只能在指定的数据库和表上使用自己的权限;
  • user 参数指定新用户的账号,由用户名和主机名构成;
  • IDENTIFIED BY 关键字用来设置密码;
  • password 参数表示新用户的密码。

例 4

下面使用 GRANT 语句创建名为 test3 的用户,主机名为 localhost,密码为 test3。该用户对所有数据库的所有表都有 SELECT 权限。SQL 语句和执行过程如下:

mysql> GRANT SELECT ON*.* TO 'test3'@localhost IDENTIFIED BY 'test3';
Query OK, 0 rows affected, 1 warning (0.01 sec)

其中,“.” 表示所有数据库下的所有表。结果显示创建用户成功,且 test3 用户对所有表都有查询(SELECT)权限。

技巧:GRANT 语句是 MySQL 中一个非常重要的语句,它可以用来创建用户、修改用户密码和设置用户权限。教程后面会详细介绍如何使用 GRANT 语句修改密码、更改权限。

4.修改用户

在 MySQL中,我们可以使用

RENAME USER

语句修改一个或多个已经存在的用户账号。

语法格式如下:

RENAME USER <旧用户> TO <新用户>

其中:

  • <旧用户>:系统中已经存在的 MySQL 用户账号。
  • <新用户>:新的 MySQL 用户账号。

使用 RENAME USER 语句时应注意以下几点:

  • RENAME USER 语句用于对原有的 MySQL 用户进行重命名。
  • 若系统中旧账户不存在或者新账户已存在,该语句执行时会出现错误。
  • 使用 RENAME USER 语句,必须拥有 mysql 数据库的 UPDATE 权限或全局 CREATE USER 权限。

例 1

使用 RENAME USER 语句将用户名 test1 修改为 testUser1,主机是 localhost。SQL 语句和执行过程如下。

mysql> RENAME USER 'test1'@'localhost'
    -> TO 'testUser1'@'localhost';
Query OK, 0 rows affected (0.03 sec)

在 cmd 命令行工具中,使用 testUser1 用户登录数据库服务器,如下所示。

C:\Users\USER>mysql -h localhost -u testUser1 -p
Enter password: *****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.7.20-log MySQL Community Server (GPL)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

5.删除用户

在 MySQL 数据库中,可以使用 DROP USER 语句删除用户,也可以直接在 mysql.user 表中删除用户以及相关权限。

1. 使用 DROP USER 语句删除普通用户

使用 DROP USER 语句删除用户的语法格式如下:

DROP USER <用户1> [ , <用户2> ]…

其中,用户用来指定需要删除的用户账号。

使用 DROP USER 语句应注意以下几点:

  • DROP USER 语句可用于删除一个或多个用户,并撤销其权限。
  • 使用 DROP USER 语句必须拥有 mysql 数据库的 DELETE 权限或全局 CREATE USER 权限。
  • 在 DROP USER 语句的使用中,若没有明确地给出账户的主机名,则该主机名默认为“%”。

注意:用户的删除不会影响他们之前所创建的表、索引或其他数据库对象,因为 MySQL 并不会记录是谁创建了这些对象。

例 1

下面使用 DROP USER 语句删除用户’test1@‘localhost’。SQL 语句和执行过程如下。

mysql> DROP USER 'test1'@'localhost';
Query OK, 0 rows affected (0.00 sec)

在 cmd 命令行工具中,使用 test1 用户登录数据库服务器,发现登录失败,说明用户已经删除,如下所示。

C:\Users\USER>mysql -h localhost -u test1 -p
Enter password: ****
ERROR 1045 (28000): Access denied for user 'test'@'localhost' (using  password: YES)

2. 使用DELETE语句删除普通用户

可以使用 DELETE 语句直接删除 mysql.user 表中相应的用户信息,但必须拥有 mysql.user 表的 DELETE 权限。其基本语法格式如下:

DELETE FROM mysql.user WHERE Host=‘hostname’ AND User=‘username’;

Host 和 User 这两个字段都是 mysql.user 表的主键。因此,需要两个字段的值才能确定一条记录。

例 2

下面使用 DELETE 语句删除用户’test2’@‘localhost’。SQL 语句和执行过程如下所示。

DELETE FROM mysql.user WHERE Host='localhost'AND User='test2';
Query OK, 1 rows affected (0.00 sec)

结果显示删除成功。可以使用 SELETE 语句查询 mysql.user 表,以确定该用户是否已经成功删除。

6.查看用户权限

在 MySQL 中,可以通过查看 mysql.user 表中的数据记录来查看相应的用户权限,也可以使用 SHOW GRANTS 语句查询用户的权限。

mysql 数据库下的 user 表中存储着用户的基本权限,可以使用 SELECT 语句来查看。SELECT 语句的代码如下:

SELECT * FROM mysql.user;

要执行该语句,必须拥有对 user 表的查询权限。

注意:新创建的用户只有登录 MySQL 服务器的权限,没有任何其它权限,不能查询 user 表。

除了使用 SELECT 语句之外,还可以使用 SHOW GRANTS FOR 语句查看权限。其语法格式如下:

SHOW GRANTS FOR ‘username’@‘hostname’;

其中,username 表示用户名,hostname 表示主机名或主机 IP。

例 1

下面创建 testuser1 用户并查询权限,SQL 语句和执行过程如下:

mysql> CREATE USER 'testuser1'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GRANTS FOR 'testuser1'@'localhost';
+-----------------------------------------------+
| Grants for testuser1@localhost                |
+-----------------------------------------------+
| GRANT USAGE ON *.* TO 'testuser1'@'localhost' |
+-----------------------------------------------+
1 row in set (0.00 sec)

其中,

USAGE ON *.*

表示该用户对任何数据库和任何表都没有权限。

例 2

下面查询 root 用户的权限,代码如下:

mysql> SHOW GRANTS FOR 'root'@'localhost';
+---------------------------------------------------------------------+
| Grants for root@localhost                                           |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION        |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)

7.用户授权

授权就是为某个用户赋予某些权限。例如,可以为新建的用户赋予查询所有数据库和表的权限。MySQL 提供了 GRANT 语句来为用户设置权限。

在 MySQL 中,拥有 GRANT 权限的用户才可以执行

GRANT

语句,其语法格式如下:

GRANT priv_type [(column_list)] ON database.table
TO user [IDENTIFIED BY [PASSWORD] 'password']
[, user[IDENTIFIED BY [PASSWORD] 'password']] ...
[WITH with_option [with_option]...]

其中:

  • priv_type 参数表示权限类型;
  • columns_list 参数表示权限作用于哪些列上,省略该参数时,表示作用于整个表;
  • database.table 用于指定权限的级别;
  • user 参数表示用户账户,由用户名和主机名构成,格式是“‘username’@‘hostname’”;
  • IDENTIFIED BY 参数用来为用户设置密码;
  • password 参数是用户的新密码。

WITH 关键字后面带有一个或多个 with_option 参数。这个参数有 5 个选项,详细介绍如下:

  • GRANT OPTION:被授权的用户可以将这些权限赋予给别的用户;
  • MAX_QUERIES_PER_HOUR count:设置每个小时可以允许执行 count 次查询;
  • MAX_UPDATES_PER_HOUR count:设置每个小时可以允许执行 count 次更新;
  • MAX_CONNECTIONS_PER_HOUR count:设置每小时可以建立 count 个连接;
  • MAX_USER_CONNECTIONS count:设置单个用户可以同时具有的 count 个连接。

MySQL 中可以授予的权限

有如下几组:

  • 列权限,和表中的一个具体列相关。例如,可以使用 UPDATE 语句更新表 students 中 name 列的值的权限。
  • 表权限,和一个具体表中的所有数据相关。例如,可以使用 SELECT 语句查询表 students 的所有数据的权限。
  • 数据库权限,和一个具体的数据库中的所有表相关。例如,可以在已有的数据库 mytest 中创建新表的权限。
  • 用户权限,和 MySQL 中所有的数据库相关。例如,可以删除已有的数据库或者创建一个新的数据库的权限。

对应地,在

GRANT 语句中可用于指定权限级别的值

有以下几类格式:

  • *:表示当前数据库中的所有表。
  • .:表示所有数据库中的所有表。
  • db_name.*:表示某个数据库中的所有表,db_name 指定数据库名。
  • db_name.tbl_name:表示某个数据库中的某个表或视图,db_name 指定数据库名,tbl_name 指定表名或视图名。
  • db_name.routine_name:表示某个数据库中的某个存储过程或函数,routine_name 指定存储过程名或函数名。
  • TO 子句:如果权限被授予给一个不存在的用户,MySQL 会自动执行一条 CREATE USER 语句来创建这个用户,但同时必须为该用户设置密码。

权限类型说明

1)授予数据库权限时,<权限类型>可以指定为以下值:

权限名称对应user表中的字段说明
SELECTSelect_priv表示授予用户可以使用 SELECT 语句访问特定数据库中所有表和视图的权限。
INSERTInsert_priv表示授予用户可以使用 INSERT 语句向特定数据库中所有表添加数据行的权限。
DELETEDelete_priv表示授予用户可以使用 DELETE 语句删除特定数据库中所有表的数据行的权限。
UPDATEUpdate_priv表示授予用户可以使用 UPDATE 语句更新特定数据库中所有数据表的值的权限。
REFERENCESReferences_priv表示授予用户可以创建指向特定的数据库中的表外键的权限。
CREATECreate_priv表示授权用户可以使用 CREATE TABLE 语句在特定数据库中创建新表的权限。
ALTERAlter_priv表示授予用户可以使用 ALTER TABLE 语句修改特定数据库中所有数据表的权限。
SHOW VIEWShow_view_priv表示授予用户可以查看特定数据库中已有视图的视图定义的权限。
CREATE ROUTINECreate_routine_priv表示授予用户可以为特定的数据库创建存储过程和存储函数的权限。
ALTER ROUTINEAlter_routine_priv表示授予用户可以更新和删除数据库中已有的存储过程和存储函数的权限。
INDEXIndex_priv表示授予用户可以在特定数据库中的所有数据表上定义和删除索引的权限。
DROPDrop_priv表示授予用户可以删除特定数据库中所有表和视图的权限。
CREATE TEMPORARY TABLESCreate_tmp_table_priv表示授予用户可以在特定数据库中创建临时表的权限。
CREATE VIEWCreate_view_priv表示授予用户可以在特定数据库中创建新的视图的权限。
EXECUTE ROUTINEExecute_priv表示授予用户可以调用特定数据库的存储过程和存储函数的权限。
LOCK TABLESLock_tables_priv表示授予用户可以锁定特定数据库的已有数据表的权限。
ALL 或 ALL PRIVILEGES 或 SUPERSuper_priv表示以上所有权限/超级权限

2) 授予表权限时,<权限类型>可以指定为以下值:

权限名称对应user表中的字段说明
SELECTSelect_priv授予用户可以使用 SELECT 语句进行访问特定表的权限
INSERTInsert_priv授予用户可以使用 INSERT 语句向一个特定表中添加数据行的权限
DELETEDelete_priv授予用户可以使用 DELETE 语句从一个特定表中删除数据行的权限
DROPDrop_priv授予用户可以删除数据表的权限
UPDATEUpdate_priv授予用户可以使用 UPDATE 语句更新特定数据表的权限
ALTERAlter_priv授予用户可以使用 ALTER TABLE 语句修改数据表的权限
REFERENCESReferences_priv授予用户可以创建一个外键来参照特定数据表的权限
CREATECreate_priv授予用户可以使用特定的名字创建一个数据表的权限
INDEXIndex_priv授予用户可以在表上定义索引的权限
ALL 或 ALL PRIVILEGES 或 SUPERSuper_priv所有的权限名

3) 授予列权限时,<权限类型>的值只能指定为 SELECT、INSERT 和 UPDATE,同时权限的后面需要加上列名列表 column-list。

4) 最有效率的权限是用户权限。

授予用户权限时,<权限类型>除了可以指定为授予数据库权限时的所有值之外,还可以是下面这些值:

  • CREATE USER:表示授予用户可以创建和删除新用户的权限。
  • SHOW DATABASES:表示授予用户可以使用 SHOW DATABASES 语句查看所有已有的数据库的定义的权限。

例 1

使用 GRANT 语句创建一个新的用户 testUser,密码为 testPwd。用户 testUser 对所有的数据有查询、插入权限,并授予 GRANT 权限。SQL 语句和执行过程如下。

mysql> GRANT SELECT,INSERT ON *.*
    -> TO 'testUser'@'localhost'
    -> IDENTIFIED BY 'testPwd'
    -> WITH GRANT OPTION;
Query OK, 0 rows affected, 1 warning (0.05 sec)

使用 SHOW GRANTS 语句查询用户 testUser 的权限,如下所示。

mysql> SHOW GRANTS FOR 'testUser'@'localhost';
+-------------------------------------------------------------------------+
| Grants for testUser@localhost                                           |
+-------------------------------------------------------------------------+
| GRANT SELECT, INSERT ON *.* TO 'testUser'@'localhost' WITH GRANT OPTION |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

结果显示,testUser 对所有数据库的所有表有查询、插入权限,并可以将这些权限赋予给别的用户。

拓展

数据库管理员给普通用户授权时一定要特别小心,如果授权不当,可能会给数据库带来致命的破坏。一旦发现给用户的权限太多,应该尽快使用 REVOKE 语句将权限收回。此处特别注意,最好不要授予普通用户 SUPER 权限,GRANT 权限。

8.删除用户权限

在 MySQL 中,可以使用 REVOKE 语句删除某个用户的某些权限(此用户不会被删除),在一定程度上可以保证系统的安全性。例如,如果数据库管理员觉得某个用户不应该拥有 DELETE 权限,那么就可以删除 DELETE 权限。

使用 REVOKE 语句删除权限的语法格式有两种形式,如下所示:

1)第一种

删除用户某些特定的权限,语法格式如下:

REVOKE priv_type [(column_list)]…

ON database.table
FROM user [, user]…

REVOKE 语句中的参数与 GRANT 语句的参数意思相同。其中:

  • priv_type 参数表示权限的类型;
  • column_list 参数表示权限作用于哪些列上,没有该参数时作用于整个表上;
  • user 参数由用户名和主机名构成,格式为“username’@‘hostname’”。

2)第二种

删除特定用户的所有权限,语法格式如下:

REVOKE ALL PRIVILEGES, GRANT OPTION FROM user [, user] …

删除用户权限需要注意以下几点:

  • REVOKE 语法和 GRANT 语句的语法格式相似,但具有相反的效果。
  • 要使用 REVOKE 语句,必须拥有 MySQL 数据库的全局 CREATE USER 权限或 UPDATE 权限。

例 1

使用 REVOKE 语句取消用户 testUser 的插入权限,SQL 语句和执行过程如下。

mysql> REVOKE INSERT ON *.*
    -> FROM 'testUser'@'localhost';
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW GRANTS FOR 'testUser'@'localhost';
+-----------------------------------------------------------------+
| Grants for testUser@localhost                                   |
+-----------------------------------------------------------------+
| GRANT SELECT ON *.* TO 'testUser'@'localhost' WITH GRANT OPTION |
+-----------------------------------------------------------------+
1 row in set (0.00 sec)

结果显示,删除 testUser 用户的 INSERT 权限成功。

9.登录、退出服务器

在《登录MySQL数据库》一节,我们了解到用户可以通过 mysql 命令来登录 MySQL 服务器。但是前面还有些参数没有介绍到,本节将详细介绍 MySQL 中登录和退出服务器的方法。

启动 MySQL 服务后,可以使用以下命令来登录。

mysql -h hostname|hostlP -p port -u username -p DatabaseName -e “SQL语句”

对上述参数说明如下:

  • -h:指定连接 MySQL 服务器的地址。可以用两种方式表示,hostname 为主机名,hostIP 为主机 IP 地址。
  • -p:指定连接 MySQL 服务器的端口号,port 为连接的端口号。MySQL 的默认端口号是 3306,因此如果不指定该参数,默认使用 3306 连接 MySQL 服务器。
  • -u:指定连接 MySQL 服务器的用户名,username 为用户名。
  • -p:提示输入密码,即提示 Enter password。
  • DatabaseName:指定连接到 MySQL 服务器后,登录到哪一个数据库中。如果没有指定,默认为 mysql 数据库。
  • -e:指定需要执行的 SQL 语句,登录 MySQL 服务器后执行这个 SQL 语句,然后退出 MySQL 服务器。

例 1

下面使用 root 用户登录到 test 数据库中,命令和运行过程如下:

C:\Users\11645>mysql -h localhost -u root -p test
Enter password: ****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.29-log MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

上述命令中,通过值 localhost 指定 MySQL 服务器的地址,参数 -u 指定了登录 MySQL 服务器的用户账户,参数 -p 表示会出现输入密码提示信息,最后值“test”指定了登录成功后要使用的数据库。

由结果可以看到,输入命令后,会出现“Enter password”提示信息,在这条信息之后输入密码,然后按 Enter 键。密码正确后,就成功登录到 MySQL 服务器了。

例 2

下面使用 root 用户登录到自己计算机的 mysql 数据库,同时查询 student 表的表结构,命令和运行过程如下:

C:\Users\11645>mysql -h localhost -u root -p test -e"DESC student"
Enter password: ****
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(4)      | NO   | PRI | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| age   | int(4)      | YES  |     | NULL    |       |
| stuno | int(11)     | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

结果显示,执行命令并输入正确密码后,窗口中就会显示出 student 表的基本结构。

用户也可以直接在 mysql 命令的 -p 后加上登录密码,登录密码与 -p 之间没有空格。

例 3

下面使用 root 用户登录到自己计算机的 MySQL 服务器中,密码直接加在 mysql 命令中。命令如下:

C:\Users\11645>mysql -h localhost -u root -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.29-log MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

上述命令执行后,后面不会提示输入密码。因为 -p 后面有密码,MySQL 会直接使用这个密码。

退出 MySQL 服务器的方式很简单,只要在命令行输入 EXIT 或 QUIT 即可。“\q”是 QUIT 的缩写,也可以用来退出 MySQL 服务器。退出后就会显示 Bye。如下所示:

mysql> QUIT;
Bye

10.root修改普通用户密码

在 MySQL 中,root 用户拥有很高的权限,不仅可以修改自己的密码,还可以修改其他用户的密码。本节主要介绍 root 用户修改普通用户密码的几种方法。

使用SET语句修改普通用户的密码

在 MySQL 中,只有 root 用户可以通过更新 MySQL 数据库来更改密码。使用 root 用户登录到 MySQL 服务器后,可以使用 SET 语句来修改普通用户密码。语法格式如下:

SET PASSWORD FOR ‘username’@‘hostname’ = PASSWORD (‘newpwd’);

其中,username 参数是普通用户的用户名,hostname 参数是普通用户的主机名,newpwd 是要更改的新密码。

注意:新密码必须使用 PASSWORD() 函数来加密,如果不使用 PASSWORD() 加密,也会执行成功,但是用户会无法登录。

如果是普通用户修改密码,可省略 FOR 子句来更改自己的密码。语法格式如下:

SET PASSWORD = PASSWORD(‘newpwd’);

示例 1

首先创建一个没有密码的 testuser 用户,SQL 语句和运行结果如下:

mysql> CREATE USER 'testuser'@'localhost';
Query OK, 0 rows affected (0.14 sec)

root 用户登录 MySQL 服务器后,再使用 SET 语句将 testuser 用户的密码修改为“newpwd”,SQL 语句和运行结果如下:

mysql> SET PASSWORD FOR 'testuser'@'localhost' = PASSWORD("newpwd");
Query OK, 0 rows affected, 1 warning (0.01 sec)

由运行结果可以看出,SET 语句执行成功,testuser 用户的密码被成功设置为“newpwd”。

下面验证 testuser 用户密码是否修改成功。退出 MySQL 服务器,使用 testuser 用户登录,输入密码“newpwd”,SQL 语句和运行结果如下:

C:\Users\leovo>mysql -utestuser -p
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.29-log MySQL Community Server (GPL)
 
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
 
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

由运行结果可以看出,testuser 用户登录成功,修改密码成功。

示例 2

使用 testuser 用户登录 MySQL 服务器,再使用 SET 语句将密码更改为“newpwd1”,SQL 语句和运行结果如下所示:

mysql> SET PASSWORD = PASSWORD(‘newpwd1’);
Query OK, 0 rows affected, 1 warning (0.00 sec)

由运行结果可以看出,修改密码成功。

使用UPDATE语句修改普通用户的密码

使用 root 用户登录 MySQL 服务器后,可以使用 UPDATE 语句修改 MySQL 数据库的 user 表的 authentication_string 字段,从而修改普通用户的密码。UPDATA 语句的语法如下:

UPDATE MySQL.user SET authentication_string = PASSWORD(“newpwd”) WHERE User = “username” AND Host = “hostname”;

其中,username 参数是普通用户的用户名,hostname 参数是普通用户的主机名,newpwd 是要更改的新密码。

注意,执行 UPDATE 语句后,需要执行 FLUSH PRIVILEGES 语句重新加载用户权限。

示例 3

使用 root 用户登录 MySQL 服务器,再使用 UPDATE 语句将 testuser 用户的密码修改为“newpwd2”的 SQL 语句和运行结果如下:

mysql> UPDATE MySQL.user SET authentication_string = PASSWORD ("newpwd2")
    -> WHERE User = "testuser" AND Host = "localhost";
Query OK, 1 row affected, 1 warning (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 1
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.03 sec)

由运行结果可以看出,密码修改成功。testuser 的密码被修改成了 newpwd2。使用 FLUSH PRIVILEGES 重新加载权限后,就可以使用新的密码登录 testuser 用户了。

使用 GRANT 语句修改普通用户密码

除了前面介绍的方法,还可以在全局级别使用 GRANT USAGE 语句指定某个账户的密码而不影响账户当前的权限。需要注意的是,使用 GRANT 语句修改密码,必须拥有 GRANT 权限。一般情况下最好使用该方法来指定或修改密码。语法格式如下:

GRANT USAGE ON . TO ‘user’@’hostname’ IDENTIFIED BY ‘newpwd’;

其中,username 参数是普通用户的用户名,hostname 参数是普通用户的主机名,newpwd 是要更改的新密码。

示例 4

使用 root 用户登录 MySQL 服务器,再使用 GRANT 语句将 testuser 用户的密码修改为“newpwd3”,SQL 语句和运行结果如下:

mysql> GRANT USAGE ON *.* TO 'testuser'@'localhost' IDENTIFIED BY 'newpwd3';
Query OK, 0 rows affected, 1 warning (0.05 sec)

由运行结果可以看出,密码修改成功。

11.修改root密码

在 MySQL 中,root 用户拥有很高的权限,因此必须保证 root 用户密码的安全。修改 root 用户密码的方式有很多种,本节将介绍几种常用的修改 root 用户密码的方法。

使用mysqladmin命令在命令行指定新密码

root 用户可以使用 mysqladmin 命令来修改密码,mysqladmin 的语法格式如下:

mysqladmin -u username -h hostname -p password “newpwd”

语法参数说明如下:

  • usermame 指需要修改密码的用户名称,在这里指定为 root 用户;
  • hostname 指需要修改密码的用户主机名,该参数可以不写,默认是 localhost;
  • password 为关键字,而不是指旧密码;
  • newpwd 为新设置的密码,必须用双引号括起来。如果使用单引号会引发错误,可能会造成修改后的密码不是你想要的。

执行完上面的语句,root 用户的密码将被修改为“newpwd”。

示例 1

下面使用 mysqladmin 将 root 用户的密码修改为“rootpwd”,在 Windows 命令行窗口(cmd)中执行命令和运行结果如下:

C:\Users\leovo>mysqladmin -u root -p password "rootpwd"
Enter password: ****
mysqladmin: [Warning] Using a password on the command line interface can be insecure.
Warning: Since password will be sent to server in plain text, use ssl connection to ensure password safety.

输入 mysqladmin 命令后,按回车键,然后输入 root 用户原来的密码。执行完毕后,密码修改成功,root 用户登录时将使用新的密码。

运行结果中,输入密码后会提示在命令行界面上使用密码可能不安全的警告信息,因为在命令行输入密码时,MySQL 服务器就会提示这些安全警告信息。

下面使用修改后的“rootpwd”密码登录 root 用户,SQL 语句和运行结果如下:

C:\Users\leovo>mysql -uroot -p
Enter password: *******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 23
Server version: 5.7.29-log MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

结果显示,root 用户登录成功,所以使用 mysqladmin 命令修改 root 用户密码成功。

修改MySQL数据库的user表

因为所有账户信息都保存在 user 表中,因此可以直接通过修改 user 表来改变 root 用户的密码。

root 用户登录到 MySQL 服务器后,可以使用 UPDATE 语句修改 MySQL 数据库的 user 表的 authentication_string 字段,从而修改用户的密码。

使用 UPDATA 语句修改 root 用户密码的语法格式如下:

UPDATE mysql.user set authentication_string = PASSWORD ("rootpwd) WHERE User = “root” and Host=“localhost”;

新密码必须使用 PASSWORD() 函数来加密。执行UPDATE语句后,需要执行FLUSH PRIVILEGES语句重新加载用户权限。

示例 2

下面使用 UPDATE 语句将 root用户的密码修改为“rootpwd2”。

使用 root 用户登录到 MySQL 服务器后,SQL 语句和运行结果如下所示:

mysql> UPDATE mysql.user set authentication_string = password ("rootpwd2")
    -> WHERE User = "root" and Host = "localhost";
Query OK, 1 row affected, 0 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings:0
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.06 sec)

结果显示,密码修改成功。而且使用了

FLUSH PRIVILEGES;

语句加载权限。退出后就必须使用新密码来登录了。

使用SET语句修改root用户的密码

SET PASSWORD 语句可以用来重新设置其他用户的登录密码或者自己使用的账户的密码。使用 SET 语句修改密码的语法结构如下:

SET PASSWORD = PASSWORD (“rootpwd”);

示例 3

下面使用 SET 语句将 root 用户的密码修改为“rootpwd3”。

使用 root 用户登录到 MySQL 服务器后,SQL 语句和运行结果如下所示:

MySQL> SET PASSWORD = password ("rootpwd3");
Query OK, 0 rows affected (0.00 sec)

结果显示,SET 语句执行成功,root 用户的密码被成功设置为“rootpwd3”。

12.修改密码的3种方式

在使用数据库时,我们也许会遇到

MySQL

需要修改密码的情况,比如密码太简单需要修改等。本节主要介绍了 3 种修改 MySQL 数据库密码的方法。

1. 使用 SET PASSWORD 命令

步骤 1):输入命令

mysql -u root -p

指定 root 用户登录 MySQL,输入后按回车键输入密码。如果没有配置环境变量,请在 MySQL 的 bin 目录下登录操作。

步骤 2):使用 SET PASSWORD 修改密码命令格式为

set password for username @localhost = password(newpwd);

,其中 username 为要修改密码的用户名,newpwd 为要修改的新密码。如图所示。
在这里插入图片描述

步骤 3):输入

quit;

命令退出 MySQL 重新登录,输入新密码“root”登录就可以了;

2. 使用mysqladmin修改密码

使用 mysqladmin 命令修改 MySQL 的 root 用户密码格式为

mysqladmin -u用户名 -p旧密码 password 新密码

注意:下图修改密码的命令中 -uroot 和 -proot 是整体,不要写成 -u root -p root,-u 和 root 间可以加空格,但是会有警告出现,所以就不要加空格了。

在这里插入图片描述

3. UPDATE直接编辑user表

步骤 1):输入命令

mysql -u root -p

指定 root 用户登录 MySQL,输入后按回车键输入密码。如果没有配置环境变量,请在 MySQL 的 bin 目录下登录操作。

步骤 2):输入

use mysql;

命令连接权限数据库。

步骤 3):输入命令

update mysql.user set authentication_string=password('新密码') where user='用户名' and Host ='localhost';

设置新密码。

步骤 4):输入

flush privileges;

命令刷新权限。

步骤 5):输入

quit;

命令退出 MySQL 重新登录,此时密码已经修改为刚才输入的新密码了。

十二、数据库备份与恢复

任何数据库都需要备份,备份数据是维护数据库必不可少的操作。

在学习如何备份数据之前,我们先了解一下数据库备份是为了应对哪些场景?为什么数据库需要备份?

备份就是为了防止原数据丢失,保证数据的安全。当数据库因为某些原因造成部分或者全部数据丢失后,备份文件可以帮我们找回丢失的数据。因此,数据备份是很重要的工作。

常见数据库备份的应用场景如下。

数据丢失应用场景:

  • 人为操作失误造成某些数据被误操作
  • 软件 BUG 造成部分数据或全部数据丢失
  • 硬件故障造成数据库部分数据或全部数据丢失
  • 安全漏洞被入侵数据恶意破坏

非数据丢失应用场景:

  • 特殊应用场景下基于时间点的数据恢复
  • 开发测试环境数据库搭建
  • 相同数据库的新环境搭建
  • 数据库或者数据迁移

以上列出的是一些数据库备份常见的应用场景,数据库备份还有其它应用场景,这里就不一一列举了。

比如磁盘故障导致整个数据库所有数据丢失,并且无法从已经出现故障的硬盘上面恢复出来时,可以通过最近时间的整个数据库的物理或逻辑备份数据文件,尽可能的将数据恢复到故障之前最近的时间点。

操作失误造成数据被误操作后,我们需要有一个能恢复到错误操作时间点之前的瞬间的备份文件存在,当然这个备份可能是整个数据库的备份,也可以仅仅只是被误操作的表的备份。

1.备份类型

备份是以防万一的一种必要手段,在出现硬件损坏或非人为的因素而导致数据丢失时,可以使用备份恢复数据,以将损失降低到最小程度,因此备份是必须的。备份可以分为以下几个类型。

根据备份的方法(是否需要数据库离线)可以将备份分为:

  • 热备(Hot Backup)
  • 冷备(Cold Backup)
  • 温备(Warm Backup)

热备份可以在数据库运行中直接备份,对正在运行的数据库操作没有任何的影响,数据库的读写操作可以正常执行。这种方式在 MySQL 官方手册中称为 Online Backup(在线备份)。

冷备份必须在数据库停止的情况下进行备份,数据库的读写操作不能执行。这种备份最为简单,一般只需要复制相关的数据库物理文件即可。这种方式在 MySQL 官方手册中称为 Offline Backup(离线备份)。

温备份同样是在数据库运行中进行的,但是会对当前数据库的操作有所影响,备份时仅支持读操作,不支持写操作。

按照备份后文件的内容,热备份又可以分为:

  • 逻辑备份
  • 裸文件备份

在 MySQL 数据库中,逻辑备份是指备份出的文件内容是可读的,一般是文本内容。内容一般是由一条条 SQL 语句,或者是表内实际数据组成。如 mysqldump 和 SELECT * INTO OUTFILE 的方法。这类方法的好处是可以观察导出文件的内容,一般适用于数据库的升级、迁移等工作。但其缺点是恢复的时间较长。

裸文件备份是指复制数据库的物理文件,既可以在数据库运行中进行复制(如 ibbackup、xtrabackup 这类工具),也可以在数据库停止运行时直接复制数据文件。这类备份的恢复时间往往比逻辑备份短很多。

按照备份数据库的内容来分,备份又可以分为:

  • 完全备份
  • 部分备份

完全备份是指对数据库进行一个完整的备份,即备份整个数据库,如果数据较多会占用较大的时间和空间。

部分备份是指备份部分数据库(例如,只备份一个表)。

部分备份又分为:

  • 增量备份
  • 差异备份

增量备份需要使用专业的备份工具。指的是在上次完全备份的基础上,对更改的数据进行备份。也就是说每次备份只会备份自上次备份之后到备份时间之内产生的数据。因此每次备份都比差异备份节约空间,但是恢复数据麻烦。

差异备份指的是自上一次完全备份以来变化的数据。和增量备份相比,浪费空间,但恢复数据比增量备份简单。

MySQL 中进行不同方式的备份还要考虑存储引擎是否支持,如 MyISAM 不支持热备,支持温备和冷备。而 InnoDB 支持热备、温备和冷备。

一般情况下,我们需要备份的数据分为以下几种:

  • 表数据
  • 二进制日志、InnoDB 事务日志
  • 代码(存储过程、存储函数、触发器、事件调度器)
  • 服务器配置文件

下面是几种常用的备份工具:

  • mysqldump:逻辑备份工具,适用于所有的存储引擎,支持温备、完全备份、部分备份、对于 InnoDB 存储引擎支持热备。
  • cp、tar 等归档复制工具:物理备份工具,适用于所有的存储引擎、冷备、完全备份、部分备份。
  • lvm2 snapshot:借助文件系统管理工具进行备份。
  • mysqlhotcopy:名不副实的一个工具,仅支持 MyISAM 存储引擎。
  • xtrabackup:一款由 percona 提供的非常强大的 InnoDB/XtraDB 热备工具,支持完全备份、增量备份。

2.数据库备份

数据库的主要作用就是对数据进行保存和维护,所以备份数据是数据库管理中最常用的操作。为了防止数据库意外崩溃或硬件损伤而导致的数据丢失,数据库系统提供了备份和恢复策略。

保证数据安全的最重要的一个措施就是定期的对数据库进行备份。这样即使发生了意外,也会把损失降到最低。

数据库备份是指通过导出数据或者复制表文件的方式来制作数据库的副本。当数据库出现故障或遭到破坏时,将备份的数据库加载到系统,从而使数据库从错误状态恢复到备份时的正确状态。

MySQL 中提供了两种备份方式,即 mysqldump 命令以及 mysqlhotcopy 脚本。由于 mysqlhotcopy 只能用于 MyISAM 表,所以 MySQL 5.7 移除了 mysqlhotcopy 脚本。

本节主要介绍如何使用 mysqldump 命令备份数据库。

mysqldump 命令执行时,可以将数据库中的数据备份成一个文本文件。数据表的结构和数据将存储在生成的文本文件中。

备份一个数据库

使用 mysqldump 命令备份一个数据库的语法格式如下:

mysqldump -u username -p dbname [tbname ...]> filename.sql

对上述语法参数说明如下:

  • username:表示用户名称;
  • dbname:表示需要备份的数据库名称;
  • tbname:表示数据库中需要备份的数据表,可以指定多个数据表。省略该参数时,会备份整个数据库;
  • 右箭头“>”:用来告诉 mysqldump 将备份数据表的定义和数据写入备份文件;
  • filename.sql:表示备份文件的名称,文件名前面可以加绝对路径。通常将数据库备份成一个后缀名为.sql的文件。

注意:mysqldump 命令备份的文件并非一定要求后缀名为

.sql

,备份成其他格式的文件也是可以的。例如,后缀名为

.txt

的文件。通常情况下,建议备份成后缀名为

.sql

的文件。因为,后缀名为

.sql

的文件给人第一感觉就是与数据库有关的文件

例 1

下面使用 root 用户备份 test 数据库下的 student 表。打开命令行(cmd)窗口,输入备份命令和密码,运行过程如下:

C:\Windows\system32>mysqldump -uroot -p test student>C:\student.sql
Enter password: ****

注意:mysqldump 命令必须在 cmd 窗口下执行,不能登录到 MySQL 服务中执行。

输入密码后,MySQL 会对 test 数据库下的 student 数据表进行备份。之后就可以在指定路径下查看刚才备份过的文件了。student.sql 文件中的部分内容如下:

– MySQL dump 10.13 Distrib 5.7.29, for Win64 (x86_64)

– Host: localhost Database: test


– Server version 5.7.29-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT /;
/
!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS /;
–此处删除了部分内容

– Table structure for table student

DROP TABLE IF EXISTS student;
/
!40101 SET @saved_cs_client = @@character_set_client /;
/
!40101 SET character_set_client = utf8 /;
CREATE TABLE student (
id int(4) NOT NULL,
name varchar(20) DEFAULT NULL,
stuno int(11) DEFAULT NULL,
age int(4) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/
!40101 SET character_set_client = @saved_cs_client /;

– Dumping data for table student

LOCK TABLES student WRITE;
/
!40000 ALTER TABLE student DISABLE KEYS /;
INSERT INTO student VALUES (1,‘zhangsan’,23,18),(2,‘lisi’,24,19),(3,‘wangwu’,25,18),(4,‘zhaoliu’,26,18);
/
!40000 ALTER TABLE student ENABLE KEYS /;
UNLOCK TABLES;
/
!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

– Dump completed on 2019-03-09 13:03:15

student.sql 文件开头记录了 MySQL 的版本、备份的主机名和数据库名。

文件中,以“–”开头的都是 SQL 语言的注释。以 “/!40101” 等形式开头的是与 MySQL 有关的注释。40101 是 MySQL 数据库的版本号,这里就表示 MySQL 4.1.1。如果恢复数据时,MySQL 的版本比 4.1.1 高,“/!40101”和“/”之间的内容被当作 SQL 命令来执行。如果比 4.1.1 低,“/!40101”和“/”之间的内容被当作注释。“/!”和“*/”中的内容在其它数据库中将被作为注释忽略,这可以提高数据库的可移植性。

DROP 语句、CREATE 语句和 INSERT 语句都是数据库恢复时使用的;“DROP TABLE IF EXISTS ‘student’ ”语句用来判断数据库中是否还有名为 student 的表,如果存在,就删除这个表;CREATE 语句用来创建 student 表;INSERT 语句用来恢复所有数据。文件的最后记录了备份的时间。

注意:上面 student.sql 文件中没有创建数据库的语句,因此,student.sql 文件中的所有表和记录必须恢复到一个已经存在的数据库中。恢复数据时,CREATE TABLE 语句会在数据库中创建表,然后执行 INSERT 语句向表中插入记录。

备份多个数据库

如果要使用 mysqldump 命令备份多个数据库,需要使用 --databases 参数。备份多个数据库的语法格式如下:

mysqldump -u username -P --databases dbname1 dbname2 ... > filename.sql

加上“–databases”参数后,必须指定至少一个数据库名称,多个数据库名称之间用空格隔开。

例 2

下面使用 root 用户备份 test 数据库和 mysql 数据库。命令如下:

mysqldump -u root -p --databases test mysql>C:\testandmysql.sql

执行完后,可以在

C:\

下面看到名为 testandmysql.sql 的文件,这个文件中存储着这两个数据库的信息。

备份所有数据库

mysqldump 命令备份所有数据库的语法格式如下:

mysqldump -u username -P --all-databases>filename.sql

使用“–all-databases”参数时,不需要指定数据库名称。

例 3

下面使用 root 用户备份所有数据库。命令如下:

mysqldump -u root -p --all-databases > C:\all.sql

执行完后,可以在

C:\

下面看到名为 all.sql 的文件,这个文件中存储着所有数据库的信息。

3.恢复数据库

当数据丢失或意外损坏时,可以通过恢复已经备份的数据来尽量减少数据的丢失和破坏造成的损失。本节主要介绍如何对备份的数据进行恢复操作。

在《MySQL mysqldump备份数据库》一节中介绍了如何使用 mysqldump 命令将数据库中的数据备份成一个文本文件,且备份文件中通常包含 CREATE 语句和 INSERT 语句。

在 MySQL 中,可以使用 mysql 命令来恢复备份的数据。mysql 命令可以执行备份文件中的 CREATE 语句和 INSERT 语句,也就是说,mysql 命令可以通过 CREATE 语句来创建数据库和表,通过 INSERT 语句来插入备份的数据。

mysql 命令语法格式如下:

mysql -u username -P [dbname] < filename.sql

其中:

  • username 表示用户名称;
  • dbname 表示数据库名称,该参数是可选参数。如果 filename.sql 文件为 mysqldump 命令创建的包含创建数据库语句的文件,则执行时不需要指定数据库名。如果指定的数据库名不存在将会报错;
  • filename.sql 表示备份文件的名称。

注意:mysql 命令和 mysqldump 命令一样,都直接在命令行(cmd)窗口下执行。

例 1

下面使用 root 用户恢复所有数据库,命令如下:

mysql -u root -p < C:\all.sql

执行完后,MySQL 数据库就已经恢复了 all.sql 文件中的所有数据库。

注意:如果使用

--all-databases

参数备份了所有的数据库,那么恢复时不需要指定数据库。因为,其对应的 sql 文件中含有 CREATE DATABASE 语句,可以通过该语句创建数据库。创建数据库之后,可以执行 sql 文件中的 USE 语句选择数据库,然后在数据库中创建表并且插入记录。

4.导出数据表

通过对数据表的导入导出,可以实现 MySQL 数据库服务器与其它数据库服务器间移动数据。导出是指将 MySQL 数据表的数据复制到文本文件。数据导出的方式有多种,本节主要介绍使用 SELECTI…INTO OUTFILE 语句导出数据。

在 MySQL 中,可以使用 SELECTI…INTO OUTFILE 语句将表的内容导出成一个文本文件。SELECT…INTO OUTFILE 语句基本格式如下:

SELECT 列名 FROM table [WHERE 语句] INTO OUTFILE ‘目标文件’[OPTIONS]

该语句用 SELECT 来查询所需要的数据,用 INTO OUTFILE 来导出数据。其中,

目标文件

用来指定将查询的记录导出到哪个文件。这里需要注意的是,目标文件不能是一个已经存在的文件。

[OPTIONS] 为可选参数选项,OPTIONS 部分的语法包括 FIELDS 和 LINES 子句,其常用的取值有:

  • FIELDS TERMINATED BY ‘字符串’:设置字符串为字段之间的分隔符,可以为单个或多个字符,默认情况下为制表符‘\t’。
  • FIELDS [OPTIONALLY] ENCLOSED BY ‘字符’:设置字符来括上 CHAR、VARCHAR 和 TEXT 等字符型字段。如果使用了 OPTIONALLY 则只能用来括上 CHAR 和 VARCHAR 等字符型字段。
  • FIELDS ESCAPED BY ‘字符’:设置如何写入或读取特殊字符,只能为单个字符,即设置转义字符,默认值为‘\’。
  • LINES STARTING BY ‘字符串’:设置每行开头的字符,可以为单个或多个字符,默认情况下不使用任何字符。
  • LINES TERMINATED BY ‘字符串’:设置每行结尾的字符,可以为单个或多个字符,默认值为‘\n’ 。

注意:FIELDS 和 LINES 两个子句都是自选的,但是如果两个都被指定了,FIELDS 必须位于 LINES的前面。

示例 1

下面使用 SELECT…INTO OUTFILE 语句来导出 test 数据库中的 person 表中的记录。SQL 语句和运行结果如下:

mysql> SELECT * FROM test.person INTO OUTFILE 'C://ProgramData/MySQL/MySQL Server 5.7/Uploads/person.txt';
Query OK, 5 rows affected (0.05 sec)

然后根据导出的路径找到 person.txt 文件,文件内容如下:

1    Java 12
2    MySQL     13
3    C      15
4    C++  22
5    Python     18

导出 person 表数据成功。

注意:导出时可能会出现下面的错误:

The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

这是因为MySQL 限制了数据的导出路径。MySQL 导入导出文件只能在 secure-file-priv 变量的指定路径下的文件才可以导入导出。

有以下 2 种解决办法:

  1. 首先使用
show variables like '%secure%';

语句查看 secure-file-priv 变量配置。

mysql> show variables like '%secure%' \G
*************************** 1. row ***************************
Variable_name: require_secure_transport
        Value: OFF
*************************** 2. row ***************************
Variable_name: secure_auth
        Value: ON
*************************** 3. row ***************************
Variable_name: secure_file_priv
        Value: C:\ProgramData\MySQL\MySQL Server 5.7\Uploads\
3 rows in set, 1 warning (0.04 sec)

secure_file_priv 的值指定的是 MySQL 导入导出文件的路径。将 SQL 语句中的导出文件路径修改为该变量的指定路径,再执行导入导出操作即可。也可以在 my.ini 配置文件中修改 secure-file-priv 的值,然后重启服务即可。

  1. 如果 secure_file_priv 值为 NULL,则为禁止导出,可以在 MySQL 安装路径下的 my.ini 文件中添加
secure_file_priv=设置路径

语句,然后重启服务即可。

示例 2

使用 SELECT…INTO OUTFILE 语句将 test 数据库中的 person 表中的记录导出到文本文件,使用 FIELDS 选项和 LINES 选项,要求字段之间用

隔开,字符型数据用双引号括起来。每条记录以

-

开头。SQL 语句如下:

SELECT * FROM test.person INTO OUTFILE 'C:/person.txt'
    FIELDS TERMINATED BY '\、' OPTIONALLY ENCLOSED BY '\"' LINES STARTING BY '\-'
TERMINATED BY '\r\n';

其中:

  • FIELDS TERMINATED BY '、’:表示字段之间用分隔;
  • ENCLOSED BY ‘"’:表示每个字段都用双引号括起来;
  • LINES STARTING BY ‘-’:表示每行以-开头;
  • TERMINATED BY ‘\r\n’ 表示每行以回车换行符结尾,保证每一条记录占一行。

person.txt 文件内容如下:

-1、"Java"、12
-2、"MySQL"、13
-3、"C"、15
-4、"C++"、22
-5、"Python"、18

可以看到,每条记录都以

-

开头,每个数据之间以都以

隔开,所有的字段值都被双引号包括。

5.数据库恢复

数据库恢复是指以备份为基础,与备份相对应的系统维护和管理操作。

系统进行恢复操作时,先执行一些系统安全性的检查,包括检查所要恢复的数据库是否存在、数据库是否变化及数据库文件是否兼容等,然后根据所采用的数据库备份类型采取相应的恢复措施。

数据库恢复机制设计的两个关键问题是:第一,如何建立冗余数据;第二,如何利用这些冗余数据实施数据库恢复。

建立冗余数据最常用的技术是数据转储和登录日志文件。通常在一个数据库系统中,这两种方法是一起使用的。

数据转储是 DBA 定期地将整个数据库复制到磁带或另一个磁盘上保存起来的过程。这些备用的版本成为后备副本或后援副本。

可使用 LOAD DATA…INFILE 语句来恢复先前备份的数据。

【实例】将之前导出的数据备份文件 file.txt 导入数据库 test_db 的表 tb_students_copy 中,其中 tb_students_copy 的表结构和 tb_students_info 相同。

首先创建表 tb_students_copy,输入的 SQL 语句和执行结果如下所示。

mysql> CREATE TABLE tb_students_copy
    -> LIKE tb_students_info;
Query OK, 0 rows affected (0.52 sec)
mysql> SELECT * FROM tb_students_copy;
Empty set (0.00 sec)

导入数据与查询表 tb_students_copy 的过程如下所示。

mysql> LOAD DATA INFILE 'C:/ProgramData/MySQL/MySQL Server 5.7/
Uploads/file.txt'
    -> INTO TABLE test_db.tb_students_copy
    -> FIELDS TERMINATED BY ','
    -> OPTIONALLY ENCLOSED BY '"'
    -> LINES TERMINATED BY '?';
Query OK, 10 rows affected (0.14 sec)
Records: 10  Deleted: 0  Skipped: 0  Warnings: 0
mysql> SELECT * FROM test_db.tb_students_copy;
+----+--------+---------+------+------+--------+------------+
| id | name   | dept_id | age  | sex  | height | login_date |
+----+--------+---------+------+------+--------+------------+
|  1 | Dany   |       1 |   25 | F    |    160 | 2015-09-10 |
|  2 | Green  |       3 |   23 | F    |    158 | 2016-10-22 |
|  3 | Henry  |       2 |   23 | M    |    185 | 2015-05-31 |
|  4 | Jane   |       1 |   22 | F    |    162 | 2016-12-20 |
|  5 | Jim    |       1 |   24 | M    |    175 | 2016-01-15 |
|  6 | John   |       2 |   21 | M    |    172 | 2015-11-11 |
|  7 | Lily   |       6 |   22 | F    |    165 | 2016-02-26 |
|  8 | Susan  |       4 |   23 | F    |    170 | 2015-10-01 |
|  9 | Thomas |       3 |   22 | M    |    178 | 2016-06-07 |
| 10 | Tom    |       4 |   23 | M    |    165 | 2016-08-05 |
+----+--------+---------+------+------+--------+------------+
10 rows in set (0.00 sec)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值