Mysql数据库基础

数据库基础

SQL查询中各个关键字的执行先后顺序 from > on> join > where > group by > with > having > select > distinct > order by > limit

提示:

复杂的SQL语句有其固定的"骨架",即关键字的执行顺序 。可以记忆一个逻辑链,例如 "FWGHSOO" ,它对应着 FROM-> WHERE-> GROUP BY-> HAVING-> SELECT-> ORDER BY-> OFFSET & FETCH。理解了这一点,就能像搭积木一样构建和分解复杂查询。

注意区分mysql和mysqld之间的关系;掌握mysql和mysqld以及磁盘这三者的联系

mysqld:数据库的服务端

mysqld是 MySQL 服务的核心守护进程,它直接管理磁盘上的数据文件 。

  • 核心功能:它负责解析 SQL 语句、执行数据查询(SELECT)、更新(INSERT/UPDATE/DELETE)、管理事务、维护索引等所有数据操作 。

  • 与磁盘的交互 :所有数据库、表最终都以文件形式保存在磁盘上(例如位于 /var/lib/mysql/目录下)。mysqld进程是读写这些文件的唯一正式通道,它通过存储引擎 (如 InnoDB)以特定格式(如 .ibd文件)和**页(Page)**​ 为单位(通常是 16KB)来高效地进行磁盘 I/O 操作,以减少读写次数 。

简单来说,mysqld是连接 mysql客户端和磁盘数据文件的桥梁

mysql:数据库的客户端

mysql是一个用于连接 mysqld服务的命令行工具 。

核心功能:提供一个界面,让你能够输入 SQL 命令,并将命令发送给 mysqld,然后接收并显示 mysqld返回的结果 。

它与磁盘的关系:是间接的。它不直接操作磁盘上的数据文件,所有请求都必须通过 mysqld服务端处理。

磁盘提供了非易失性存储,确保 MySQL 服务重启或服务器断电后,数据依然存在 。

  • 存储内容 :在数据目录(如 /var/lib/mysql/)下,每个数据库对应一个子目录,表对应目录内的文件 。常见的文件包括:

    • 表结构文件.frm):描述表的结构。

    • 数据和索引文件 :取决于存储引擎。例如,InnoDB 引擎使用 .ibd文件,而 MyISAM 引擎使用 .MYD(数据)和 .MYI(索引)文件 。

MySQL架构

先不细说了,后面再说

SQL分类

DDL【data definition language】数据定义语言,用来维护存储数据的结构 代表指令: create, drop, alter

DML【data manipulation language】数据操纵语言,用来对数据进行操作 代表指令: insert,delete,update DML中又单独分了一个DQL,数据查询语言,代表指令: select

DCL【Data Control Language】数据控制语言,主要负责权限管理和事务 代表指令: grant,revoke,commit

存储引擎

存储引擎是:数据库管理系统如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术 的实现方法。 MySQL的核心就是插件式存储引擎,支持多种存储引擎。

MySQL 的存储引擎是数据库的"心脏",它决定了数据如何存储、索引以及支持哪些功能。简单来说,存储引擎就是表级别的数据处理与存储机制​ 。

MySQL 中最常见的几种存储引擎的核心特性。

存储引擎 事务支持 锁机制 外键支持 主要优势 典型应用场景
InnoDB ✅ 支持 (ACID) 行级锁 ✅ 支持 高并发、数据完整性、崩溃恢复 金融交易、电商平台、用户中心
MyISAM ❌ 不支持 表级锁 ❌ 不支持 极高的读取速度、全文索引 内容管理系统(CMS)、数据仓库、日志分析(读多写少)
Memory ❌ 不支持 表级锁 ❌ 不支持 数据存于内存,读写速度极快 会话缓存、临时表、高速查询中间结果
Archive ❌ 不支持 行级锁 ❌ 不支持 极高的压缩比,节省存储空间 日志归档、历史数据存储(只插不查)

为什么需要多种引擎

MySQL 采用独特的插件式存储引擎架构​ 。这意味着你可以根据每张表的具体用途,为它选择最合适的"引擎",就像给汽车换上适合不同地形的轮胎一样。这种灵活性是 MySQL 广受欢迎的重要原因之一 。例如,你可以将核心交易表设为 InnoDB 以保证安全,而将用于全文搜索的日志表设为 MyISAM 以追求速度。

查看存储引擎

库的操作

只写容易忽略忘记的部分,过于基础的不再赘述

关于字符集和校验规则

查看系统默认字符集以及校验规则

show variables like 'character_set_database';

show variables like 'collation_database';

查看数据库支持的字符集 show charset;

查看数据库支持的字符集校验规则 show collation;

校验规则对数据库的影响

校验规则使用utf8_ general_ ci[不区分大小写]

校验规则使用utf8_ bin[区分大小写]

区分大小写排序以及结果:

mysql> use test2;

mysql> select * from person order by name;

操纵数据库

操纵数据库是数据库管理和维护中的一项基本操作。

显示创建语句

show create database 数据库名;

该命令的主要用途是:

