该文章前面会简单介绍MySQL的一些基本知识,如已了解,可以直接跳到C语言 MySQL API章节,C++有支持MySQL的库,但是仍然选择C语言库,主要目的是为了练习用C++对C代码进行封装。
文章目录
- 数据库简介
- MySQL初步认识
- SQL
- 数据完整性
- [DQL 之复杂查询](#DQL 之复杂查询)
- 数据库的备份和恢复
- [C 语言的 MySQL API](#C 语言的 MySQL API)
-
- 相关结构体
- 常用函数介绍
-
- `mysql_init()`
- `mysql_real_connect()`
- `mysql_select_db`函数
- [`mysql_query()` / `mysql_real_query()`](#
mysql_query()/mysql_real_query()) - 获取查询结果
- `mysql_fetch_row()`
- [`mysql_free_result()` / `mysql_close()`](#
mysql_free_result()/mysql_close())
- [C 语言 API 示例代码](#C 语言 API 示例代码)
数据库简介
在实际的开发过程中,应用程序会有大量的数据需要持久地存储在磁盘之中,目前我们已知的持久化存储的方式就是采用文件,但是在磁盘物理的结构上,文件其实是一种顺序的字符集合。使用文件可以很方便地输入和输出数据,但是这些数据都是简单的字节流,不包含任何复杂数据结构,所以应用开发者需要花费大量的时间去解析文件的内容,并将其映射到内存的数据结构以供应用程序后续使用。除此之外,由于磁盘的顺序读写的速度要远远快于随机读写,所以为了访问位置靠后的数据,要消耗大量的时间(类似于链表的访问)。另外,文件系统的数据无法保证数据的安全性,应用程序没有办法在存储数据的时候检查其是否正确,而当部分数据损坏时,文件系统本身无法定位问题和设计修复方案。
数据库是"按照数据结构来组织、存储和管理数据的仓库"。它是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库具有以下特点:
- 数据结构化:数据库系统实现了整体数据的结构化,数据之间具有联系。
- 数据的共享性高,冗余度低,易扩充:数据是面向整体的,可以被多个用户、多个应用程序共享使用,大大减少了数据冗余,节约了存储空间。
- 数据独立性高:数据独立性包括数据的物理独立性和逻辑独立性。物理独立性是指数据在磁盘上的存储方式由数据库管理系统(DBMS)管理,用户程序不需要了解;逻辑独立性是指用户的应用程序与数据库的逻辑结构是相互独立的,当数据的逻辑结构改变时,用户程序可以不需要改变。
- 数据由DBMS统一管理和控制:DBMS必须提供数据的安全性保护、完整性检查、并发访问控制和故障恢复等功能。
数据库分类
数据库可以根据不同的分类标准划分为多种类型。当前的数据库可以大致分为两类:关系型数据库和非关系型数据库。
关系型数据库使用关系来组织数据库当中的各个数据,所谓的关系可以认为就是有若干行和列构成的表格。
由于关系型数据库便于理解易于使用,所以长期以来是市场上的主流数据库模式。关系型数据库通常会采用一个名为SQL(Structured Query Language,结构化查询语言)的语言来访问和更新。现在市场占用率比较高的关系型数据库有这些:Oracle、MySQL、PostgreSQL、DB2、Informix、Sybase、SQL Server 2000、Access、SQLite。Oracle是商业数据库,性能优异但是收费昂贵,一般用于传统行业和银行领域;MySQL和PostgreSQL是开源的关系型数据库管理系统,被大多数互联网公司采用。
非关系型数据库现在一般统称为NoSQL,由于在市场竞争中始终无法取代关系型数据库,现在一般将其解释为"Not only SQL",从而表示非关系型数据库是关系型数据的补充之义。NoSQL一般认为不适合作为通用领域的存储部件,但是在一些特定领域,比如高并发领域等等还是有一定的用武之地。常见的NoSQL产品有:Redis、Memcached、MongoDB、Cassandra等等。NoSQL的存储结构各有千秋,不过一般都会支持key-value的键值对存储结构。
我们使用的是目前数据库排行榜上排名第二的关系型数据库MySQL.
数据库排行榜的链接是: https://db-engines.com/en/ranking,大家可以自行查看。
MySQL发展历史
MySQL 是一款开源的关系型数据库管理系统 (RDBMS),其历史可以追溯到1994年。当时,两位瑞典工程师 Michael Widenius 和 David Axmark 在瑞典乌普萨拉大学的计算机科学系工作,他们开始着手于一个轻量级的数据库项目,命名为 mSQL(miniSQL)。
在经过几年的发展和改善后,Widenius 和 Axmark 开始寻求将他们的项目扩展为更为完整的数据库系统。1995 年,MySQL 1.0版正式发布,MySQL 取代了 mSQL 成为这个新项目的名称。MySQL 的目标是成为一个功能强大的数据库管理系统,能够处理大规模数据集,并支持多种应用程序。(MySQL这个名字据说是Widenius根据自己女儿的名字来命名的。)
MySQL 最初的版本只包含一些基本的功能,例如查询语言和表结构定义,但随着时间的推移,MySQL 逐渐成熟,并逐步扩展了许多实用和复杂的功能。其中,最令人印象深刻的改变之一是增加了支持存储过程和触发器的功能。
自 MySQL 首次发布以来,就一直是一款免费开源的数据库软件,用户可以根据自己的需要下载、安装和使用 MySQL。由于其可以跨多个平台运行(包括 Windows、Linux、Unix等),因此 MySQL 在各个行业中得到了广泛的应用。
2010 年 Oracle 公司收购了 MySQL AB 公司,这标志着 MySQL 成为了 Oracle 公司的子公司,MySQL 也成为了 Oracle 公司旗下的关键产品之一。通过 Oracle 公司的支持,MySQL 不断发展壮大,并成为了世界上最受欢迎的关系型数据库管理系统之一。
以下是MySQL发展的几个关键阶段:
早期发展(1994-1999)
- 1994年,Michael Widenius 和 David Axmark 开始开发 mSQL(miniSQL),这是 MySQL 的前身。
- 1995年,发布MySQL1.0版本,mSQL正式更名为MySQL。
- 1996年,MySQL 3.11.1发布,直接跳过了MySQL 2.x版本。
- 1999年,MySQL AB 公司成立,专门负责 MySQL 的开发和商业支持。
- 开源时代(2000-2008)
- 2000年,ISAM升级为MyISAM存储引擎,MySQL基于GPL协议开放源码。
- MySQL 的开源模式吸引了大量的社区贡献者,他们通过提交补丁、修复错误和增加新功能来推动 MySQL 的发展。
- 2002年,MySQL 4.0发布,集成了后来大名鼎鼎的InnoDB存储引擎。
- 2005年,MySQL 5.0发布,开始支持游标、存储过程、触发器、视图、XA事务等特性。
- 2008年,Sun Microsystems公司以10亿美金收购MySQL AB。同年,发布MySQL 5.1,开始支持定时器(Event scheduler)、分区、基于行的复制等特性。
- 所有权变更与继续发展(2009年至今)
- 2009年,Oracle公司以74亿美金收购Sun公司,从而间接获得了MySQL的所有权。
- 2010 年,MySQL 5.5 正式版发布,Oracle 完成了大量改进,并将 InnoDB 改成默认引擎。
- 2018年,MySQL 8.0 发布,带来了许多重要的改进,包括事务日志持久性、窗口函数、更好的 JSON 支持和更高级的安全特性。
MySQL初步认识
当MySQL数据库系统安装完毕之后,会以守护进程的方式启动一个mysqld 服务器,默认端口是3306。
------ 3306端口是MySQL的默认客户端连接端口,33060是MySQL8.0+新增的X plugin端口(不用管)。
基本结构
以下是我们对于MySQL数据库产品的一个基本结构的认识:
与MySQL服务器的三种交互方式
我们可以使用三种方式来与MySQL服务器进行交互:
- 命令行方式
- 图形化界面方式
- C语言API方式
命令行方式以下是通过命令行来连接MySQL服务器的操作:
cpp
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
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 c</urrent input statement.
mysql>
ctrl + L实现清屏
再输入quit,就可以退出与MySQL服务器的连接
mysqlmysql>quit Bye
图形化界面方式
其实图形化界面的客户端程序有多种,比如Navicat Premium、SQLyog等。如图为SQLyog界面:

在刚接触数据库时,我们更推荐使用命令行方式来学习,毕竟后续会涉及到大量的SQL语句的使用,在熟悉了这些语句后,再来使用图形化界面工具会更好。
C语言API方式

关系模型
在表的结构(关系模型)中,关系、元组、属性和域是核心概念,以下是它们各自的定义和相互之间的关系。
关系(Relation) :
关系在数据库中表示为一个二维表,这个二维表由行和列组成。它实际上描述了数据之间的一种逻辑结构或联系。一个关系就是一个数据表,包含了多个元组(记录)和多个属性(字段)。
元组(Tuple) :
在关系模型中,元组是关系(表)中的一行,代表了一个具体的实体或记录。元组是关系中的单个实例,由多个属性值组成。在二维表中,每一行就是一个元组。
属性(Attribute) :
属性是关系(表)中的一列,代表了实体的某个特征或属性。每个属性都有一个特定的名称和数据类型,用于描述实体的某个方面。在二维表中,每一列就是一个属性。
域(Domain) :
域是属性的取值范围,即属性可以取到的值的集合。它定义了属性的数据类型和可能的取值范围。例如,一个表示年龄的属性的域可能是0到150之间的整数。
关系、元组、属性和域之间的关系可以用以下方式描述:
关系是由多个元组组成的,每个元组都是关系中的一个实例。
每个元组由多个属性值组成,这些属性值对应于关系中的各个属性。
每个属性都有一个特定的域,定义了该属性可以取到的值的范围。
简单来说,关系描述了一个数据表的结构,元组是表中的具体记录,属性是表中的列(即数据的特征),而域则定义了属性的取值范围。这四个概念共同构成了关系模型的基础。
SQL
SQL(Structured Query Language)即结构化查询语言,它是一种高级的非过程化编程语言,或者说是一种声明式语言,其最初的数学来源是关系代数和元组关系演算。它主要用于存取数据以及查询、更新和管理关系数据库系统。
所谓**声明式语言(Declarative Language)**是一种编程范式,它主要关注于描述程序所期望的结果,而不是详细地描述如何获取这个结果。在声明式语言中,程序员不需要明确地列出每一步的执行细节,而是描述出问题的性质,以及求解该问题所需的约束条件。声明式语言的主要特点包括:
- 描述性:程序员通过描述问题的性质和约束条件来编写程序,而不是详细地列出执行步骤。
- 自动执行:一旦声明了问题的性质和约束条件,系统会自动决定如何执行这些声明以得到结果。
- 易于理解和维护:由于声明式语言更关注于问题的本质而不是具体的实现细节,因此它通常比命令式语言更容易理解和维护。
- 抽象程度高。
与声明式语言相对的是命令式语言(Imperative Language),如C、C++、Java等,它们要求程序员明确地指定执行步骤和顺序。
由于SQL是一种声明式语言,SQL允许用户在高层数据结构上工作,而无需了解数据的具体存放方式。SQL具有以下主要特点:
非过程化:用户只需要描述所需的数据,而不需要指定如何获取这些数据,具体的存取路径和操作过程由数据库管理系统自动完成。
面向集合:SQL采用集合操作方式,一次可以处理多个数据项。
语言简洁:SQL的语法接近英语口语,易于学习和使用。
功能强大:SQL提供了丰富的数据处理功能,包括数据定义 、数据操纵 、数据查询 和数据控制等。
接下来,我们重点看看SQL语言所包含的功能。SQL语言主要包含DDL、DML和DQL语言。DDL ,即数据定义语言(Data Definition Language):通过CREATE 、ALTER 和>DROP等语句,用于创建、修改和删除数据库、表、索引等数据库对象。
DML ,即数据操纵语言(Data Manipulation Language):通过INSERT 、UPDATE 和DELETE等语句,用于向表中添加数据、修改表中的数据以及从表中删除数据。
DQL ,即数据查询语言(Data Query Language):这是SQL的核心功能,通过SELECT语句,用户可以根据各种条件从数据库中检索数据。
数据库相关操作
查看数据库
当我们通过命令行连接到了MySQL数据库服务器后,想要查看有哪些数据库,可以使用如下语句
mysqlmysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec)从上述结果观察,我们发现使用SQL语句,有以下特点:
- 一个查询语句总是以分号结尾,换行并不意味着结束输入。
- 一个查询语句的返回结果是以二维表的形式出现的,第一行会显示每一列的属性名。
- 一个查询语句的返回结果还会告知结果的行数和执行时间,以便粗略的了解性能。
另外,SQL语句中的关键字是大小写无关的,以上语句也可以采用大写形式,其结果是相同的。
mysqlmysql> SHOW DATABASES;如果接下来想使用某一数据库,则可以使用如下语句:
mysqlmysql> use mysql;
创建数据库
指令
sql
create database 数据库名;
创建一个数据库
mysql客户端与服务端之间的通信依靠socket,本质上还是一种请求-响应的模型(客户端给服务器发请求,服务器给客户端响应)
创建数据库背后有一些细节值得关注(如下图,左侧是字符集右侧是字符校对集)

我们先来看看MySQL中创建数据库的语法规则:
sqlCREATE DATABASE [IF NOT EXISTS] 数据库名 [CHARACTER SET 字符集] [COLLATE 排序规则];\[\]内的内容代表可选。
最简
sqlCREATE DATABASE [IF NOT EXISTS] db_name如果需要指定字符集和排序规则,可以使用
sqlCREATE DATABASE [IF NOT EXISTS] db_name [DEFAULT] CHARACTER SET 字符集名称 COLLATE 排序规则名称如:
mysqlmysql> CREATE DATABASE wangdao DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
字符集和字符校对集(了解)
查看字符集
查看MySQL中支持的字符集
mysqlmysql> SHOW CHARACTER SET; +----------+---------------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+---------------------------------+---------------------+--------+ | armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 | | ascii | US ASCII | ascii_general_ci | 1 | | big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 | | binary | Binary pseudo charset | binary | 1 | | cp1250 | Windows Central European | cp1250_general_ci | 1 | | cp1251 | Windows Cyrillic | cp1251_general_ci | 1 | | cp1256 | Windows Arabic | cp1256_general_ci | 1 | | cp1257 | Windows Baltic | cp1257_general_ci | 1 | | cp850 | DOS West European | cp850_general_ci | 1 | | cp852 | DOS Central European | cp852_general_ci | 1 | | cp866 | DOS Russian | cp866_general_ci | 1 | | cp932 | SJIS for Windows Japanese | cp932_japanese_ci | 2 | | utf8mb4 | UTF-8 Unicode | utf8mb4_0900_ai_ci | 4 | +----------+---------------------------------+---------------------+--------+
查看字符校对集
查看字符校对集,也就是字符集排序规则
mysqlmysql> SHOW COLLATION; +-------------------------+----------+-----+---------+----------+---------+---------------+ | Collation | Charset | Id | Default | Compiled | Sortlen | Pad_attribute | +-------------------------+----------+-----+---------+----------+---------+---------------+ | armscii8_bin | armscii8 | 64 | | Yes | 1 | PAD SPACE | | armscii8_general_ci | armscii8 | 32 | Yes | Yes | 1 | PAD SPACE | | ascii_bin | ascii | 65 | | Yes | 1 | PAD SPACE | | ascii_general_ci | ascii | 11 | Yes | Yes | 1 | PAD SPACE | | big5_bin | big5 | 84 | | Yes | 1 | PAD SPACE | | big5_chinese_ci | big5 | 1 | Yes | Yes | 1 | PAD SPACE |注意:数据库的名字是大小写敏感的。
如何理解字符集与校对集?假设你手上有一本中文词典:
- 字符集 :决定了这本词典里收录了哪些汉字 (是简体字、繁体字,还是生僻字),以及每个字在计算机里用什么二进制编码存储。比如
utf8mb4字符集,就包含了世界上绝大多数语言的文字符号。 - 校对集 :决定了这本词典里汉字的排列顺序 和查找规则。是按拼音排?还是按部首笔画排?查找时,大小写是否区分?"a"和"A"算不算同一个字?
字符集utf8mb4
MySQL8.0默认使用的字符集是utf8mb4,该字符集包含了Emoji表情,即MySQL8.0可以存储Emoji表情。而MySQL5.7是无法存储Emoji表情的。
对于Emoji表情,它在Unicode中的编码是U+1F600,在存储时,这个编码会转换成一个或多个字节。以UTF8编码为例,它是一种可变长度的Unicode编码,它可以用1-4个字节来表示一个Unicode字符。由于Emoji表情属于Unicode中的辅助平面字符,因此它通常是需要4个字节来进行编码和存储的。
Emoji表情的链接: https://www.emojiall.com/zh-hans/all-emojis
字符校对集utf8mb4_0900_ai_ci
utf8mb4_0900_ai_ci是 MySQL 8.0 之后数据库的一种排序规则(collation)。这个名称包含了几个关键部分,我们可以逐一解释它们:
- utf8mb4:这是一个字符集(charset)的名称,用于存储 Unicode 字符。utf8mb4 是 utf8 的一个超集,支持存储四字节的 Unicode 字符,这意味着它可以存储更多的字符,包括某些表情符号和特殊字符。
- 0900:这部分对应的是 Unicode 9.0 的规范。它表示这个排序规则是基于 Unicode 9.0 的标准来定义的。
- ai:表示 "accent insensitivity",也就是"不区分音调"。这意味着在排序和比较时,字符的重音(或音调)将被忽略。
- ci:表示 "case insensitivity",也就是"不区分大小写"。在排序和比较时,字符的大小写将被忽略。
utf8mb4_0900_ai_ci排序规则的优点和原因包括:
- 支持更多的字符 :
utf8mb4_0900_ai_ci字符集支持存储和处理包括表情符号、特殊符号和罕见字符在内的更多 Unicode 字符。- 更准确的排序方式:这个排序规则是根据 Unicode 标准制定的,它提供了更准确和一致的排序方式。
在 MySQL 8.0 之后的版本中,
utf8mb4_0900_ai_ci被设置为默认的排序规则,取代了之前版本的utf8mb4_general_ci。这是因为utf8mb4_0900_ai_ci提供了更广泛的 Unicode 支持和更准确的排序方式。然而,当将 MySQL 8.0 版本的表导入到 MySQL 5.7 或更低版本时,可能会遇到字符集无法识别的问题,因为这些旧版本可能不支持
utf8mb4_0900_ai_ci排序规则。
修改数据库
当需要修改数据库的字符集和排序规则时,可以使用ALTER语句。
sqlALTER DATABASE 数据库名 DEFAULT CHARACTER SET 字符集名称 COLLATE 排序规则;注意:如想要修改数据库的名字,是不支持直接修改的。
一般的做法是创建一个新库,然后将旧库中的所有表剪切到新的库中
sqlRENAME TABLE 旧库名.student TO 新库名.student;
删除数据库
当需要删除数据库时,可以使用DROP语句。
sqlDROP DATABASE 数据库名字;

这四个是系统数据库,不能删除!!!
-
mysql(核心元数据库)
存储数据库实例的运行时元数据,主要包括:用户账号与权限信息(user、db 等表)、时区、存储引擎配置及服务器日志等。该库是 MySQL 正常启动与身份认证的基础,不可删除或随意修改。
-
information_schema(标准化信息视图)
提供对数据库服务器元数据的标准化只读访问。包含所有数据库中的表、列、索引、约束等结构信息,基于 SQL 标准实现,是一组虚拟视图,无物理文件,不可修改。
-
performance_schema(性能监控接口)
用于低开销监控数据库运行时的性能指标。记录当前会话、锁等待、I/O 操作、内存使用、SQL 语句执行状态等实时数据,所有数据驻留于内存中,重启后重置,仅支持只读查询。
-
sys(性能诊断辅助视图)
提供对 performance_schema 和 information_schema 数据的优化组织形式。以可读性更高的视图、存储过程及函数形式,封装了常用性能诊断逻辑(如慢查询分析、锁等待检测),仅作为查询接口,不存储业务数据。
数据类型
在数据库中,数据类型是确保数据完整性、准确性、高效性和易用性的重要因素。
以下是常见的数据类型。
| 分类 | 数据类型 | 说明 |
|---|---|---|
| 数值类型 | BIT(M) | 位类型,M指定位数,范围1-64 |
| BOOL,BOOLEAN | 使用1或0表示真或假 | |
| TINYINT UNSIGNED ZEROFILL | 1个字节。带符号的范围 -128 到127 无符号的范围0到255 | |
| SMALLINT UNSIGNED ZEROFILL | 2个字节。 带符号的范围-32768到32767 无符号的范围0到65535 | |
| MEDIUMINT UNSIGNED ZEROFILL | 3个字节。 带符号的范围-8388608到8388607 无符号的范围0到16777215 | |
| INTUNSIGNED ZEROFILL | 4个字节。 带符号的范围-2147483648到2147483647 无符号的范围0到4294967295 | |
| BIGINT UNSIGNED ZEROFILL | 8个字节。 带符号的范围-2^63到 2^63 - 1 无符号的范围0到2^64 - 1 | |
| FLOAT(M,D) | 单精度浮点数,M指定显示长度,D指 定小数位数 | |
| DOUBLE(M,D) | 双精度浮点数 | |
| 文本、二进制类型 | CHAR(size) | 固定长度字符串,size表示有多少个字符 |
| VARCHAR(size) | 可变长度字符串,size表示最大有多少个字符 | |
| BLOB、LONGBLOB | 二进制数据,比如图片、视频等 | |
| TEXT、LONGTEXT | 文本字符串 | |
| 日期和时间 | DATE | 日期(YYYY-MM-DD) |
| TIME | 时间(HH:MM:SS) | |
| DATETIME | 日期和时间的组合 | |
| TIMESTAMP | 时间戳 |
注意:
- 关于数据类型中,字符串CHAR和VARCHAR是最常用的,其他的比如BLOB和TEXT就不推荐使用了。需要存图片的话,直接存BLOB这种巨大的二进制文件会非常影响性能,一般是数据库存图片的路径,图片本身还是存在磁盘上。
表结构相关操作
在上面的演示中,我们已经创建了一个wangdao数据库,如果想知道该数据库中有哪些表
使用语句
show tables:
mysqlmysql> show tables; +-------------------+ | Tables_in_wangdao | +-------------------+ | char_type | | test_deadlock | +-------------------+ 2 rows in set (0.00 sec)
创建表
在MySQL中,创建表的基本语法是CREATE TABLE语句,其语法规则如下:
sqlCREATE TABLE 表名 ( filed1 datatype, filed2 datatype, ..., filedn datatype );如我们想要添加一张表student,可以使用如下语句:
mysqlCREATE TABALE student( id INT, name VARCHAR(20), age INT, birth DATE, chinese INT );注意:在创建表时,表名和属性是大小写敏感的。
囊括表中属性的是小括号,不是大括号!
查看表结构
所谓查看表结构,即查看表中有几个列或属性或字段,每个字段的数据类型。其查看的方式有两种:
- DESC tb_name;
- SHOW CREATE TABLE tb_name;
比如我们想要查看student表结构,直接使用DESC语句
mysqlmysql> DESC student; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 5 rows in set (0.00 sec)
Field 字段名称
Type 字段数据类型
Null 是否允许为 NULL(YES/NO)
Key 键类型(PRI 主键 / UNI 唯一键 / MUL 普通索引)
Default 字段默认值
Extra 额外属性
第二种方式,则是使用SHOW CREAT TABLE语句了。
mysqlmysql> SHOW CREATE TABLE student; +---------+--------------------------------------------------------+ | Table | Create Table | +---------+--------------------------------------------------------+ | student | CREATE TABLE `student` ( `id` int DEFAULT NULL, `name` varchar(20) DEFAULT NULL, `age` int DEFAULT NULL, `birth` date DEFAULT NULL, `chinese` int DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +---------+--------------------------------------------------------+ 1 row in set (0.00 sec)
显示的内容更全一些,还包括了表的核心配置:存储引擎(InnoDB)、字符集、排序规则。
修改表结构
当创建了表结构之后,假设需要对其中的属性进行添加、修改和删除操作,则必须要使用ALTER TABLE语句了,其语法规则如下:
mysql# 添加属性 ALTER TABLE tb_name ADD field datatype [, field datatype]; # 修改属性名字 ALTER TABLE tb_name change old_field new_filed datatype; # 修改属性的数据类型 ALTER TABLE tb_name MODIFY filed datatype; # 删除属性 ALTER TABLE tb_name DROP filed;给学生表添加属性math
mysqlmysql> ALTER TABLE student ADD math INT; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> DESC student; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | int | YES | | NULL | | | math | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec)修改学生表中属性name的名字为s_name
mysqlmysql> ALTER TABLE student CHANGE name s_name VARCHAR(20); Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> DESC student; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | s_name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | int | YES | | NULL | | | math | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec) mysql>修改学生表中属性chinese的数据类型为float
mysqlmysql> ALTER TABLE student MODIFY chinese FLOAT(5,2); Query OK, 0 rows affected, 1 warning (0.03 sec) Records: 0 Duplicates: 0 Warnings: 1 mysql> DESC student; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | s_name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | float(5,2) | YES | | NULL | | | math | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec)删除学生表中的属性chinese
mysqlmysql> ALTER TABLE student DROP chinese; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> DESC student; +--------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | s_name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | math | int | YES | | NULL | | +--------+-------------+------+-----+---------+-------+ 5 rows in set (0.00 sec)
数据操纵语言DML
DML (Data Manipulation Language) 是 SQL (Structured Query Language) 的一部分,用于处理数据库中的数据。与 DDL (Data Definition Language) 不同,DML 主要关注数据的插入、更新、删除和查询。
插入(添加)数据
往表中插入数据,使用INSERT语句,其语法规则:
cppINSERT INTO tb_name [(field1, field2, ...)] VALUES(value1, value2, ...);情况一:对指定列进行数据的添加
cpp# 一次添加1行数据 mysql> INSERT INTO student(name,age,birth,chinese) VALUES('dijia', 20, '2000-01-01', 80); Query OK, 1 row affected (0.00 sec) # 查看表中的数据情况 mysql> SELECT * FROM student; +------+-------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+-------+------+------------+---------+------+ | NULL | dijia | 20 | 2000-01-01 | 80 | NULL | +------+-------+------+------------+---------+------+ 1 row in set (0.00 sec) # 一次添加多行数据 mysql> INSERT INTO student(name,age,birth,chinese) -> VALUES('taijia', 21, '2001-02-02', 90), -> ('leiou', 22, '2002-03-03', 70); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 # 查看表中的数据情况 mysql> SELECT * FROM student; +------+--------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+--------+------+------------+---------+------+ | NULL | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | +------+--------+------+------------+---------+------+ 3 rows in set (0.00 sec)情况二:对所有列进行数据的添加
这种情况依然可以采用情况一的方式来添加,不过由于是对所有列都添加数据,所以可以进行简化操作,INSERT语句中可以不再指定列了,但添加数据时,必须要将所有列的数据全部加上,否则会报错。
cppmysql> DESC student; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | int | YES | | NULL | | | math | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec) # 一次添加一行数据 mysql> INSERT INTO student VALUES(1, 'oubu', 22, '2003-04-04', 50, 55); mysql> SELECT * FROM student; +------+--------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+--------+------+------------+---------+------+ | NULL | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | +------+--------+------+------------+---------+------+ 4 rows in set (0.00 sec) # 一次添加多行数据 mysql> INSERT INTO student VALUES(2, 'beisuosi', 24, '2004-05-05', 90, 95), -> (3, 'aguru', 26, '2005-06-06', 77, 66); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> SELECT * FROM student; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | NULL | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 6 rows in set (0.00 sec)
更新数据
更新表中的数据,使用UPDATE语句,其语法规则:
sqlUPDATE tb_name SET field1=expr1 [, field2=expr2 ] [WHERE ];如:将student表中名字为"dijia"的id更新为4
mysqlmysql> UPDATE student SET id=4 WHERE name='dijia'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT * FROM student; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 6 rows in set (0.00 sec)
接下来,我们要演示删除数据了。但在删除数据之前,我们先复制一份数据作为备份,是更好的选择。所以我们先来看看如何复制一张表。
复制表
关于复制表,有两种情况需要考虑:
情况一:复制表结构,但并不复制数据
mysqlCREATE TABLE new_tbname LIKE old_tbname;如:
mysqlmysql> CREATE TABLE student2 LIKE student; Query OK, 0 rows affected (0.01 sec) mysql> SELECT * FROM student2; Empty set (0.00 sec) mysql> DESC student2; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | id | int | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | age | int | YES | | NULL | | | birth | date | YES | | NULL | | | chinese | int | YES | | NULL | | | math | int | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec)
情况二:复制表结构和数据
sqlCREATE TABLE new_tbname SELECT * FROM old_tbname;如:
mysqlmysql> CREATE TABLE student3 SELECT * FROM student; Query OK, 6 rows affected (0.01 sec) Records: 6 Duplicates: 0 Warnings: 0 mysql> SELECT * FROM student3; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 6 rows in set (0.00 sec)
删除数据
当需要删除数据时,是以行为单位的。可以使用DELETE语句或者TRUNCATE语句,其语法规则:
sql# 根据条件删除数据 DELETE FROM tb_name [ WHERE ]; # 删除表中的所有数据 TRUNCATE tb_name;接下来,我们在student3表中删除数据
测试1: 删除名字为"taijia"的记录
mysqlmysql> DELETE FROM student3 WHERE name='taijia'; Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM student3; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 5 rows in set (0.00 sec)测试2:删除数学成绩为NULL的记录
mysqlmysql> DELETE FROM student3 WHERE math is NULL; Query OK, 2 rows affected (0.01 sec) mysql> SELECT * FROM student3; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 3 rows in set (0.00 sec)测试3:删除student3表中的所有数据
mysqlmysql> TRUNCATE student3; Query OK, 0 rows affected (0.01 sec) mysql> SELECT * FROM student3; Empty set (0.00 sec)
数据查询语言DQL
数据查询语言(Data Query Language, DQL)是数据库管理系统(DBMS)中用于从数据库中检索或查询数据的语言部分。在关系型数据库管理系统(RDBMS)中,最广泛使用的数据查询语言是SQL(Structured Query Language)的查询部分。
简单查询
简单查询针对于单表而言。DQL的基本结构是以
SELECT语句开始,其语法规则如下:
sqlSELECT field1, fiedl2, ... FROM tb_name [WHERE condition] [ORDER BY] [LIMIT ];
SELECT子句
在SQL中,
SELECT子句是SELECT语句的核心部分,用于指定从数据库表中检索哪些列的数据。如果要查看表中的所有数据,则不需要指定属性,直接用通配符* 就可以,它代表要查看所有列。
mysqlmysql> SELECT * FROM student; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+----------+------+------------+---------+------+ 6 rows in set (0.00 sec)如我们只想要查看student表中的name和chinese两个属性,则可以使用如下语句
mysqlmysql> SELECT name,chinese FROM student; +----------+---------+ | name | chinese | +----------+---------+ | dijia | 80 | | taijia | 90 | | leiou | 70 | | oubu | 50 | | beisuosi | 90 | | aguru | 77 | +----------+---------+ 6 rows in set (0.00 sec)
DISTINCT 去除重复值
当查询时,如果结果集中有重复值时,我们需要去重,就可以在SELECT关键字之后加上DISTINCT,且只能在SELECT之后,不能放在属性中间。
mysqlmysql> SELECT DISTINCT chinese FROM student; +---------+ | chinese | +---------+ | 80 | | 90 | | 70 | | 50 | | 77 | +---------+ 5 rows in set (0.00 sec)
当distinct作用于单个字段时,去除重复的列即可;
当distinct作用于多个字段时,表示的是多个字段都相同时,才会去除;
distinct关键字必须要放在第一个字段前面,否则会报错。
使用表达式
我们还可以使用表达式对列中的数据进行计算。
mysqlmysql> SELECT name, chinese + math FROM student; +----------+----------------+ | name | chinese + math | +----------+----------------+ | dijia | NULL | | taijia | NULL | | leiou | NULL | | oubu | 105 | | beisuosi | 185 | | aguru | 143 | +----------+----------------+ 6 rows in set (0.00 sec)也可以用 AS 对表达式取一个别名
mysqlmysql> SELECT name, chinese + math as total FROM student; +----------+-------+ | name | total | +----------+-------+ | dijia | NULL | | taijia | NULL | | leiou | NULL | | oubu | 105 | | beisuosi | 185 | | aguru | 143 | +----------+-------+ 6 rows in set (0.00 sec)
**注意:**select语句得到的结果中出现的列不一定存在数据表中。
数据的存储和数据的展示是两回事
ORDER By子句
选择一个字段进行排序
mysqlmysql> SELECT * FROM student ORDER BY chinese; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +--------+----------+-----+------------+---------+--------+ 6 rows in set Time: 0.008s
默认情况下是升序ASC
如下两条语句是等价的
sqlSELECT * FROM student ORDER BY chinese; SELECT * FROM student ORDER BY chinese ASC;
降序排列用DESC
mysqlmysql> SELECT * FROM student ORDER BY chinese DESC; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | +--------+----------+-----+------------+---------+--------+ 6 rows in set Time: 0.007s
如果选择多个字段,第一个是主排序的字段,后续的依次副排序的字段
mysqlmysql> SELECT * FROM student ORDER BY age ASC, chinese DESC; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +--------+----------+-----+------------+---------+--------+ 6 rows in set Time: 0.011s
WHERE子句
在SQL中, Where子句是很重要的组成部分,它用来过滤查询结果。你可以指定一个或多个条件,来进行查询结果的过滤。
比较运算符
Where子句中支持多种比较运算符,如= 等号
<> 或 != 不等于
> 大于
< 小于
>= 大于或等于
<= 小于或等于
如:
sqlmysql> select * from student where math > 80; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +------+----------+------+------------+---------+------+ 1 row in set (0.00 sec)
范围查找
between ... and ... 常常一起使用来指定一个值的范围,这个范围包括两个边界的值。
如:
sqlmysql> select * from student where chinese between 60 and 80; +------+-------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+-------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+-------+------+------------+---------+------+ 3 rows in set (0.00 sec)
逻辑运算符
在
Where子句中,可以使用多种逻辑运算符来组合或修改条件,包括AND: 所有条件都必须为真
OR: 至少有一个条件为真
NOT: 条件为假
如:
sqlmysql> select * from student where chinese > 80 and age > 20; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +------+----------+------+------------+---------+------+ 2 rows in set (0.00 sec)
mysqlmysql> select * from student where chinese > 80 or age > 20; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +--------+----------+-----+------------+---------+--------+ 4 rows in set Time: 0.008s
mysqlmysql> select * from student where not age > 20; +--------+-------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+-------+-----+------------+---------+--------+ | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | +--------+-------+-----+------------+---------+--------+ 2 rows in set Time: 0.007s
模糊查询
我们还可以使用
LIKE进行模糊查询,包括% 表示任意数量的字符
_ 表示一个字符
如:
sqlmysql> select * from student where name LIKE '%b%'; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +------+----------+------+------------+---------+------+ 2 rows in set (0.00 sec)如:
mysqlmysql> select * from student where name like '_e%'; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +--------+----------+-----+------------+---------+--------+ 2 rows in set
IN指定多个值
如果想要查询某个字段是否包含在一组值中,可以直接使用
IN运算符。如:
sqlmysql> select * from student where id IN (1, 3, 10); +------+-------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+-------+------+------------+---------+------+ | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +------+-------+------+------------+---------+------+ 2 rows in set (0.00 sec)
判空操作
在SQL中,判空操作通常用于检查某个字段是否为
NULL。NULL在SQL中表示字段没有值,这与字段被设置为0,空字符串('')或其他默认值是不同的。如:
sqlmysql> select * from student where math is NULL; +------+--------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+--------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | +------+--------+------+------------+---------+------+ 3 rows in set (0.00 sec)
分页查询
在SQL中,分页查询通常用于从大量数据中检索一部分数据,以便在前端页面进行分页显示。在MySQL中,分页查询采用LIMIT子句来实现,它有以下几种情况。
LIMIT m 表示获取前m条记录
LIMIT m,n 表示偏移m条记录后,获取n条记录
LIMIT m OFFSET n 表示偏移n条记录后,获取m条记录
如:
sqlmysql> select * from student; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +--------+----------+-----+------------+---------+--------+ mysql> select * from student limit 3; +------+--------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+--------+------+------------+---------+------+ | 4 | dijia | 20 | 2000-01-01 | 80 | NULL | | NULL | taijia | 21 | 2001-02-02 | 90 | NULL | | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | +------+--------+------+------------+---------+------+ 3 rows in set (0.00 sec) mysql> select * from student limit 2,3; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +------+----------+------+------------+---------+------+ 3 rows in set (0.00 sec) mysql> select * from student limit 3 offset 2; +------+----------+------+------------+---------+------+ | id | name | age | birth | chinese | math | +------+----------+------+------------+---------+------+ | NULL | leiou | 22 | 2002-03-03 | 70 | NULL | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | +------+----------+------+------------+---------+------+ 3 rows in set (0.00 sec)
数据完整性
数据完整性(Data Integrity)是数据库管理中的一个核心概念,它指的是数据的准确性和一致性。在数据库系统中,数据完整性确保数据在传输、存储和处理过程中保持其原始性和正确性,没有未经授权的修改或破坏。
数据完整性包括三个主要方面:实体完整性、域完整性和参照完整性。
实体完整性
实体完整性确保表中的每一行都是唯一的,并且具有唯一的标识符(通常是一个主键)。这意味着在表中不能有重复的行,并且主键字段不能包含NULL值。
主键
在SQL中,主键(Primary Key)用于唯一标识数据库表中的每一行记录。它可以是一个或多个字段的组合,
其具备以下特点:
- 唯一: 主键的值必须是唯一的,不允许有重复的值。
- 非空:主键字段不能包含
NULL值- 主键可以加快查询速度:。
主键的作用
(1)物理层面:数据的"物理组织者"
这也是主键和唯一约束最本质的区别。在 MySQL 的 InnoDB 引擎中,主键决定了数据在磁盘上的物理存储顺序。
- 有主键 :表就是一棵巨大的 B+ 树(聚簇索引) 。主键是这棵树的"目录"。数据行(一整行记录)直接挂在 B+ 树最底层的叶子节点上,严格按照主键值的顺序排列。
- 无主键 :MySQL 会自动创建一个 6 字节隐藏列
ROW_ID来充当"隐式主键"去组织数据。数据按插入顺序堆积在磁盘上。没有显式主键,表在磁盘上就是一个"无头苍蝇"式的堆表,维护成本极高。
如果创建表时设置了主键,那么主键在创建时会自动建立索引,这也有助于提高查询速度
(2)逻辑层面:行级数据的"身份证号"
主键作为行的唯一标识,具备三个刚性特征:
- 唯一性(Unique):绝对不允许重复。这是和唯一约束重合的功能。
- 非空性(Not Null) :绝对不允许为
NULL。这是主键和唯一约束最直接的语法区别(唯一约束允许 NULL)。- 稳定性 :一旦设定,严禁修改。因为修改主键值相当于把一个人的身份证号改了,会导致数据库在内核层面进行大量的数据页分裂和移动,性能极差且极易产生数据碎片。
(3)关系层面:维系整个数据库的"骨架"
主键作为外键引用的唯一目标:子表的外键理应引用的是父表的主键。只有主键能代表实体的唯一身份。
注意: 以后只要创建表的时候,都要设置主键,不能没有主键。主键能够保持记录的唯一,不能有重复的记录。 建议都使用整型数据设置主键。
比如,在之前的表中,把id设为主键是比较合适的,因为每一条记录的id都是不同的。
设置主键
(1)当创建表时,设置主键
sqlmysql> CREATE TABLE employee( id INT PRIMARY KEY, name VARCHAR(20), age INT, balance INT );或者采用以下方式
sqlmysql> CREATE TABLE employee( id INT, name VARCHAR(20), age INT, balance INT, PRIMARY KEY(id) );在上面的表中,id字段被设置为主键了。
(2)当表已经存在时,想要设置主键则需要修改字段。
sql# 当id不存在时 mysql> ALTER TABLE student ADD id INT PRIMARY KEY; # 当id存在,但没有设置为主键时 mysql> ALTER TABLE student MODIFY id INT PRIMARY KEY; # 或者 mysql> ALTER TABLE student ADD PRIMARY KEY(id);
主键自增
设置了主键后,在插入数据时,必须要指定不重复的主键,而有时是无法直接知道主键要设置为多少的。
因此,一般情况下,可以将主键设置为自动增长的,在MySQL中需要使用关键字AUTO_INCREMENT。
如:
sql#当前 mysql> select * from student; +--------+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +--------+----------+-----+------------+---------+--------+ | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | <null> | taijia | 21 | 2001-02-02 | 90 | <null> | | <null> | leiou | 20 | 2002-03-03 | 70 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +--------+----------+-----+------------+---------+--------+ #先将id中的null补全 UPDATE student SET id = 5 WHERE id IS NULL LIMIT 1; UPDATE student SET id = 6 WHERE id IS NULL LIMIT 1; +----+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +----+----------+-----+------------+---------+--------+ | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | 5 | taijia | 21 | 2001-02-02 | 90 | <null> | | 6 | leiou | 20 | 2002-03-03 | 70 | <null> | | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | +----+----------+-----+------------+---------+--------+ #把id设为非空主键 ALTER TABLE student MODIFY id INT NOT NULL PRIMARY KEY; #给id字段添加自增属性 ALTER TABLE student MODIFY id INT AUTO_INCREMENT; #再添加一行,不指定id的记录 mysql> insert into student(name,age,birth,chinese,math) values( -> 'gaiya',27,'1999-7-3',88,89); #新记录的id为7(在已有记录中最大的id的基础上递增) +----+----------+-----+------------+---------+--------+ | id | name | age | birth | chinese | math | +----+----------+-----+------------+---------+--------+ | 1 | oubu | 22 | 2003-04-04 | 50 | 55 | | 2 | beisuosi | 24 | 2004-05-05 | 90 | 95 | | 3 | aguru | 26 | 2005-06-06 | 77 | 66 | | 4 | dijia | 20 | 2000-01-01 | 80 | <null> | | 5 | taijia | 21 | 2001-02-02 | 90 | <null> | | 6 | leiou | 20 | 2002-03-03 | 70 | <null> | | 7 | gaiya | 27 | 1999-07-03 | 88 | 89 | +----+----------+-----+------------+---------+--------+ 7 rows in set**注意:**设置了主键之后(无论有没有设置自增约束),表中存储的数据会改变存储的顺序,原本是根据插入顺序排序,现在是根据主键id的升序排序。
底层原理是表的重建:原本没设置主键,mysql会产生"隐形主键",也就是数据插入顺序。显式设置了主键之后,底层创建了一张新表,旧表的数据迁移到新表,按照主键升序排列。最后用新表取代旧表。
sqlmysql> insert into student(id,name,age,birth,chinese,math) valu -> es(7,'daina',27,'1999-7-3',88,89); #id=7的记录已经存在,主键必须唯一 (1062, "Duplicate entry '7' for key 'student.PRIMARY'") #添加成功(或者这里直接利用id的自增约束,不需要指定id,自然会是8) mysql> insert into student(id,name,age,birth,chinese,math) valu -> es(8,'daina',27,'1999-7-3',88,89); Query OK, 1 row affected
删除主键
对于自动增长的主键,删除操作需要两步:
(1)先删除自动增长的约束
(2)再删除主键
sql#取消了自动增长的约束 mysql> ALTER TABLE student modify id int; #取消了id的主键地位,并不是真正意义上删除了id这个字段 mysql> ALTER TABLE student drop primary key;
取消了自己设定的主键后,mysql又会启用隐形主键,也就是数据存储顺序回到了最初的插入顺序。
联合主键
在MySQL中,我们还可以设置联合主键(也称为复合主键或复合键),这意味着主键由两个或多个列组成。当表中的任何两个或更多列的组合唯一标识表中的每一行时,可以使用联合主键。
如学生表student中,对学生进行分组,id相同的为同一组学生,则可以将id和name设置为联合主键。
sqlmysql> CREATE TABLE student( id INT , name VARCHAR(20), age INT, birth DATE, chinese INT, math INT, PRIMARY KEY(id, name) );当id和name都相同时,就无法插入数据。
如果在创建表的时候没有设置联合主键,想要追加设置。
首先要删除现有主键(如果有的话),因为主键只能有一个;
然后设置联合主键
sql
-- 删除原来的id主键
ALTER TABLE student DROP PRIMARY KEY;
-- 给name和birth设置联合主键
ALTER TABLE student ADD PRIMARY KEY(name, birth);
# 添加的数据中name和birth都与已有数据相同,插入失败
mysql> insert into student(id,name,age,birth,chinese,math) valu
-> es(9,'dijia',27,'2000-1-1',88,89);
(1062, "Duplicate entry 'dijia-2000-01-01' for key 'student.PRIMARY'")
# 添加的新数据与旧数据name相同,birth不同,添加成功
mysql> insert into student(id,name,age,birth,chinese,math) valu
-> es(9,'dijia',27,'2000-1-2',88,89);
Query OK, 1 row affected
Time: 0.002s
域完整性
域完整性确保表中每一列必须要符合某种特定的数据类型或约束。
常用的约束包括非空约束、默认值约束、唯一约束。
非空约束
将
student表中的name字段设置为非空约束
mysqlALTER TABLE student MODIFY name VARCHAR(20) NOT NULL;如果想取消非空约束
mysqlALTER TABLE student MODIFY name VARCHAR(20) DEFAULT NULL;
默认值约束
将
student表中的chinese字段设置默认值为60
sqlmysql> ALTER TABLE student MODIFY chinese INT DEFAULT 60;
唯一约束
将
employee表中的name字段设置为唯一约束
sql# 已存在表时 mysql> ALTER TABLE student MODIFY name VARCHAR(20) UNIQUE; #如果此时表中已经有name相同的多条记录,这一步会报错 # 不存在表时 mysql> CREATE TABLE employee ( id INT, name VARCHAR(20) UNIQUE, age INT, PRIMARY KEY(id), );
唯一约束允许有NULL,而且多个NULL不算重复。
删除唯一约束
删除
employee表中name字段的唯一约束,需要两步:先获取唯一约束的名字,再删除。注意:删除唯一约束时,必须要用关键字INDEX
sqlmysql> show create table student; +---------+--------------------------------------------------------------------+ | Table | Create Table | +---------+--------------------------------------------------------------------+ | student | CREATE TABLE `student` ( | | | `id` int NOT NULL, | | | `name` varchar(20) DEFAULT NULL, | | | `age` int DEFAULT NULL, | | | `birth` date NOT NULL, | | | `chinese` int DEFAULT NULL, | | | `math` int DEFAULT NULL, | | | UNIQUE KEY `name` (`name`) | | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +---------+--------------------------------------------------------------------+ mysql> ALTER TABLE employee DROP INDEX `name`;
联合唯一约束
此外,唯一约束也可以设置为多列,即联合唯一约束。
(1)创建表时直接定义联合唯一约束
sqlmysql> CREATE TABLE employee ( id INT, name VARCHAR(20), age INT, CONSTRAINT uk_employee_id_name UNIQUE (id, name) );这里的uk_employee_id_name是自己命名的元数据(一般的命名规则:uk即unique key唯一约束)
如下,可查看
mysqlSHOW CREATE TABLE employee; #输出内容如下 CREATE TABLE `employee` ( `id` int DEFAULT NULL, `name` varchar(20) DEFAULT NULL, `age` int DEFAULT NULL, UNIQUE KEY `uk_employee_id_name` (`id`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
(2)表已存在后添加联合唯一约束
mysqlALTER TABLE employee ADD CONSTRAINT uk_employee_id_name UNIQUE (id, name);
效果
mysql#第一次插入成功 INSERT INTO employee (id, name, age) VALUES (1, '张三', 25); #第二次插入同样的 id + name 报错 INSERT INTO employee (id, name, age) VALUES (1, '张三', 30); -- 报错:Duplicate entry '1-张三' for key 'uk_employee_id_name' -- 允许 id 相同但 name 不同 INSERT INTO employee (id, name, age) VALUES (1, '李四', 26); -- 允许 name 相同但 id 不同 INSERT INTO employee (id, name, age) VALUES (2, '张三', 27);
如果要删除联合唯一约束,也是要先确认约束名,再执行删除
mysql
SHOW CREATE TABLE employee;
ALTER TABLE employee DROP INDEX uk_employee_id_name;
参照完整性
参照完整性 确保两个表之间的数据关系是有效的,它主要通过外键约束来实现。
外键
外键是一个或多个字段的组合,其值引用另一个表的主键.
其作用是为了确保表与表之间的数据一致性,防止出现"孤儿数据"。
假设有 学生表 (子表) 和 班级表(父表)。
- 学生表中的
class_id(外键) 指向班级表的id(主键)。- 如果没有外键 ,你可能在学生表中插入一个
class_id = 999,但班级表里根本没有999这个班。这条学生数据就成了"无主孤魂"------这就是数据不一致。- 有了外键 ,MySQL 会强制检查:插入或更新
class_id时,这个值必须在班级表的id列中存在,否则操作被拒绝。
定义外键
在MySQL中,使用Foreign Key关键字来定义外键关系。
外键语法规则如下:
Foreign Key (filed) References other_tbname(other_filed)
如存在一张学生表
student,现有一张订单表torder,其中的id要参照student表中的主键id(学生表是父表,订单表是子表,订单表中的s_id必须对应某一个学生id)
sqlCREATE TABLE student( id INT AUTO_INCREMENT, name VARCHAR(20), birth Date, chinese INT, math INT, PRIMARY KEY(id) ); CREATE TABLE torder( id INT AUTO_INCREMENT, p_name VARCHAR(20), s_id INT, PRIMARY KEY(id) ); # 表已经存在的情况下,添加外键约束 # constraint代表约束, fk_1是自己给外键约束起的名字(foreign key 1) mysql> ALTER TABLE torder ADD CONSTRAINT fk_1 FOREIGN KEY(s_id) REFERENCES student(id); #torder表的外键s_id引用student表的主键id # torder不存在的情况下,在创建表时设置外键约束 mysql> CREATE TABLE torder( id INT AUTO_INCREMENT, p_name VARCHAR(20), s_id INT, PRIMARY KEY(id), CONSTRAINT fk_torder_student FOREIGN KEY(s_id) REFERENCES student(id) );
外键名(字段名/列名)是表中实实在在的一列数据,在本例中即s_id,是存储的具体的数据值。
外键约束名(规则名)是附加在表上的一个规则/标签,在本例中即fk_torder_student,告诉 MySQL 执行检查规则,本身不存数据。
可以这样理解:
外键字段(s_id) 就像是家里的大门。
外键约束名(fk_torder_student) 就像是贴在大门上的一张"安检标签"。标签上写着:"所有进入大门的人,必须持有学生证的才能进。"这个标签本身不是门,但它决定了门怎么用。
效果
sql
#创建学生表后,加入一条记录
insert into student (id,name,birth,chinese,math) values(1,'Jack','1996.11.23',99,88);
# 成功:因为student 表里有 id=1 的学生
INSERT INTO torder (p_name, s_id) VALUES ('《数据库入门》', 1);
# 失败:student 表里没有 id=99 的学生
INSERT INTO torder (p_name, s_id) VALUES ('《深入浅出MySQL》', 99);
-- 报错:Cannot add or update a child row: a foreign key constraint fails
删除外键
先获取外键约束的名字
再删除外键约束
如:
sql#获取外键约束的名字 show create table torder; +--------+-------------------------------------------------------------------------------------+ | Table | Create Table | +--------+-------------------------------------------------------------------------------------+ | torder | CREATE TABLE `torder` ( | | | `id` int NOT NULL AUTO_INCREMENT, | | | `p_name` varchar(20) DEFAULT NULL, | | | `s_id` int DEFAULT NULL, | | | PRIMARY KEY (`id`), | | | KEY `fk_1` (`s_id`), | | | CONSTRAINT `fk_1` FOREIGN KEY (`s_id`) REFERENCES `student` (`id`) | | | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +--------+-------------------------------------------------------------------------------------+ #删除外键约束 ALTER TABLE torder DROP FOREIGN KEY fk_1; Query OK, 0 rows affected (0.00 sec)
DQL 之复杂查询
连接查询
数据库中共存储了 A 表和 B 表,经过交叉连接之后,就得到了一张大表。该表是一张临时表,占据内存,并没有真正存储在数据库中(数据库中实际的数据存在磁盘上)。
如果查询得到的这个结果集很大,内存可能放不下,MySQL 就会把它写到磁盘上的临时目录里,用完了再删掉。
连接查询涉及到多张表,主要包括以下几种类型:
- 交叉连接(笛卡尔积)
- 内连接
- 外连接
- 左外连接
- 右外连接
交叉连接(笛卡尔积)
语法格式:
sql
-- 显式写法
SELECT * FROM LeftTable CROSS JOIN RightTable;
-- 简写(省略 CROSS)
SELECT * FROM LeftTable JOIN RightTable;
-- 隐式写法(不推荐,但常见)
SELECT * FROM LeftTable, RightTable;
说明:
- 交叉连接返回的是两个表的笛卡尔积,即左表的每一行与右表的每一行组合。
- 如果 A 表有 m 行,B 表有 n 行,结果集为
m × n行。
示例:
-
A 表数据:
1, 2 -
B 表数据:
3, 4, 5 -
交叉连接结果:
1-3, 1-4, 1-5
2-3, 2-4, 2-5
笛卡尔积的筛选
在实际业务中,我们通常不会直接使用完整的笛卡尔积,而是通过 WHERE 或 ON 子句对其进行筛选,得到有意义的关联数据。
内连接
语法格式:
sql
SELECT * FROM A INNER JOIN B ON A.field1 = B.field2;
作用:
- 返回两个表中满足连接条件的记录(即交集)。
- 内连接只返回匹配的行,不匹配的行会被丢弃。
student表

torder表

内连接查询
sql
SELECT * FROM student INNER JOIN torder ON student.id = torder.s_id;

得到有订单的学生信息及其订单信息
隐式写法(不推荐):
sql
SELECT * FROM A, B WHERE A.field1 = B.field2;
连接查询取别名
在进行连接查询时,可以给每一张表取一个别名,方便使用。
sql#只查询部分字段(满足student的主键与torder的外键对应的) #表名太长,取别名更方便 #字段名也可以取别名 SELECT s.id, s.name, o.id AS order_id, o.p_name FROM student AS s INNER JOIN torder AS o ON s.id = o.s_id;

外连接
外连接又分为左外连接和右外连接
左外连接(LEFT JOIN)
语法格式:
sql
SELECT * FROM A LEFT JOIN B ON A.field1 = B.field2;
特点:
-
左表为主表,左表中的所有数据都会出现。
-
当右表中没有与之对应的记录时,查询出的右表中的数据全部用
NULL代替。 -
左连接结果 = 左表全部 + 右表匹配部分
sql
SELECT * FROM student AS s
LEFT JOIN torder AS o ON s.id = o.s_id;

------ 如果只想查看部分字段
**
sql
SELECT s.id, s.name, o.id AS order_id, o.p_name
FROM student AS s
LEFT JOIN torder AS o ON s.id = o.s_id;

------查看左连接独有记录
sql
#注意比对字段是否为空用的是is null,而不是 = null
select * from student left join torder on student.id = torder.s_id where torder.id is null;

右外连接(RIGHT JOIN)
语法格式:
sql
SELECT * FROM A RIGHT JOIN B ON A.field = B.field;
特点:
-
右表为主表,右表中的所有数据都会出现。
-
当左表中没有与之对应的记录时,查询出的左表中的数据全部用
NULL代替。 -
右连接结果 = 右表全部 + 左表匹配部分
sql
SELECT * FROM student AS s
RIGHT JOIN torder AS o ON s.id = o.s_id;

想要展示坐标补充NULL的效果,我们可以给torder表添加一个在学生表里找不到对应学生的订单。但是现在torder表有外键约束,无法添加这样的数据,先删除掉外键约束,再添加。
sql
#获取外键约束名
mysql>show create table torder;
| torder | CREATE TABLE `torder` ( |
| | `id` int NOT NULL AUTO_INCREMENT, |
| | `p_name` varchar(20) DEFAULT NULL, |
| | `s_id` int DEFAULT NULL, |
| | PRIMARY KEY (`id`), |
| | KEY `fk_1` (`s_id`), |
| | CONSTRAINT `fk_1` FOREIGN KEY (`s_id`) REFERENCES `student` (`id`) |
#删除外键约束
mysql> alter table torder drop foreign key fk_1;
#添加一个不对应任何学生的订单
INSERT INTO torder (p_name, s_id) VALUES ('《网络协议》', 99);
#查看torder表
select * from torder;
+----+----------------+------+
| id | p_name | s_id |
+----+----------------+------+
| 1 | 《数据库入门》 | 1 |
| 2 | 《数据结构》 | 1 |
| 3 | 《算法》 | 3 |
| 4 | 《网络协议》 | 99 |
+----+----------------+------+
#右连接查询
SELECT * FROM student AS s
RIGHT JOIN torder AS o ON s.id = o.s_id;

------ 查看右连接所独有的记录
sql
select * from student right join torder on student.id = torder.s_id where student.id is null;

连接查询总结
------ 外连接与内连接的区别如下:
| 对比 | 内连接(INNER JOIN) | 外连接(LEFT / RIGHT JOIN) |
|---|---|---|
| 返回结果 | 只返回两表匹配的行 | 返回主表全部 + 另一表匹配的行(不匹配则补 NULL) |
| 主表 | 无主次之分 | 有主次之分(LEFT 左表为主,RIGHT 右表为主) |
| 丢弃数据 | 不匹配的行直接丢弃 | 主表不匹配的行保留 (另一侧补 NULL) |
------ 左连接与右连接的效果:
| 连接类型 | SQL 语句 | 说明 |
|---|---|---|
| 左连接 | SELECT * FROM A LEFT JOIN B ON A.id = B.id; |
左表全部,右表匹配或 NULL |
| 左表独有 | SELECT * FROM A LEFT JOIN B ON A.id = B.id WHERE B.id IS NULL; |
只在左表,不在右表 |
| 右连接 | SELECT * FROM A RIGHT JOIN B ON A.id = B.id; |
右表全部,左表匹配或 NULL |
| 右表独有 | SELECT * FROM A RIGHT JOIN B ON A.id = B.id WHERE A.id IS NULL; |
只在右表,不在左表 |
图示:

------全连接(取并集)

子查询
子查询也叫嵌套查询 ,是指在 WHERE 子句或 FROM 子句中又嵌入 SELECT 查询语句。
where子查询也称作where子句的嵌套查询;
from子查询也称作from子句的嵌套查询。
WHERE 子查询
场景:查询语文成绩高于95 分的学生的订单信息。
student

torder

嵌套查询
sql
SELECT * FROM torder
WHERE s_id IN (SELECT id FROM student WHERE chinese > 95);

只有Jack的语文成绩高于95,查询到Jack(id=1)对应的所有订单。
FROM 子查询
场景:查询学号大于1 的学生所购买的产品,显示学生 id、姓名以及购买的产品。
sql
SELECT s.id, s.name, o.p_name
FROM (SELECT * FROM student WHERE id > 1) AS s
JOIN torder AS o
ON s.id = o.s_id;

这里注意区分一下where子查询和from子查询。
WHERE子查询 :把结果当作一个"值"或"值的列表",用来做条件过滤。FROM子查询 :把结果当作一张"临时的表",用来继续做查询。
| 对比 | WHERE 子查询 |
FROM 子查询 |
|---|---|---|
| 结果的性质 | 被当作一个值 (标量)或一列值(列表) | 被当作一张临时的表(有行有列) |
| 结果的行数 | 通常返回 1 列 (多行)或 1 个值 | 可以返回多行多列 |
| 在 SQL 中的角色 | 充当筛选条件 | 充当数据源 |
联合查询
联合查询能够合并两条查询语句的查询结果,去掉其中的重复数据行,然后返回没有重复数据行的查询结果。
语法格式:
sql
SELECT ... UNION SELECT ...;
说明:
UNION默认会去重。- 如果需要保留所有重复行,使用
UNION ALL(性能更好)。
当前两张表

需求一:把学生的名字和商品的名称放在同一列里展示。
sql
SELECT name FROM student
UNION
SELECT p_name FROM torder;

需求二 :把学生的 id 和订单的 s_id 合并成一列
学生id有1,2,3;
订单s_id有1,1,3,99
sql
SELECT id FROM student
UNION
SELECT s_id FROM torder;

------ 使用UNION会去重
sql
SELECT id FROM student
UNION ALL
SELECT s_id FROM torder;

------ 使用UNION ALL不去重。
报表查询
统计函数
常用统计函数:
| 函数 | 作用 |
|---|---|
COUNT(*) |
计数,统计行数 |
SUM(列) |
求和 |
AVG(列) |
求平均值 |
MAX(列) |
求最大值 |
MIN(列) |
求最小值 |
示例:
sql
SELECT COUNT(*) FROM student; -- 学生总人数
SELECT SUM(chinese) FROM student; -- 语文总成绩
SELECT AVG(chinese) FROM student; -- 语文平均分
SELECT MAX(chinese) FROM student; -- 语文最高分
SELECT MIN(math) FROM student; -- 数学最低分

示例一:

示例二:

示例三:

注:除了COUNT(*),有时也会见到COUNT(字段名)的用法
COUNT(*):统计所有行的数量 ,不管字段值是否为NULL。COUNT(字段名):统计该字段中值不为NULL的行数。
GROUP BY 分组
GROUP BY 用于将数据按某个字段进行分组,通常配合统计函数使用。
先给student表多增加一些数据

按语文成绩进行分组,查询成绩值与每组的人数。
sql
SELECT chinese, COUNT(*)
FROM student
GROUP BY chinese;

分组过滤(HAVING)
当分组之后,还需要对记录进行过滤,只能使用 HAVING 子句。
sql
SELECT chinese, COUNT(*)
FROM student
GROUP BY chinese;
HAVING COUNT(*) > 1;

与having有些类似的,where子句也是起过滤效果。
但不同的是,
WHERE子句在分组之前使用,过滤的是原始行;
HAVING在分组之后使用,过滤的是分组之后的结果组。group by之后不能再用where
综合示例
需求:按语文成绩进行分组,查看语文成绩在 60 分以上的得分情况,同时统计数学的最高分和最低分,并根据语文成绩降序排列。
sql
SELECT
chinese,
COUNT(chinese),
max(math),
min(math)
FROM student
WHERE chinese > 90
GROUP BY chinese
HAVING chinese > 95
ORDER BY chinese;
下面来看看这么复杂的语句,具体执行的步骤:
第 1 步:FROM student ------ 取出所有原始数据
| id | name | birth | chinese | math |
|---|---|---|---|---|
| 1 | Jack | 1996-11-23 | 99 | 88 |
| 2 | Rose | 1999-04-02 | 88 | 99 |
| 3 | Alice | 2000-01-01 | 95 | 92 |
| 4 | Tom | 1998-04-23 | 99 | 78 |
| 5 | Bob | 1999-06-05 | 95 | 89 |
| 6 | Lucy | 2001-01-01 | 97 | 85 |
第 2 步:WHERE chinese > 90 ------ 筛选行
只看语文成绩 大于 90 的学生。
| id | name | birth | chinese | math | 是否保留 |
|---|---|---|---|---|---|
| 1 | Jack | 1996-11-23 | 99 | 88 | 保留(99 > 90) |
| 2 | Rose | 1999-04-02 | 88 | 99 | 剔除(88 ≤ 90) |
| 3 | Alice | 2000-01-01 | 95 | 92 | 保留(95 > 90) |
| 4 | Tom | 1998-04-23 | 99 | 78 | 保留(99 > 90) |
| 5 | Bob | 1999-06-05 | 95 | 89 | 保留(95 > 90) |
| 6 | Lucy | 2001-01-01 | 97 | 85 | 保留(97 > 90) |
筛选后的数据:
| id | name | birth | chinese | math |
|---|---|---|---|---|
| 1 | Jack | 1996-11-23 | 99 | 88 |
| 3 | Alice | 2000-01-01 | 95 | 92 |
| 4 | Tom | 1998-04-23 | 99 | 78 |
| 5 | Bob | 1999-06-05 | 95 | 89 |
| 6 | Lucy | 2001-01-01 | 97 | 85 |
Rose(88分)被
WHERE提前筛掉了,不参与任何后续分组和统计。
第 3 步:GROUP BY chinese ------ 按语文成绩分组
把上一步保留的数据,按 chinese 值分组:
| 组(chinese) | 组内成员 | 行数 |
|---|---|---|
| 99 | Jack(99分)、Tom(99分) | 2 行 |
| 95 | Alice(95分)、Bob(95分) | 2 行 |
| 97 | Lucy(97分) | 1行 |
第 4 步:HAVING chinese > 95 ------ 筛选组
在分组完成后,筛选出 chinese > 95 的组。
| 组(chinese) | 是否满足 chinese > 95 |
|---|---|
| 99 | 满足(99 > 95) |
| 95 | 不满足(95 > 95 为假) |
| 97 | 满足(97 > 95) |
结果:95 分组被淘汰,剩下 99和97 分组。
第 5 步:SELECT 聚合统计
对 99 分组进行统计:
COUNT(chinese):该组人数 = 2(Jack 和 Tom)MAX(math):数学最高分 = max(88, 78) = 88MIN(math):数学最低分 = min(88, 78) = 78
对97分组进行统计:
COUNT(chinese):该组人数 = 1(Lucy)MAX(math):数学最高分 = max(85) = 85MIN(math):数学最低分 = min(85) = 85
统计结果:
| chinese | 人数 | 数学最高分 | 数学最低分 |
|---|---|---|---|
| 99 | 2 | 88 | 78 |
| 97 | 1 | 85 | 85 |
第 6 步:ORDER BY chinese ------ 默认升序排列
最终结果

复杂查询小结
在写 SQL 语句时,涉及到多表查询,都可以从先得到一张大表(临时表)后,再开始根据需求进行条件过滤,逐步进行。
复杂查询可以使用图形化界面的工具,更方便。
数据库的备份和恢复
备份
在终端(命令行)下执行:
bash
$ mysqldump -u root -p wangdao > wangdao.sql
该命令将 wangdao 数据库导出为 wangdao.sql 脚本文件,保存目录即为当前所在目录。
恢复
- 先在 MySQL 服务器上创建一个空的数据库(如
wangdao2):
sql
CREATE DATABASE wangdao2;
- 两种恢复方式:
方式一:在命令行下
bash
$ mysql -u root -p wangdao2 < wangdao.sql
方式二:进入 MySQL 后
sql
USE wangdao2;
SOURCE wangdao.sql;
C 语言的 MySQL API
相关结构体
MYSQL代表一个MYSQL连接
- 通过
mysql_init()初始化。- 通过
mysql_real_connect()建立连接。- 所有后续操作(查询、获取错误信息等)都需要通过这个句柄。
- 最后用
mysql_close()关闭并释放。
MySQL的服务端与客户端之间的通信是通过socket
MYSQL_RES代表结果集
代表
SELECT、SHOW等查询返回的完整结果集。
- 结果集包含了所有行 和列元数据(字段名、类型等)。
- 它是在客户端内存中分配的(通过
mysql_store_result())或由服务器流式返回(通过mysql_use_result())。- 使用完后必须释放,否则会造成内存泄漏。
MYSQL_ROW代表结果集中的一行数据
它是一个
char**类型的指针数组,每个元素指向一个字段值的字符串表示。
- 每一行通过
mysql_fetch_row()返回。- 行的每一列通过下标访问:
row[0]、row[1]、...、row[字段数-1]。- 如果某个字段值为
NULL,对应的指针为NULL。
MYSQL_FIELD代表列(字段)
代表结果集中一列的元数据,描述列的结构信息。

常用函数介绍
mysql_init()
c
MYSQL * mysql_init(MYSQL* mysql);
功能:
分配或初始化与 mysql_real_connect() 相适应的 MYSQL 对象。
- 如果传入的参数
mysql是NULL指针,MYSQL会在在堆内存中动态分配 一个新的MYSQL结构体对象,并把它初始化好,然后返回这个对象的地址。 - 如果传入的参数是一个已经分配好内存的 MYSQL 结构体变量的地址,那么MySQL 不会重新分配内存,而是把传进去的那个对象重新初始化,然后返回该对象的地址。
- 如果
mysql_init()分配了新的对象,使用完后,必须调用mysql_close(conn),它会自动释放这个对象占用的内存。
返回值:
- 成功:初始化的
MYSQL*句柄 - 失败:
NULL(内存不足)
mysql_real_connect()
c
MYSQL* mysql_real_connect(MYSQL *mysql,
const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long client_flag);
功能:
尝试与运行在 host 上的 MySQL 服务器建立连接。
在执行任何其他需要有效 MYSQL 连接句柄的 API 函数之前,客户端程序必须成功连接到服务器。
参数说明:
| 参数 | 类型 | 说明 | 常用值 |
|---|---|---|---|
mysql |
MYSQL* |
由 mysql_init() 初始化的连接句柄 |
conn |
host |
const char* |
MySQL 服务器主机名或 IP 地址 | "localhost" 或 "127.0.0.1" |
user |
const char* |
登录用户名 | "root" |
passwd |
const char* |
登录密码 | "1234" |
db |
const char* |
要连接的数据库名(可传 NULL,连接后再用 mysql_select_db() 选择) |
"test" 或 NULL |
port |
unsigned int |
MySQL 服务器端口号,默认 3306,传入 0 表示使用默认端口 |
0 |
unix_socket |
const char* |
Unix 套接字文件路径(Windows 下忽略),一般传 NULL |
NULL |
client_flag |
unsigned long |
客户端标志位,一般传 0 |
0 |
返回值:
- 成功:返回
MYSQL*连接句柄(与第 1 个参数值相同) - 失败:返回
NULL
错误信息:
如果发生错误,mysql_error(conn) 返回以 null 结尾的字符串;如果没有错误,返回空字符串。
mysql_select_db函数
mysql_select_db()是用来切换当前数据库连接的默认数据库的函数。
cint mysql_select_db(MYSQL *mysql, const char *db)参数:
- 连接句柄
- 想要切换到的目标数据库的名称
返回值:
函数执行成功时返回
0;如果发生任何错误,则返回一个非零值
mysql_query() / mysql_real_query()
c
int mysql_query(MYSQL *mysql, const char *stmt_str);
int mysql_real_query(MYSQL *mysql, const char *stmt_str, unsigned long length);
功能:
执行 SQL 语句。
mysql_query():执行以\0结尾的字符串指向的 SQL 语句。mysql_real_query():执行SQL 语句,长度为length字节。
------ 为什么要设计这样两个不同形式的函数呢?
mysql_query()是 MySQL 最早期的 API 函数,设计得非常简单:你给它一个字符串,它就执行。这在处理纯文本 SQL(比如SELECT * FROM student)时完全够用。但随着 MySQL 的发展,用户开始往数据库里存图片、音频等二进制文件,也就是说参数stmt_str可能本身就是包含了
\0的SQL语句。这时候mysql_query()的简单设计就成了致命缺陷------因为它无法处理包含\0空字符的 SQL。但已经有很多老项目依赖mysql_query()了,MySQL 不能直接删掉它,于是新增了mysql_real_query()来解决这个问题。
sql
CREATE TABLE images (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
data BLOB -- 二进制大对象,用于存储图片数据
);
c
//在准备好的存放SQL语句的缓冲器buff中保存"INSERT INTO images (name, data) VALUES ('photo1', 'FF D8 FF E0 \0 10 4A 46 49 46 \0 01 ...')"
mysql_query(mysql,buff); //这样就会读取出问题
mysql_real_query(mysql,buff,total_len);//这样就OK
注意事项:
- 通常,字符串必须由单个 SQL 语句 组成,不带终止分号
;。 - 如果启用了多语句执行,则可以包含多个以分号分隔的语句(最后一个语句的后面可以加分号,也可以不加)。
(在调用 mysql_real_connect() 时,将最后一个参数 client_flag 设置为 CLIENT_MULTI_STATEMENTS,即开启多语句执行)
返回值:
- 成功:
0 - 失败:非
0值
常见错误码:
| 错误码 | 说明 |
|---|---|
CR_COMMANDS_OUT_OF_SYNC |
以不恰当的顺序执行了命令 |
CR_SERVER_GONE_ERROR |
MySQL 服务器不可用 |
CR_SERVER_LOST |
在查询过程中与服务器的连接丢失 |
CR_UNKNOWN_ERROR |
出现未知错误 |
获取查询结果
方式一:mysql_store_result()
c
MYSQL_RES *mysql_store_result(MYSQL *mysql);
特点:
- 将查询的全部结果 读取到客户端(此过程是阻塞式的),分配一个
MYSQL_RES结构,并将结果置于该结构中。 - 如果查询未返回结果集,返回
NULL指针。 - 一旦调用了
mysql_store_result()并获得了非NULL的结果,可调用:mysql_num_rows():获取结果集中的行数mysql_fetch_row():获取结果集中的一行,放到循环中就可以逐行遍历(此时数据已经在客户端内存中,遍历速度很快)mysql_row_seek():跳转到某一行mysql_row_tell():获取当前行位置
- 完成操作后,必须调用
mysql_free_result()释放内存。
方式二:mysql_use_result()
c
MYSQL_RES *mysql_use_result(MYSQL *mysql);
特点:
- 并不会真正获取数据,只是在MYSQL服务器端开启了一个数据流管道。
- 只有当调用
mysql_fetch_row()函数后才真正获取数据,每调用一次获取一行数据。 - 每次是直接从服务器读取结果。
- 与
mysql_store_result()相比,使用的内存更少(因为不需要存整个结果集,每次读只存一行)。
返回值:
- 成功:返回
MYSQL_RES*结果 - 失败:返回
NULL
**注:**当查询语句的结果集很大时,更适合使用方式二。
mysql_fetch_row()
c
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
功能:
检索结果集的下一行。
- 在
mysql_store_result()之后使用:如果没有要检索的行,返回NULL。 - 在
mysql_use_result()之后使用:如果没有要检索的行或出现了错误(比如网络连接中断),返回NULL。
访问方式:
c
//MYSQL_ROW就是char**,得到一行的内容以字符串数组的方式存放
MYSQL_ROW row = mysql_fetch_row(res);
//行内值的数目(一行有多少列)由 mysql_num_fields函数得出
int num_fields = mysql_num_fields(res);
//得到一个long型数组的首元素地址,数组中保存的是得到的一行内容中每一列(每个字段)的数据长度
unsigned long *lengths = mysql_fetch_lengths(res);;
//按照 `row[0]` 到 `row[mysql_num_fields(result) - 1]` 访问,遍历此行的每一列
for (int i = 0; i < num_fields; i++) {
// 第一步:判断是否为 NULL
if (row[i] == NULL) {
printf("NULL\t");
continue;
}
// 第二步:判断是否为空字符串(长度为 0)
if (lengths[i] == 0) {
printf("(空字符串)\t");
continue;
}
// ★ 第三步:有实际数据
printf("%s\t", row[i]);
}
------
mysql_fetch_lengths()的使用方法
cunsigned long *mysql_fetch_lengths(MYSQL_RES *result);这个函数返回一个
unsigned long*数组,每个元素对应一列的长度。必须在调用mysql_fetch_row()之后立即调用,因为长度信息与当前行绑定。
mysql_free_result() / mysql_close()
c
void mysql_free_result(MYSQL_RES *result);
void mysql_close(MYSQL *mysql);
mysql_free_result():释放由mysql_store_result()、mysql_use_result()等为结果集分配的内存。完成对结果集的操作后必须调用,释放完成后不要尝试访问结果集。mysql_close():关闭前面打开的连接。如果句柄是由mysql_init()或mysql_connect()自动分配的,mysql_close()还将解除分配连接句柄。
C 语言 API 示例代码
编译方式:
gcc Test.c -lmysqlclient
写操作示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h> //加载mysql客户端的头文件
int main()
{
//MYSQL conn;
//1. 初始化MYSQL结构体
MYSQL * pconn = mysql_init(NULL);
if(pconn == NULL) {
printf("mysql_init failed\n");
return EXIT_FAILURE;
}
//2. 连接数据库
const char * host = "localhost";
const char * user = "root";
const char * passwd = "1234";
const char * db = "7th";
//连接成功时, ret的指向与pconn是相同的
MYSQL * ret = mysql_real_connect(pconn, host, user, passwd, db, 0, NULL, 0);
if(ret == NULL) {
printf("mysql connect failed: %s.\n", mysql_error(pconn));
return EXIT_FAILURE;
}
printf("ret: %p, pconn: %p\n", ret, pconn);
//3. 执行查询
const char * sql = "UPDATE student set chinese=80 where id=8";
int nret = mysql_real_query(pconn, sql, strlen(sql));
if(nret != 0) {
//查询失败
printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
mysql_close(pconn);
return EXIT_FAILURE;
}
//4. 程序如果能执行到这里,就是写操作成功了
printf("Query OK, %ld row affected.\n", mysql_affected_rows(pconn));
//5. 关闭mysql的连接
mysql_close(pconn);
return 0;
}
读操作示例
c
#include <mysql/mysql.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
// 1. 初始化结构体 MYSQL
MYSQL *conn = mysql_init(NULL);
const char *host = "localhost";
const char *user = "root";
const char *passwd = "1234";
const char *db = "7th";
// 2. 与 MySQL 服务器建立连接
conn = mysql_real_connect(conn, host, user, passwd, db, 0, NULL, 0);
if (!conn) {
fprintf(stderr, "%s\n", mysql_error(conn));
return EXIT_FAILURE;
}
// 3. 执行查询操作
const char *query = "SELECT * FROM student";
int ret = mysql_query(conn, query);
if (ret != 0) {
fprintf(stderr, "%d, %s\n", mysql_errno(conn), mysql_error(conn));
return EXIT_FAILURE;
}
// 4. 获取查询的结果集
MYSQL_RES *res = mysql_store_result(conn);
if (res) {
// 获取字段名的信息
MYSQL_FIELD *field = NULL;
while ((field = mysql_fetch_field(res)) != NULL) {
printf("%s\t", field->name);
}
printf("\n");
int rows = mysql_num_rows(res);
int cols = mysql_num_fields(res);
// 获取每一行的数据
MYSQL_ROW row;
while ((row = mysql_fetch_row(res)) != NULL) {
for (int i = 0; i < cols; ++i) {
printf("%s\t", row[i]);
}
printf("\n");
}
}
// 5. 释放结果集(实际生产代码中需要调用)
mysql_free_result(res);
// 6. 关闭连接
mysql_close(conn);
return 0;
}
经典问题
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h> //加载mysql客户端的头文件
int main()
{
//MYSQL conn;
//1. 初始化MYSQL结构体
MYSQL * pconn = mysql_init(NULL);
if(pconn == NULL) {
printf("mysql_init failed\n");
return EXIT_FAILURE;
}
//2. 连接数据库
const char * host = "localhost";
const char * user = "root";
const char * passwd = "1234";
const char * db = "7th";
//连接成功时, ret的指向与pconn是相同的
MYSQL * ret = mysql_real_connect(pconn, host, user, passwd, db, 0, NULL, 0);
if(ret == NULL) {
printf("mysql connect failed: %s.\n", mysql_error(pconn));
return EXIT_FAILURE;
}
printf("ret: %p, pconn: %p\n", ret, pconn);
//3. 执行查询
const char * sql = "select * from student";
printf("第一次执行查询\n");
int nret = mysql_real_query(pconn, sql, strlen(sql));
if(nret != 0) {
//查询失败
printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
mysql_close(pconn);
return EXIT_FAILURE;
}
/* 1. 每次 SELECT 后,必须调用 mysql_store_result() 或 mysql_use_result() 获取结果集。 */
/* 2. 调用 mysql_fetch_row() 读取完所有行。 */
/* 3. 调用 mysql_free_result() 释放结果集。 */
/* 4. 然后才能执行下一次查询。 */
printf("\n第二次执行查询\n");
nret = mysql_real_query(pconn, sql, strlen(sql));
if(nret != 0) {
//查询失败
printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
mysql_close(pconn);
return EXIT_FAILURE;
}
//4. 程序如果能执行到这里,就是读操作成功了, 获取结果集
//printf("Query OK, %ld row affected.\n", mysql_affected_rows(pconn));
MYSQL_RES * result = mysql_store_result(pconn);
if(result) {
//获取到了结果集
int rows = mysql_num_rows(result);//9行
int cols = mysql_num_fields(result);//6列
printf("res: (%d, %d)\n", rows, cols);
//打印字段名
MYSQL_FIELD * filed;
while((filed = mysql_fetch_field(result)) != NULL) {
printf("%s\t", filed->name);
//filed->db;//数据库的名字
//filed->table;//表的名字
}
printf("\n");
//打印每一行
MYSQL_ROW row;
while((row = mysql_fetch_row(result)) != NULL) {
for(int i = 0; i < cols; ++i) {
printf("%s\t", row[i]);
}
printf("\n");
}
}
//5. 释放结果集
mysql_free_result(result);
//6. 关闭mysql的连接
mysql_close(pconn);
return 0;
}