获取数据库的创建"元信息" 。它不是用来增删改查数据的,而是用来查看数据库这个"容器"本身的定义和属性 的,主要用于维护、迁移、复制和问题诊断

  • mytest :反引号 `是MySQL中用于包裹数据库名、表名、字段名的标识符,防止它们与SQL关键字冲突。这里表示数据库名叫 mytest

  • /*!40100 DEFAULT CHARACTER SET utf8 */:这是一个MySQL特有的注释语法,但它会被执行。

    • 40100表示MySQL服务器版本 >= 4.1.0 时,才执行此语句。

    • DEFAULT CHARACTER SET utf8是这条语句的精髓 ,它指定了这个数据库的默认字符集为 utf8 。这意味着,如果你在这个数据库里创建新表时没有指定字符集,表就会默认使用 utf8字符集,这对于存储中文等多语言文本至关重要。

了解数据库的"出生证明":就像查看一个产品的制造规格一样,这个命令能让你看到某个数据库最初被创建时的完整、准确的SQL语句。

关键用途场景:

迁移与备份:当你想在另一台服务器上重建一个一模一样的数据库时,这个创建语句就是最准确的蓝图。你可以直接复制这条语句去执行,确保新数据库的字符集等设置和原库完全一致。

排查问题:例如,当你的数据出现乱码时,首先要检查的就是数据库的字符集设置。这个命令能立刻告诉你当前数据库的字符集(比如图中的 utf8),这是判断编码问题根源的第一步。

学习和审计:作为开发者或管理员,你可能需要了解现有数据库的结构和设置,这个命令提供了最基础的信息。

修改数据库

对数据库的修改主要指的是修改数据库的字符集,校验规则

备份和恢复

  • -P :(可省略) 这是 --port的简写,后面接的是 MySQL 服务器的端口号 。如果 MySQL 服务没有使用默认的 3306 端口,就需要通过这个参数来指定。例如 -P3307

  • -B : 这是 --databases的简写,后面接一个或多个数据库名 。使用这个参数的关键区别在于,它会在导出的 SQL 文件中包含 CREATE DATABASEUSE语句。这意味着在恢复时,如果数据库不存在,它会自动创建 。这在备份多个数据库时尤其方便

备份

备份语法: # mysqldump -P3306 -u root -p 密码 -B 数据库名 > 数据库备份存储的文件路径

备份多个数据库:# mysqldump -u root -p -B 数据库名1 数据库名2 ... > 数据库存放路径

如果备份一个数据库时,没有带上-B参数, 在恢复数据库时,需要先创建空数据库,然后使用数据 库,再使用source来还原。

备份其中一张表:# mysqldump -u root -p 数据库名 表名1 表名2 > D:/mytest.sql

示例:将mytest库备份到文件(退出连接)

mysqldump -P3306 -u root -p123456 -B mytest > D:/mytest.sql

还原

mysql> source D:/mysql-5.7.22/mytest.sql;

查看连接情况

可以告诉我们当前有哪些用户连接到我们的MySQL,如果查出某个用户不是你正常登陆的,很有可能你 的数据库被人入侵了。以后大家发现自己数据库比较慢时,可以用这个指令来查看数据库连接情况。

表的操作

表的创建

character set 字符集,如果没有指定字符集,则以所在数据库的字符集为准 collate 校验规则,如果没有指定校验规则,则以所在数据库的校验规则为准

复制代码
create  table users (

id int,

name varchar(20) comment '用户名',

password char(32) comment '密码是32位的md5值',

birthday date comment '生日'

) character set utf8 engine MyISAM;

说明: 不同的存储引擎,创建表的文件不一样。

users 表存储引擎是 MyISAM ,在数据目中有三个不同的文件,分别是:

users.frm:表结构

users.MYD:表数据

users.MYI:表索引

查看表结构

desc 表名;

修改表

其中删除表:drop table 表名;

在项目实际开发中,经常修改某个表的结构,比如字段名字,字段大小,字段类型,表的字符集类型, 表的存储引擎等等。我们还有需求,添加字段,删除字段等等。这时我们就需要修改表。

关于表的各种修改(表格)

操作类别 具体操作 基本SQL语法示例 关键说明
🔧 字段操作 添加字段 ALTER TABLE 表名 ADD 字段名 数据类型 [约束]; 可使用 FIRST/AFTER指定位置
删除字段 ALTER TABLE 表名 DROP 字段名; 数据将永久丢失,需提前备份
修改字段名 ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型; 必须指定新数据类型,即使不修改
修改字段类型/约束 ALTER TABLE 表名 MODIFY 字段名 新数据类型 [约束]; 更改类型时需注意数据兼容性
🔄 表级属性 修改表名 ALTER TABLE 旧表名 RENAME TO 新表名;RENAME TABLE 旧表名 TO 新表名;
修改字符集 ALTER TABLE 表名 CONVERT TO CHARACTER SET 字符集名; 可转换整张表及所有字符型字段的字符集
修改存储引擎 ALTER TABLE 表名 ENGINE = 存储引擎名; 如将引擎从 MyISAM 改为 InnoDB
📑 索引与约束 添加主键/索引 ALTER TABLE 表名 ADD PRIMARY KEY (字段名);``ALTER TABLE 表名 ADD INDEX 索引名 (字段名);
删除主键/索引 ALTER TABLE 表名 DROP PRIMARY KEY;``ALTER TABLE 表名 DROP INDEX 索引名;

数据类型

数据类型分类

数值类型

bit类型

bit[(M)] : 位字段类型。M表示每个值的位数,范围从1到64。如果M被忽略,默认为1。

注意:bit字段在显示时,是按照ASCII码对应的值显示。

如果我们有这样的值,只存放0或1,这时可以定义bit(1)。这样可以节省空间

小数类型:

float[(m, d)] [unsigned] : m指定显示长度,d指定小数位数,占用空间4个字节

decimal(m, d) [unsigned] : 定点数m指定长度,d表示小数点的位数

说明:float表示的精度大约是7位。 decimal整数最大位数m为65。支持小数最大位数d是30。如果d被省略,默认为0.如果m被省略, 默认是10。

字符串类型

char(L): 固定长度字符串,L是可以存储的长度,单位为字符,最大长度值可以为255

varchar(L): 可变长度字符串,L表示字符长度,最大长度65535个字节

关于varchar(len),len到底是多大,这个len值,和表的编码密切相关:

varchar长度可以指定为0到65535之间的值,但是有1 - 3 个字节用于记录数据大小,所以说有效字 节数是65532。 当我们的表的编码是utf8时,varchar(n)的参数n最大值是65532/3=21844[因为utf中,一个字符占 用3个字节],如果编码是gbk,varchar(n)的参数n最大是65532/2=32766(因为gbk中,一个字符 占用2字节)。

数据确定长度,使用定长(char)

数据长度有变化,就使用变长(varchar),但要保证最长的能存的进去。

定长的磁盘空间比较浪费,但是效率高。 变长的磁盘空间比较节省,但是效率低。

定长的意义是,直接开辟好对应的空间 变长的意义是,在不超过自定义范围的情况下,用多少,开辟多少。

日期和时间类型

date :日期 'yyyy-mm-dd' ,占用三字节

datetime 时间日期格式 'yyyy-mm-dd HH:ii:ss' 表示范围从1000 到 9999 ,占用八字节

timestamp :时间戳,从1970年开始的 yyyy-mm-dd HH:ii:ss 格式和 datetime 完全一致,四字节

enum 和set

ENUM 是一种枚举类型,用于实现"单选"功能。

  • 核心特性 :在一个ENUM字段中,你只能从预定义的选项中选择一个 值。例如,表示性别的字段 (ENUM('男', '女')) 或订单状态 (ENUM('待支付', '已发货', '已完成')) 。

  • 存储原理 :出于效率考虑,MySQL并非直接存储字符串,而是在内部将每个选项映射为一个整数索引(从1开始)。'选项1'对应1,'选项2'对应2,以此类推 。正因为如此,ENUM类型非常节省存储空间。对于1-255个选项,仅需1个字节;256-65535个选项,需要2个字节 。

  • 默认值 :如果字段被定义为 NOT NULL,其默认值将是选项列表中的第一个值。如果允许 NULL,则默认值为 NULL

  • 排序规则 :ENUM类型的值是按照其定义的顺序(即整数索引)进行排序的,而非字母顺序 。

SET 是一种集合类型,用于实现"多选"功能。

  • 核心特性 :在一个SET字段中,可以从预定义的选项中选择零个、一个或多个值 。多个值之间用逗号分隔,例如兴趣爱好字段 (SET('读书', '音乐', '运动')) 或用户权限 。

  • 存储原理:SET类型在内部使用位图(bitmap)方式存储。每个选项代表一个二进制位(1, 2, 4, 8...),选中的选项其对应位被置为1 。这种机制也决定了其存储空间是固定的,根据选项的数量(1-8个成员占1字节,9-16占2字节,最多64个成员占8字节) 。

  • 输入与显示:插入数据时,可以按任意顺序书写选项,MySQL在存储时会自动按照定义时的顺序进行规范化和去重 。

特性 ENUM (枚举) SET (集合)
选择方式 单选 多选
设计目的 从多个值中选取一个 从多个值中选取多个
内部存储 整数索引(1, 2, 3...) 位图(1, 2, 4, 8...)
最大成员数 65,535 64
典型用例 性别、状态、分类 标签、权限、兴趣爱好

表的约束

真正约束字段的是数据类型,但是数据类型约束很单一,需要有一些额外的约束,更好的保证数据的合 法性,从业务逻辑角度保证数据的正确性。比如有一个字段是email,要求是唯一的。

表的约束很多,这里主要介绍如下几个: null/not null,default, comment, zerofill,primary key,auto_increment,unique key。

注意:not null和defalut一般不需要同时出现,因为default本身有默认值,不会为空

列描述

列描述:comment,没有实际含义,专门用来描述字段,会根据表创建语句保存,用来给程序员或DBA 来进行了解。通过show可以看到

zerofill

MySQL 中的 ZEROFILL属性主要用于格式化数值字段的显示效果。

特性维度 说明与要点
本质 一种显示格式化属性,不影响实际存储的数值本身。
显示规则 显示数值时,若实际位数小于指定显示宽度,左侧用零填充至该宽度。
自动 UNSIGNED 一旦为字段设置 ZEROFILL,该字段会自动变为无符号类型,不能存储负数。
显示宽度作用 仅影响在特定客户端下的显示格式,不决定存储范围或输入值的长度限制。
适用数据类型 整数类型(如 INT, SMALLINT)和浮点数类型(如 DECIMAL)。

在创建表或修改表结构时,可以在数值类型的字段定义后加上 ZEROFILL关键字。显示宽度在括号内指定。

cpp 复制代码
-- 创建表时指定
create table example_table (
    id int(5) zerofill,
    account_number int(8) zerofill
);

-- 修改现有表字段
alter table example_table modify account_number int(10) zerofill;

插入数据时,如果数值的位数小于指定的显示宽度,查询结果中就会看到补零的效果。例如,向 int(5) zerofill字段插入数字 42,查询结果会显示为 00042

如果插入的数值实际位数大于指定的显示宽度,MySQL 会直接显示完整的数值,不会进行截断或补零 。

注意:

  1. 自动设置为无符号 :为字段设置 zerofill属性后,该字段会自动变为 unsigned,这意味着你不能在该字段中存储负数 。

  2. 仅影响显示zerofill只影响查询结果在特定数据库客户端下的显示格式,并不会改变数据在磁盘上的存储方式。当你进行数值计算时,补零效果通常会消失。例如,对 zerofill字段进行 +1操作,结果将是一个普通的整数,没有补零 。

  3. 客户端依赖性:补零效果在某些数据库客户端工具中可能不会显示,这与工具的数据呈现方式有关 。

  4. 显示宽度非存储限制 :指定的显示宽度(如 int(5)中的 5)仅仅是提示性的,用于格式化显示,并不限制你能存储的数值的实际范围。一个 int(5)字段仍然可以存储 -21474836482147483647(有符号时)或 04294967295(无符号时)范围内的值。

主键

主键:primary key用来唯一的约束该字段里面的数据 ,不能重复,不能为空,一张表中最多只能有一个 主键;主键所在的列通常是整数类型。主键约束:主键对应的字段中不能重复,一旦重复,操作失败。

创建表的时候直接在字段上指定主键

当表创建好以后但是没有主键的时候,可以再次追加主键:alter table 表名 add primary key(字段列表)

删除主键 alter table 表名 drop primary key;

区别:

特性 单列主键 复合主键
定义 用一个列做主键 用多个列的组合做主键
数量 只有一个主键 也只有一个主键(但由多列组成)
示例 id INT PRIMARY KEY PRIMARY KEY(col1, col2)
索引 自动创建唯一索引 自动创建复合唯一索引
查询 按主键列查最快 按最左前缀查最快

复合主键

在创建表的时候,在所有字段之后,使用primary key(主键字段列表)来创建主键,如果有多个字段 作为主键,可以使用复合主键。

自增长

在MySQL中,自增长(AUTO_INCREMENT)是一个非常有用的特性,它能自动为字段(通常是主键)生成唯一的递增值,简化了数据插入操作。

auto_increment:当对应的字段,不给值,会自动的被系统触发,系统会从当前字段中已经有的最大值 +1操作,得到一个新的不同的值。通常和主键搭配使用,作为逻辑主键。

自增长的特点:

任何一个字段要做自增长,前提是本身是一个索引(key一栏有值);m 自增长字段必须是整数

一张表最多只能有一个自增长

唯一键

在 mysql 中,唯一键(unique key)是一种重要的约束,用于确保表中某个字段或字段组合的值是唯一的。虽然它和主键都能保证唯一性,但两者在使用上有所不同。

一张表中有往往有很多字段需要唯一性,数据不能重复,但是一张表中只能有一个主键:唯一键就可以 解决表中有多个字段需要唯一性约束的问题。

我们可以简单理解成,主键更多的是标识唯一性 的。而唯一键更多的是保证在业务上,不要和别的信息 出现重复。

特性 主键 (primary key) 唯一键 (unique key)
数量限制 每张表只能有 1个 每张表可以有 多个
是否允许为空 不允许​ (not null) 允许,且多个空值(null)不视为重复
核心作用 唯一标识每一行记录 确保业务数据的唯一性(如用户名、邮箱)

创建方法:

1. 建表时在字段后直接指定

2. 建表时在所有字段后统一指定

这种方法更清晰,尤其适合设置复合唯一键(多个字段组合唯一)或为约束命名。

cpp 复制代码
create table classroom (
    id int primary key,
    class_id int not null,
    seat_number int not null,
    student_name varchar(50),
    unique key uk_class_seat (class_id, seat_number)  -- 复合唯一键:同一班级内座位号唯一
);

注意 :复合唯一键要求的是几个字段的组合值不能重复,但单个字段的值是可以重复的

3. 表已存在时使用 alter table添加

cpp 复制代码
-- 为已存在的 products 表的 product_code 字段添加唯一键
alter table products add unique key (product_code);

-- 也可以为约束命名,便于后续管理
alter table employees add constraint uk_employee_id unique (employee_id);
  • 查看唯一键 :使用 show index from命令可以查看表中的索引信息,包括唯一键。

  • 删除唯一键 :通过 drop index命令删除唯一键。删除时需要指定创建时生成的索引名

  • alter table users drop index username;

在实际数据库设计中,一个常见的做法是:

  • 使用一个与业务无关的自增id字段作为主键,用于唯一标识记录。

  • 将业务上要求唯一的字段(如工号、身份证号、邮箱等)设置为唯一键,以保证业务逻辑的正确性。

cpp 复制代码
create table students (
    id int auto_increment primary key,  -- 逻辑主键
    student_number char(10) not null unique key,  -- 学号,业务上唯一
    name varchar(20) not null,
    id_card char(18) unique key,  -- 身份证号,唯一但允许暂未登记(null)
    unique key uk_number (student_number)  -- 另一种写法,效果同上行
);

外键

外键是数据库设计中用于建立表间关联、保证数据完整性的重要约束。

1. 什么是外键

外键是表中的一个字段(或字段组),其值必须匹配另一个表的主键或唯一键的值。它创建了表之间的"从属"关系 。外键约束主要定义在从表上,主表则必须是有主键约束或unique 约束。当定义外键后,要求外键列数据必须在主表的主键列存在或为null。

语法;foreign key (字段名) references 主表(列)

  • 主表与被参照表:被外键引用的表称为主表或被参照表。

  • 从表与参照表:包含外键的表称为从表或参照表 。

  • 核心作用:确保只能向从表插入在主表中存在对应值的数据,防止出现"孤儿记录",从而维护数据的一致性和完整性 。

2. 外键的作用

外键约束主要通过以下方式保障数据关系 :

  • 阻止非法操作

    • 禁止在从表中插入外键值在主表中不存在的数据。

    • 禁止删除主表中已被从表记录引用的行(取决于约束规则)。

    • 禁止修改主表中已被引用的主键值(取决于约束规则)。

  • 启用级联操作:可以设置当主表数据变更时,自动更新或删除从表中的相关数据。

核心区别。

特征 主表 (父表) 从表 (子表)
角色 被引用的核心实体 引用主表,存储扩展或关联信息
键约束 必须包含**主键 (Primary Key)**​ 或唯一键 包含外键 (Foreign Key),引用主表主键
数据依赖性 独立存在,不依赖于从表 数据依赖于主表,要求外键值必须在主表中存在
操作限制 受保护(如被引用的记录可能无法直接删除) 受约束(如插入数据时外键值必须有效)

基本查询

CRUD:create创建 retrieve读取 update更新 delete删除

只记录忘记或者模糊的地方

create

多行数据 + 指定列插入

插入两条记录,value_list 数量必须和指定列数量及顺序一致

由于 主键 或者 唯一键 对应的值已经存在而导致插入失败,可以选择性的进行同步更新操作

语法:

cpp 复制代码
INSERT ... ON DUPLICATE KEY UPDATE 
column = value [, column = value] ... 
cpp 复制代码
INSERT INTO students (id, sn, name) VALUES (100, 10010, '唐大师')
 ON DUPLICATE KEY UPDATE sn = 10010, name = '唐大师';
 Query OK, 2 rows affected (0.47 sec)

替换

-- 主键 或者 唯一键 没有冲突,则直接插入;

-- 主键 或者 唯一键 如果冲突,则删除后再插

retrieve

-- 通常情况下不建议使用 * 进行全列查询

-- 1. 查询的列越多,意味着需要传输的数据量越大;

-- 2. 可能会影响到索引的使用。

指定列查询 -- 指定列的顺序不需要按定义表的顺序来

查询字段为表达式

-- 表达式不包含字段

--表达式包含一个或者多个字段

为查询结果指定别名

结果去重

-- 98 分重复了 select distinct math from exam_result;

where条件

比较运算符:

逻辑运算符:

-- 基本比较 SELECT name, english FROM exam_result WHERE english < 60;

-- 使用 AND 进行条件连接 SELECT name, chinese FROM exam_result WHERE chinese >= 80 AND chinese <= 90;

-- 使用 BETWEEN ... AND ... 条件 SELECT name, chinese FROM exam_result WHERE chinese BETWEEN 80 AND 90;

-- 使用 OR 进行条件连接 SELECT name, math FROM exam_result WHERE math = 58 OR math = 59 OR math = 98 OR math = 99;

-- 使用 IN 条件 SELECT name, math FROM exam_result WHERE math IN (58, 59, 98, 99);

% 匹配任意多个(包括 0 个)任意字符 SELECT name FROM exam_result WHERE name LIKE '孙%';

_ 匹配严格的一个任意字符 SELECT name FROM exam_result WHERE name LIKE '孙_';

语文成绩好于英语成绩的同学

WHERE 条件中比较运算符两侧都是字段

SELECT name, chinese, english FROM exam_result WHERE chinese > english;

总分在 200 分以下的同学

WHERE 条件中使用表达式 ,别名不能用在 WHERE 条件中

-- AND 与 NOT 的使用

SELECT name, chinese FROM exam_result WHERE chinese > 80 AND name NOT LIKE '孙%';

孙某同学,否则要求总成绩 > 200 并且 语文成绩 < 数学成绩 并且 英语成绩 > 80

综合性查询 SELECT name, chinese, math, english, chinese + math + english 总分 FROM exam_result WHERE name LIKE '孙_' OR ( chinese + math + english > 200 AND chinese < math AND english > 80 );

结果排序

注意:当你的查询语句中没有明确使用 ORDER BY指定排序方式时,数据库返回给你的行的顺序是完全不可预测、不保证的。

-- NULL 视为比任何值都小,降序出现在最下面

SELECT name, qq FROM students ORDER BY qq DESC;

查询同学各门成绩,依次按 数学降序,英语升序,语文升序的方式显示

-- 多字段排序,排序优先级随书写顺序:

SELECT name, math, english, chinese FROM exam_result ORDER BY math DESC, english, chinese;

查询同学及总分,由高到低

-- ORDER BY 中可以使用表达式

SELECT name, chinese + english + math FROM exam_result ORDER BY chinese + english + math DESC;

-- ORDER BY 子句中可以使用列别名

SELECT name, chinese + english + math 总分 FROM exam_result ORDER BY 总分 DESC;

查询姓孙的同学或者姓曹的同学数学成绩,结果按数学成绩由高到低显示

-- 结合 WHERE 子句 和 ORDER BY 子句

SELECT name, math FROM exam_result WHERE name LIKE '孙%' OR name LIKE '曹%' ORDER BY math DESC;

查询分页结果

语法:

-- 起始下标为 0

-- 从 0 开始,筛选 n 条结果 SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT n;

-- 从 s 开始,筛选 n 条结果 SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT s, n ;

-- 从 s 开始,筛选 n 条结果,比第二种用法更明确:SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT n OFFSET s;

建议:对未知表进行查询时,最好加一条 LIMIT 1,避免因为表中数据过大,查询全表数据导致数据库卡死

举例:

按 id 进行分页,每页 3 条记录,分别显示 第 1、2、3 页

update

语法:

UPDATE table_name SET column = expr [, column = expr ...] [WHERE ...] [ORDER BY ...] [LIMIT ...]

一次更新多个列:

UPDATE exam_result SET math = 60, chinese = 70 WHERE name = '曹孟德';

将总成绩倒数前三的 3 位同学的数学成绩加上 30 分

-- 更新值为原值基础上变更 -- 查看原数据 -- 别名可以在ORDER BY中使用

SELECT name, math, chinese + math + english 总分 FROM exam_result ORDER BY 总分 LIMIT 3;

-- 数据更新,不支持 math += 30 这种语法 :

UPDATE exam_result SET math = math + 30 ORDER BY chinese + math + english LIMIT 3;

delete

语法: DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ...]

我们先创建一个名为for_delete的测试表格

-- 删除整表数据 :DELETE FROM for_delete;

-- 再插入一条数据,自增 id 在原值上增长 INSERT INTO for_delete (name) VALUES ('D');

截断表

truncate [table] table_name

  1. 只能对整表操作,不能像 DELETE 一样针对部分数据操作; 2. 实际上 MySQL 不对数据操作,所以比 DELETE 更快,但是TRUNCATE在删除数据的时候,并不经过真正的事 物,所以无法回滚 3. 会重置 AUTO_INCREMENT 项

插入查询结果

INSERT INTO table_name [(column [, column ...])] SELECT ...

聚合函数

COUNT([DISTINCT] expr) 返回查询到的数据的 数量

SUM([DISTINCT] expr 返回查询到的数据的 总和,不是数字没有意义

AVG([DISTINCT] expr) 返回查询到的数据的 平均值,不是数字没有意义

MAX([DISTINCT] expr) 返回查询到的数据的 最大值,不是数字没有意义

MIN([DISTINCT] expr) 返回查询到的数据的 最小值,不是数字没有意义

-- 使用 * 做统计,不受 NULL 影响 SELECT COUNT(*) FROM students;

-- 使用表达式做统计 SELECT COUNT(1) FROM students;

-- NULL 不会计入结果 SELECT COUNT(qq) FROM students;

-- COUNT(math) 统计的是全部成绩 SELECT COUNT(math) FROM exam_result;

-- COUNT(DISTINCT math) 统计的是去重成绩数量 SELECT COUNT(DISTINCT math) FROM exam_result;

统计数学成绩总分

SELECT SUM(math) FROM exam_result;

-- 不及格 < 60 的总分,没有结果,返回 NULL SELECT SUM(math) FROM exam_result WHERE math < 60;

统计平均总分 SELECT AVG(chinese + math + english) 平均总分 FROM exam_result;

返回英语最高分 SELECT MAX(english) FROM exam_result;

group by子句的使用

在select中使用group by 子句可以对指定列进行分组查询 select column1, column2, .. from table group by column;

显示每个部门的每种岗位的平均工资和最低工资 select avg(sal),min(sal),job, deptno from EMP group by deptno, job;

显示平均工资低于2000的部门和它的平均工资

统计各个部门的平均工资 select avg(sal) from EMP group by deptno

having和group by配合使用,对group by结果进行过滤 select avg(sal) as myavg from EMP group by deptno having myavg<2000;

--having经常和group by搭配使用,作用是对分组进行筛选,作用有些像where。

面试题:SQL查询中各个关键字的执行先后顺序 from > on> join > where > group by > with > having > select > distinct > order by > limit

函数

日期函数

字符串函数

数学函数

其他函数

user() 查询当前用户 select user();

md5(str)对一个字符串进行md5摘要,摘要后得到一个32位字符串: select md5('admin')

database()显示当前正在使用的数据库 select database();

password()函数,MySQL数据库使用该函数对用户加密 select password('root');

ifnull(val1, val2) 如果val1为null,返回val2,否则返回val1的值 select ifnull('abc', '123');

复合查询

查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的 J

select * from EMP where (sal>500 or job ='MANAGER') and ename like 'J%';

按照部门号升序而雇员的工资降序排序

select * from EMP order by deptno,sal desc;

表的内连和外连

内连接

内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连 接,也是在开发过程中使用的最多的连接查询。

语法: select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件;

显示SMITH的名字和部门名称

-- 用前面的写法

select ename, dname from EMP, DEPT where EMP.deptno=DEPT.deptno and ename='SMITH';

-- 用标准的内连接写法

select ename, dname from EMP inner join DEPT on EMP.deptno=DEPT.deptno and ename='SMITH';

外连接

外连接分为左外连接和右外连接

左外连接:如果联合查询,左侧的表完全显示我们就说是左外连接。

语法:select 字段名 from 表名1 left join 表名2 on 连接条件

右外连接:如果联合查询,右侧的表完全显示我们就说是右外连接。

语法: select 字段 from 表名1 right join 表名2 on 连接条件;

索引特性

索引:提高数据库的性能,索引是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行 正确的 create index,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高 是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个 海量数据的检索速度。

常见索引分为: 主键索引(primary key) 唯一索引(unique) 普通索引(index)

全文索引(fulltext)--解决中子文索引问题。

认识磁盘

MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。

数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格子中,就是我们经常所说 的扇区。当然,数据库文件很大,也很多,一定需要占据多个扇区。

最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。 而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。

定位扇区

柱面(磁道): 多盘磁盘,每盘都是双面,大小完全相等。那么同半径的磁道,整体上便构成了一个柱 面

每个盘面都有一个磁头,那么磁头和盘面的对应关系便是1对1的

所以,我们只需要知道,磁头(Heads)、柱面(Cylinder)(等价于磁道)、扇区(Sector)对应的编 号。即可在磁盘上定位所要访问的扇区。这种磁盘数据定位方式叫做CHS 。不过实际系统软件使用的并不是CHS(但是硬件是),而是 LBA ,一种线性地址,可以想象成虚拟地址与物理地址。系统 将LBA地址最后会转化成为 CHS ,交给磁盘去进行数据读取。不过,我们现在不关心转化细节,知 道这个东西,让我们逻辑自洽起来即可。

总结:

  • 数据最终存储在磁盘的扇区中。

  • 操作系统与磁盘交互的基本单位是数据块(通常为4KB),而非单个扇区,以减少I/O次数、降低对硬件的依赖。

  • MySQL(InnoDB引擎) ​ 与磁盘交互的基本单位是 Page(页) ,大小为 16KB。所有数据文件在磁盘中都以Page形式保存。

  • MySQL在内存中申请 Buffer Pool​ 来缓存磁盘的Page数据,以减少实际的磁盘I/O操作。

MySQL 与磁盘交互基本单位

而MySQL作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高 基本的IO效率, MySQL 进行IO的基本单位是 16KB

磁盘这个硬件设备的基本单位是 512 字节,而 即,M ySQL 和磁盘进行数据交互的基本单位是 MySQL InnoDB引擎使用 16KB 进行IO交互。 16KB 。这个基本数据单元,在 MySQL 这里叫做page(注 意和系统的page区分)

建立共识

MySQL 中的数据文件,是以page为单位保存在磁盘当中的。

MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数 据。

而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新 策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位 就是Page。

为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称 Buffer Pool的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进 行IO交互。 为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数

索引的理解

  • 单Page结构:内部使用目录优化查找

MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要先描述,在组织,我们目前可以简单理解成一个个独立文件是有一个或者多个Page构成的。

不同的Page,在 MySQL 中,都是 16KB ,使用 因为有主键的问题, prev 和 next 构成双向链表 MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看 出,数据是有序且彼此关联的。

  • 多Page结构:通过B+树组织多个Page
  • 为什么选择B+树而不是其他数据结构
    • 链表:线性遍历效率低
    • 二叉搜索树:可能退化为链表
    • AVL/红黑树:树高过高,IO次数多
    • Hash:不支持范围查询
    • B树 vs B+树:B+树更矮,叶子节点相连便于范围查询

重点掌握原因:这是MySQL索引的核心实现原理,面试和实际优化都必须掌握。

聚簇索引 vs 非聚簇索引

核心对比:

特性 InnoDB(聚簇索引) MyISAM(非聚簇索引)
数据存储 索引和数据在一起 索引和数据分离
主键索引 叶子节点包含完整数据 叶子节点只包含数据地址
辅助索引 需要回表查询 直接指向数据地址

不同存储引擎的索引实现差异直接影响查询性能和设计决策。

索引操作

创建索引的三种方式:

  1. 主键索引
sql 复制代码
-- 方式1:字段后直接指定
create table user1(id int primary key, name varchar(30));

-- 方式2:表定义最后指定
create table user2(id int, name varchar(30), primary key(id));

-- 方式3:创建表后添加
alter table user3 add primary key(id);

2、唯一索引

sql 复制代码
-- 方式1:字段后指定unique
create table user4(id int primary key, name varchar(30) unique);

-- 方式2:表定义最后指定
create table user5(id int primary key, name varchar(30), unique(name));

-- 方式3:创建表后添加
alter table user6 add unique(name);

3、普通索引

sql 复制代码
-- 方式1:表定义中指定
create table user8(id int primary key, name varchar(20), index(name));

-- 方式2:创建表后添加
alter table user9 add index(name);

-- 方式3:单独创建索引
create index idx_name on user10(name);

4、全文索引

sql 复制代码
-- 方式1:表定义中指定
create table user8(id int primary key, name varchar(20), index(name));

-- 方式2:创建表后添加
alter table user9 add index(name);

-- 方式3:单独创建索引
create index idx_name on user10(name);

索引管理命令:

  • 查看索引:show keys from 表名show index from 表名
  • 删除索引:alter table 表名 drop index 索引名drop index 索引名 on 表名

事务管理

CURD不加控制,会有什么问题?

CURD满足什么属性,能解决上述问题? 1. 买票的过程得是原子 2. 买票互相应该不能影响 3. 买完票应该要永久有效 4. 买前,和买后都要是确定的状态

什么是事务?

**事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部 失败,是一个整体。**MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的 数据是不相同的。事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。

  • InnoDB 支持事务、行级锁、外键
  • MyISAM 不支持事务
  • Savepoint 也要引擎支持,InnoDB 支持,MyISAM 不支持。

ACID

  • 原子性:要么全成功,要么全失败
  • 一致性:开始前和结束后数据库状态要正确
  • 隔离性:并发事务之间尽量互不干扰
  • 持久性:提交后结果永久有效

一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中 间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个 事务从来没有执行过一样。

**一致性:**在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工 作。

**隔离性:**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read (Suncommitted )、读提交( read committed)、可重复读( erializable ) repeatable read)和串行化

**持久性:**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

上面四个属性,可以简称为 ACID 。 原子性(Atomicity,或称不可分割性) 一致性(Consistency) 隔离性(Isolation,又称独立性) 持久性(Durability)。

事务提交方式

事务的提交方式常见的有两种: 自动提交 手动提交

查看事务提交方式 mysql> show variables like 'autocommit';

用 SET 来改变 MySQL 的自动提交模式: mysql> SET AUTOCOMMIT=0; #禁止自动提交;=1 开启自动提交

事务常见操作方式

语句 描述 示例
begin;start transaction; 显式地开始一个新事务。这会暂时禁用自动提交模式,直到事务被提交或回滚。 begin; start transaction;
commit; 提交当前事务,使事务中的所有更改永久生效。 commit;
rollback; 回滚当前事务,撤销该事务中进行的所有更改。 rollback;
savepoint savepoint_name; 在当前事务内创建一个名为 savepoint_name的保存点。 savepoint sp1;
rollback to savepoint savepoint_name; 将事务回滚到之前定义的特定保存点,而不是回滚整个事务。 rollback to savepoint sp1;
release savepoint savepoint_name; 从当前事务中移除指定的保存点。 release savepoint sp1;
set autocommit = 0; 关闭当前会话的自动提交模式。之后的所有语句将在同一个事务中,直到显式执行 commitrollback set autocommit = 0;
set autocommit = 1; 开启当前会话的自动提交模式(mysql 默认状态)。每一条 sql 语句都会被视为一个独立的事务并立即提交。

结论: 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是 否设置set autocommit无关。

容易混淆的点:

  • autocommit=ON:每条 SQL 默认自己就是一个小事务,执行完自动提交
  • 但只要你 begin 了,就进入"手动事务"模式,后续不再自动提交,直到 commit / rollback

事务可以手动回滚,同时,当操作异常,MySQL会自动回滚

对于I nnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC )

事务操作注意事项

如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务 还没有提交)

如果一个事务被提交了(commit),则不可以回退(rollback) 可以选择回退到哪个保存点

InnoDB 支持事务, MyISAM 不支持事务

开始事务可以使 start transaction 或者

事务隔离级别

隔离性本质是什么

隔离级别的本质,就是规定一个事务在执行过程中,能看到别的事务的哪一部分结果。

  • 一个事务不是瞬间完成的,而是经历"执行前、执行中、执行后"
  • 单个事务从用户视角看,要么看到执行前,要么看到执行后,这体现原子性
  • 但多个事务并发执行时,它们的 SQL 可能交错,于是会互相影响
  • 所以就需要"隔离性"与"隔离级别"来控制"谁该看到谁的数据"。

mysql 的事务隔离级别定义了多个并发事务在访问和修改相同数据时,彼此之间的可见性和影响规则。它核心解决的是数据库在高并发环境下可能出现的数据一致性问题

一个事务从 begin -> CURD -> commit 中间是有执行过程的。原子性只保证"从事务自己视角看,最终结果要么全有,要么全无";但是多个事务同时执行时,它们的操作会交错,所以还会互相影响。隔离性的作用,就是让事务在执行过程中尽量不被别的事务干扰

mysql 的默认事务隔离级别是 repeatable read(可重复读)。这是由 innodb 存储引擎实现的。

隔离级别与并发问题
隔离级别 脏读 (dirty read) 不可重复读 (non-repeatable read) 幻读 (phantom read) 加锁读
**读未提交 (read uncommitted)**​ 可能 (√) 可能 (√) 可能 (√) 不加锁
**读已提交 (read committed)**​ 不可能 可能 (√)原因是:每次读都可能基于最新提交结果 可能 (√) 不加锁
**可重复读 (repeatable read)**​ 不可能 不可能 可能 (√) ​ / 不可能 (x)* 不加锁
**串行化 (serializable)**​ 不可能 不可能 不可能 加锁
各级别特性总结
隔离级别 核心描述与特点 性能与并发 备注
读未提交 事务可以读取其他未提交事务的数据。几乎无隔离性,会引发所有并发问题。 最高(几乎不加锁) 严重不建议使用
读已提交 一个事务只能看到其他已提交事务所做的改变。 较高 许多数据库(如oracle)的默认级别。会引起不可重复读和幻读。
**可重复读 (mysql默认)**​ 确保同一事务中,多次读取同一条数据会看到同样的结果。 良好 mysql的默认隔离级别。通过mvcc机制实现。在innodb中解决了幻读。
串行化 最高隔离级别,把事务尽量排队串行执行。强制事务排序,使之不可能相互冲突。所有读操作都会加共享锁。 最低(大量锁等待) 事务最高隔离级别,实际生产基本不使用。性能低、锁竞争强、容易阻塞。

并发问题定义

  • 脏读:在 RU 下,一个事务可以读到另一个事务"还在执行中、还没 commit"的修改。如事务b读到了事务a未提交的更新。

终端 A:

sql 复制代码
begin;
update account set blance=123.0 where id=1;
-- 不 commit

终端 B:

sql 复制代码
begin;
select * from account;
  • 不可重复读 :在同一个事务内,对同一条记录 的两次读取结果不同(因为其他事务修改并提交了该记录)。如"读已提交"实验所示。

  • 终端 A:

    sql 复制代码
    begin;
    update account set blance=321.0 where id=1;
    -- 后面 commit

    终端 B:

    sql 复制代码
    begin;
    select * from account; -- A commit 前,看到旧值 123
    select * from account; -- A commit 后,再看变成 321

    在 B 同一个事务里 ,两次 select 读到了不同结果。这就叫 不可重复读 non-repeatable read

  • 幻读 :在同一个事务内,两次执行相同条件 的查询,返回的结果集行数 不同(因为其他事务插入或删除了记录并提交)。insert操作可能引发此现象,mysql通过Next-Key Lock(间隙锁 + 行锁)解决了该问题。

  • 串行化 Serializable:

    两个终端都 begin

  • 终端 A 先 select

  • 终端 B 也 select

  • A 或 B 再去 update

  • 更新操作会被阻塞,直到另一边事务提交。材料里有一个更新耗时 18.19 秒,就是等待对方释放。

Serializable 基本靠加锁把并发尽量"串成队列",安全性最高,但并发性能很差。

结论:Serializable 不是"更聪明地并发",而是"尽量不要并发"。

  • 所有操作都加锁
  • 不会有并发异常
  • 但效率很低,几乎不会采用。
查看与设置语法
  • 改 session 只影响当前连接
  • 改 global 会影响后续新连接
sql 复制代码
select @@global.tx_isolation;

select @@session.tx_isolation;

set [session | global] transaction isolation level ...
sql 复制代码
-- 查看当前会话隔离级别
select @@tx_isolation;
-- 设置当前会话隔离级别
set session transaction isolation level read committed;
-- 设置全局隔离级别
set global transaction isolation level repeatable read;
mysql事务隔离级别查看与设置操作
操作类型 语句 描述与示例
查看隔离级别 select @@global.tx_isolation; 查看全局 事务隔离级别。 示例:mysql> select @@global.tx_isolation;
select @@session.tx_isolation; 查看当前会话 的事务隔离级别。 示例:mysql> select @@session.tx_isolation;
select @@tx_isolation; 查看当前隔离级别(默认同@@session.tx_isolation)。 示例:mysql> select @@tx_isolation;
设置隔离级别 set [session] transaction isolation level {level}; 设置当前会话 的隔离级别,仅影响后续事务。 可选级别read uncommittedread committedrepeatable readserializable。 示例1:set session transaction isolation level read committed; 示例2:set transaction isolation level serializable;
set global transaction isolation level {level}; 设置全局 事务隔离级别,影响所有新连接的会话。 示例:set global transaction isolation level repeatable read; 注意:文档中指出,设置全局级别后,可能需要关闭并重新连接mysql客户端才能在新会话中生效。
总结:
  • RU:可能有脏读、不可重复读、幻读

  • RC:解决脏读,但仍可能不可重复读、幻读

  • RR:解决脏读、不可重复读;课件结合 InnoDB 说明实际还能处理幻读

  • Serializable:都解决,但靠重锁和阻塞换来的

  • 两个区分点:

  • 不可重复读重点是"修改/删除":同样条件,再读值变了

  • 幻读重点是"新增":同样条件,再读记录数变了

如何理解隔离性2

后面还要更新,先不讲

视图特性

视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表,基表的数据变化也会影响到视图。

它和真正的表不同:

  • 真实表:数据真的存储在磁盘里
  • 视图 :通常只是保存了一条 select 查询逻辑

也就是说:你看到的视图,本质上像是一个"保存下来的查询结果入口"

基本使用:

sql 复制代码
create view 视图名 as select语句;

as 后面:定义这个视图的数据来源,也就是查询语句

sql 复制代码
create view v_ename_dname as
select ename, dname
from EMP, DEPT
where EMP.deptno=DEPT.deptno;

视图可以像普通表一样被 select 查询。

也就是说,对于使用者来说,你不需要每次都写:

sql 复制代码
select ename, dname
from EMP, DEPT
where EMP.deptno=DEPT.deptno;

你只需要写:

sql 复制代码
select * from v_ename_dname;

这就是视图的便利性。

删除视图

sql 复制代码
drop view 视图名;

删除视图删掉的是:

  • 这个"虚拟表"的定义
  • 这个封装好的查询入口

删除视图会不会删除基表数据

视图规则和限制

与表一样,必须唯一命名(不能出现同名视图或表名)

创建视图数目无限制,但要考虑复杂查询创建为视图之后的性能影响

视图不能添加索引,也不能有关联的触发器或者默认值。因为视图通常不是物理存储表。

视图可以提高安全性,必须具有足够的访问权限。为什么视图能提高安全性?因为你可以只把一部分列、一部分行开放给别人看。

order by 可以用在视图中,但是如果从该视图检索数据 select 中也含有 order by,那么该视图中的 order by 将被覆盖

解释:

情况 A:视图里写了 order by。此时这个视图定义里已经有排序了。

sql 复制代码
create view v1 as
select ename, sal
from emp
order by sal desc;

情况 B:外面再查视图时也写 order by

sql 复制代码
select * from v1 order by ename asc;

这时最终**按外层 selectorder by**排序

视图可以和表一起使用

视图不是孤立的,它可以像普通表一样参与 SQL。

比如:

  • 视图和表做连接
  • 视图和视图做连接
  • 在复杂查询里把视图当成一个数据源

例如:

sql 复制代码
select *
from v_ename_dname v, bonus b
where v.ename = b.ename;

这说明在 SQL 语义上,视图可以像表一样参与运算。

用户管理

用户信息

MySQL 中的用户信息,存储在系统数据库 mysqluser 表中。

sql 复制代码
use mysql;
select host,user,authentication_string from user;

这里几个字段要理解透

host:表示这个用户允许从哪个主机登录

例如:

  • 'localhost':只能从本机登录
  • '%':表示可以从任意主机登录

这个字段非常关键,因为 MySQL 里的用户不是单纯按用户名区分,而是按:**'用户名'@'主机'**一起区分。也就是说:

  • 'whb'@'localhost'
  • 'whb'@'%'就表示这个用户可以从任意主机 登录。这在生产环境通常要非常谨慎,
    不要轻易添加一个可以从任意地方登录的 user。

这是两个不同的用户身份

user用户名。

这个很好理解,就是登录 MySQL 时用的账号名。

authentication_string用户密码加密后保存的内容。

通过 password 函数加密后的值。

*_priv表示该用户拥有的权限。
  • Select_priv
  • Insert_priv
  • Update_priv

这些字段本质上描述的是:这个用户可以干什么。

用户操作

创建用户。MySQL 里的用户不是单纯按用户名区分,而是按:**'用户名'@'主机'**一起区分。

  • 用户名:whb
  • 允许登录的位置:localhost
sql 复制代码
create user '用户名'@'登陆主机/ip' identified by '密码';

create user 'whb'@'localhost' identified by '12345678';

删除用户

sql 复制代码
drop user '用户名'@'主机名';
drop user 'whb'@'localhost';

要建立这个意识:

MySQL 用户不是只有用户名,真正唯一定位一个用户的是
'user'@'host'

所以以后你做这些操作时:

  • 删除用户
  • 改密码
  • 授权
  • 回收权限
  • 查看 grants

都要带上 host。

修改用户密码

1)自己改自己的密码

sql 复制代码
set password=password('新的密码');

2)root 修改指定用户的密码

sql 复制代码
set password for '用户名'@'主机名'=password('新的密码');

示例:

set password for 'whb'@'localhost'=password('87654321');

执行后再查询 mysql.user,你会发现 authentication_string 变了。

数据库的权限

mysql 权限详解与操作速查表
类别 权限 对应系统字段 (列) 上下文/适用层级 授权语句示例 (以用户 'user'@'host' 为例) 说明与常见场景
**数据定义 (ddl)**​ create create_priv 数据库、表、索引 grant create on db_name.* to 'user'@'host'; 允许创建新数据库和表。
alter alter_priv grant alter on db_name.table_name to 'user'@'host'; 允许修改表结构(如添加/删除列)。
drop drop_priv 数据库、表 grant drop on db_name.* to 'user'@'host'; 允许删除数据库和表,慎用
index index_priv grant index on db_name.* to 'user'@'host'; 允许创建或删除索引。
create view create_view_priv 视图 grant create view on db_name.* to 'user'@'host'; 允许创建视图。通常需要相应的select权限。
show view show_view_priv 视图 grant show view on db_name.* to 'user'@'host'; 允许查看视图的定义(如show create view)。
**数据操作 (dml)**​ select select_priv grant select on db_name.* to 'user'@'host'; 最常用的权限,允许查询数据。
insert insert_priv grant insert on db_name.table_name to 'user'@'host'; 允许插入数据。
update update_priv grant update on db_name.table_name to 'user'@'host'; 允许更新现有数据。可细化到列:grant update(col1) on ...
delete delete_priv grant delete on db_name.table_name to 'user'@'host'; 允许删除数据行,慎用
存储过程/函数 create routine create_routine_priv 保存的程序 grant create routine on db_name.* to 'user'@'host'; 允许创建存储过程和函数。
alter routine alter_routine_priv 保存的程序 grant alter routine on db_name.* to 'user'@'host'; 允许修改或删除已有的存储过程和函数。
execute execute_priv 保存的程序 grant execute on procedure db_name.proc_name to 'user'@'host'; 允许执行存储过程或函数。
管理类 grant option grant_priv 数据库、表等 grant all on db_name.* to 'user'@'host' with grant option; 超级权限。允许用户将自己拥有的权限授予他人。
super super_priv 服务器管理 grant super on *.* to 'user'@'host'; 允许执行管理操作,如修改全局系统变量、kill线程、配置复制等。仅限高级dba
process process_priv 服务器管理 grant process on *.* to 'user'@'host'; 允许查看所有正在执行的线程(show processlist)。
reload reload_priv 服务器管理 grant reload on *.* to 'user'@'host'; 允许执行flush命令重新加载权限表、日志等。
shutdown shutdown_priv 服务器管理 grant shutdown on *.* to 'user'@'host'; 允许关闭mysql服务器,极高风险权限
file file_priv 文件访问 grant file on *.* to 'user'@'host'; 允许通过mysql读取/写入服务器上的文件(如load data, select ... into outfile)。存在安全风险
服务器与复制 replication client repl_client_priv 服务器管理 grant replication client on *.* to 'user'@'host'; 允许用户查询主/从服务器的状态(show master status, show slave status)。
replication slave repl_slave_priv 服务器管理 grant replication slave on *.* to 'user'@'host'; 允许从库线程从主库读取二进制日志(用于复制)。
其他 create temporary tables create_tmp_table_priv 服务器管理 grant create temporary tables on db_name.* to 'user'@'host'; 允许在操作中创建临时表。
lock tables lock_tables_priv 服务器管理 grant lock tables on db_name.* to 'user'@'host'; 允许使用lock tables语句显式锁定表。
create user create_user_priv 服务器管理 grant create user on *.* to 'user'@'host'; 允许执行create user, rename user, drop user等账户管理语句。
show databases show_db_priv 服务器管理 grant show databases on *.* to 'user'@'host'; 允许用户执行show databases查看所有数据库列表。无此权限则只看到有权限的库。
references references_priv 数据库、表 目前未使用,为未来预留。 理论上用于外键约束,但mysql中尚未实现其实际作用。
  1. 授权原则 :遵循最小权限原则,只授予完成工作所必需的最小权限。

  2. 权限层级

    • *.*:全局权限(所有数据库的所有对象)

    • database_name.*:数据库级权限(指定库的所有表)

    • database_name.table_name:表级权限(指定库的指定表)

    • database_name.routine_name:存储过程/函数权限

  3. 关键命令

    sql 复制代码
    -- 1. 授予权限
    grant 权限列表 on 层级 to '用户名'@'主机' [identified by '密码'];
    
    -- 2. 查看用户权限
    show grants for '用户名'@'主机';
    
    -- 3. 撤销权限
    revoke 权限列表 on 层级 from '用户名'@'主机';
    
    -- 4. 刷新权限(授权后执行)
    flush privileges;
  4. with grant option:授予此选项需极其谨慎,获得该权限的用户可以扩散其权限。

用户授权

注意:show grants:查看一个用户当前有哪些权限

sql 复制代码
grant 权限列表 on 库.对象名 to '用户名'@'登陆位置' [identified by '密码']

权限列表,多个权限用逗号分开

sql 复制代码
grant select on ...
 grant select, delete, create on ....
 grant all [privileges] on ... -- 表示赋予该用户在该对象上的所有权限

*.*: 代表本系统中的所有数据库的所有对象(表,视图,存储过程等)

sql 复制代码
grant all on *.* to ...

库.* : 表示某个数据库中的所有数据对象(表,视图,存储过程等)

**to '用户名'@'登录位置'**表示把权限赋给谁。再次强调:这里也必须写完整用户身份。

**identified by '密码'**如果用户存在,赋予权限的同时修改密码,如果该用户不存在,就是创建用户

回收权限
sql 复制代码
revoke 权限列表 on 库.对象名 from '用户名'@'登陆位置';

回收 whb 对 test 库的所有权限

sql 复制代码
revoke all on test.* from 'whb'@'localhost';

总结:

"从零到完整"的标准操作流程

假设你要给一个新同事开账号,最标准的步骤应该是:

sql 复制代码
-- 1. 创建用户
create user 'whb'@'localhost' identified by '12345678';

-- 2. 授权
grant select on test.* to 'whb'@'localhost';

-- 3. 查看授权结果
show grants for 'whb'@'localhost';

-- 4. 如果要改密码
set password for 'whb'@'localhost' = password('87654321');

-- 5. 如果要回收权限
revoke select on test.* from 'whb'@'localhost';

-- 6. 如果用户不用了,删除
drop user 'whb'@'localhost';

使用c语言链接

怎么用 C 语言去连接 MySQL,并执行 SQL,再把结果取回来。

也就是从"会写 SQL"进入到"在程序里操作数据库"的第一步。整章的核心主线其实非常清晰:

准备库 → 初始化连接对象 → 连接数据库 → 发送 SQL → 取结果 → 处理结果 → 关闭连接

前面你学的 MySQL,更多是在命令行里直接敲 SQL。

而这一章开始,进入的是:让 C 程序自己去连 MySQL 服务器。也就是说,以后不是你手动在 mysql 客户端里写:

sql 复制代码
select * from student;

而是你的 C 程序里写代码,让程序自动完成:

  1. 连接 MySQL
  2. 发 SQL
  3. 拿返回结果
  4. 打印/处理这些数据
  5. 要使用 C 语言连接 MySQL,需要使用官网提供的 Connector/C 库,并且要先保证 MySQL 服务是可用的,还要下载适合自己平台的连接库

Connector/C

下载下来的目录结构,大致分成两部分:

  • include:头文件,里面是各种方法声明
  • lib:库文件,里面是方法实现,已经被打包好了

你可以把它理解成:

  • include 告诉编译器:有哪些函数可以用
  • lib 真正提供这些函数的实现
sql 复制代码
#include <mysql.h>

这只是"看见声明"。但你最后编译链接时,还必须把 libmysqlclient 链接进来,否则只有声明,没有实现,程序没法真正运行。

先验证库有没有引入成功

sql 复制代码
#include <stdio.h>
#include <mysql.h>

int main()
{
    printf("mysql client Version: %s\n", mysql_get_client_info());
    return 0;
}

这个程序没有连接数据库,它只是调用:mysql_get_client_info() 来看看 MySQL 客户端库是否已经被正确引入了

它返回当前链接到的 MySQL client library 的版本字符串。比如可能输出:

mysql client Version: 6.1.6

这说明:

  • 头文件找到了
  • 库也链接上了
  • 程序能调用 MySQL C API 里的函数了

也就是说,这是一个"环境自检函数"。

编译成功了,但运行时报错:

sql 复制代码
./test: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory

错误本质是什么?

编译阶段找到了库,运行阶段没找到动态库。

你要区分两个阶段:编译/链接阶段和运行阶段。

编译/链接阶段靠的是:

复制代码
-L./lib -lmysqlclient  这让 gcc 知道去哪里找库做链接

运行阶段 。程序启动后,操作系统要去找:libmysqlclient.so.18 这个动态库文件。但系统默认搜索路径里没有你的 ./lib,所以就报错了。

解决方法

export LD_LIBRARY_PATH=./lib:告诉系统:运行程序时,也去 ./lib 目录找动态库

这样再执行:./test 就能正常输出客户端版本。

理解:

  • -L./lib:是给 编译器/链接器 看的
  • LD_LIBRARY_PATH=./lib:是给 操作系统加载器 看的

正式进入MySQL C API

第一步:mysql_init() 初始化

后面的很多函数都需要一个合法的 MYSQL*。你连"连接对象"都没有,后面当然没法连接数据库,也没法执行 SQL。

cpp 复制代码
MYSQL *mysql_init(MYSQL *mysql);

示例:

MYSQL *mfp = mysql_init(NULL);

它会创建/初始化一个 MYSQL 对象。这个 MYSQL 对象非常重要,你可以把它理解成:

"MySQL 连接句柄" / "连接控制对象"

后面所有操作几乎都围绕它展开。比如:

  • 主机地址
  • 用户名
  • 端口
  • 字符集
  • 连接状态
  • 底层方法表

这些信息都会和这个对象关联。

第二步:mysql_real_connect() 建立连接

初始化完成后,必须先连接数据库,才能做后续操作。 MySQL 网络部分基于 TCP/IP

cpp 复制代码
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 clientflag);

unix_socket 本地 socket 文件,通常填 NULL

clientflag 客户端标志位,初学一般填 0

第三步:字符集问题

如果建立连接后,英文没问题,但中文乱码,可以设置默认字符集为 utf8,因为原始默认是 latin1:

cpp 复制代码
mysql_set_character_set(myfd, "utf8");

为什么会乱码?

因为客户端和服务器之间传输字符串时,双方要对字符编码有一致认知。

如果:

  • 你的程序认为是 UTF-8
  • 连接默认按 latin1 解码

那中文肯定乱

第四步:mysql_query() 发送 SQL

把你的 SQL 字符串发给 MySQL 服务器执行。

例如:

cpp 复制代码
mysql_query(conn, "select * from student");

服务器收到后就会解析、优化、执行,然后准备结果返回给客户端。

cpp 复制代码
int mysql_query(MYSQL *mysql, const char *q);

第一个参数是连接对象,第二个参数是要执行的 SQL,比如:

select * from table

返回值怎么看?

一般:

  • 0:成功
  • 0:失败

所以通常会写:

cpp 复制代码
if (mysql_query(conn, sql) != 0) {
// 错误处理
}
第五步:mysql_store_result() 取查询结果

SQL 执行完以后,如果是查询语句,就要读取数据;如果是 updateinsert 之类,那主要看执行成功与否。

如果 mysql_query 返回成功,那么可以通过下面代码读取结果:

cpp 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);
复制代码

它会把查询结果从 MySQL 服务器读到客户端内存里,并返回一个 MYSQL_RES*。你可以把 MYSQL_RES 理解成:

"结果集对象"

它里面保存了一整张查询结果表。比如你查:

复制代码
select id, name from student;
返回三行:1 张三
2 李四
3 王五

那么这些行列数据,都会被装进 MYSQL_RES 里。

为什么不是所有 SQL 都要 mysql_store_result()

因为只有 查询类 SQL 才有结果集。比如:

复制代码
select * from student;

有很多行数据返回,所以要 mysql_store_result()

但如果你执行:

复制代码
update student set name='abc' where id=1;

这类语句没有结果表返回,你就没必要去取结果集。你只需要看 mysql_query() 是否成功即可。

所以你以后脑子里要有这个分流:

查询语句 mysql_query()mysql_store_result() → 读行读列

非查询语句 mysql_query() → 看执行结果即可

第六步:读取结果总行数 mysql_num_rows()
cpp 复制代码
my_ulonglong mysql_num_rows(MYSQL_RES *res);
第七步:读取结果列数 mysql_num_fields()
cpp 复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);
第八步:读取列名 mysql_fetch_fields()
cpp 复制代码
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

代码:

cpp 复制代码
int fields = mysql_num_fields(res);
MYSQL_FIELD *field = mysql_fetch_fields(res);
int i = 0;
for(; i < fields; i++){
    cout << field[i].name << " ";
}
cout << endl;

MYSQL_FIELD* 可以怎么理解?

它是"字段描述信息数组"。每个元素描述一列,比如:

  • 列名
  • 类型
  • 长度
  • 其他元数据
第九步:读取每一行数据 mysql_fetch_row()
cpp 复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

MYSQL_ROW 其实就是 char **,可以当二维数组来用。注意:即使数据库里是整数,取出来通常也是字符串形式来处理。

行遍历代码
cpp 复制代码
i = 0;
MYSQL_ROW line;
for(; i < nums; i++){
    line = mysql_fetch_row(res);//外层循环,从结果集里取出下一行
    int j = 0;
    for(; j < fields; j++){
        cout << line[j] << " ";//按"列"循环,把这一行的每个字段都打印出来
    }
    cout << endl;
}
第十步:关闭连接 mysql_close()

如果程序结束前不关闭,虽然操作系统最终可能帮你回收,但这是不规范的。而且在真实项目里,一个程序可能长时间运行,如果连接不及时释放,会造成:

  • 连接泄漏
  • 连接数耗尽
  • 服务端压力增大

所以规范流程一定是:用完就关。

关于事务接口(前面学的)

前面学的事务知识,不只是 mysql 命令行里能用,在 C 程序里同样能用。也就是说,程序可以自己控制:

  • 开始事务
  • 执行多条 SQL
  • 失败就回滚
  • 成功就提交

总结

这章主要讲了 3 件事。

第一件:怎么把 MySQL 的 C 库接进你的程序

  • 下载 Connector/C
  • 认识 includelib
  • gcc -I -L -l 编译链接
  • 解决动态库找不到的问题

第二件:怎么建立 MySQL 连接

  • mysql_init() 初始化连接对象
  • mysql_real_connect() 连接服务器
  • mysql_set_character_set() 处理中文乱码

第三件:怎么执行 SQL 并读取结果

  • mysql_query() 发 SQL
  • mysql_store_result() 拿结果集
  • mysql_num_rows()mysql_num_fields()mysql_fetch_fields()mysql_fetch_row() 读取表数据
  • mysql_close() 关闭连接
  • 还能用 mysql_commit()mysql_rollback() 做事务控制
相关推荐
setmoon2141 小时前
多协议网络库设计
开发语言·c++·算法
永远睡不够的入1 小时前
C++继承详解
java·c++·redis
qq_334903151 小时前
用Python实现自动化的Web测试(Selenium)
jvm·数据库·python
2501_908329851 小时前
嵌入式LinuxC++开发
开发语言·c++·算法
皮卡狮2 小时前
高阶数据结构:红黑树
c++
一只努力的微服务2 小时前
【Calcite 系列】深入理解 Calcite 的 AggregateFilterTransposeRule
大数据·数据库·calcite·优化规则
m0_518019482 小时前
使用Python操作文件和目录(os, pathlib, shutil)
jvm·数据库·python
轩情吖2 小时前
MySQL Connect
数据库·mysql·adb·select·连接·远程访问数据库
噜啦噜啦嘞好2 小时前
算法篇:二分查找
数据结构·c++·算法·leetcode