📝前言:
3w字带你入门mysql
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
一、理解数据库
1. 为什么要有数据库
普通文件存储数据:
- 安全性不高(无约束)
- 查询、管理和控制数据不方便
数据库:按照特定数据结构组织、存储和管理数据的仓库
- 数据库存储的介质:磁盘 / 内存(和文件类似,也都是用来存数据的)
数据库管理系统:用于管理数据库的软件系统(比如MySQL)
- 为程序员提供了更好管理和控制数据库数据的接口
- 并且对程序员的插入删除操作做约束,确保数据库中的数据是正确的(即:按一定规则有效组织的)
2. mysql 和 mysqld
mysql是客户端, mysqld是服务端
- mysql 连接到运行中的 mysqld 服务
- 我们写的sql语句会被发送到服务端执行
- 服务端执行完sql语句以后会向客户端返回查询结果或操作反馈
3. 服务器、库和表关系
-
所谓安装数据库服务器,只是在机器上安装了一个数据库管理系统程序,这个管理程序可以管理多个数据库
-
一般开发人员会针对每一个应用创建一个数据库。
-
为保存应用中实体的数据,一般会在数据库中创建多个表,以保存程序中实体的数据
-
我们创建的一个database在Linux下就是/var/lib/mysql中的一个目录
-
在某一个 database 里面创建的一个 table 就是在这个目录下的一个文件
-
/var/lib/是 一个存储动态数据的目录,用于存放应用程序运行时生成的持久化数据。(持久化数据就是指存在磁盘这种“非易失”的数据)
4. sql语句的分类
- DDL【data definition language】 数据定义语言,用来维护存储数据的结构 代表指令: create, drop, alter
- DML【data manipulation language】 数据操纵语言,用来对数据进行操作 代表指令: insert,delete,update
- DML中又单独分了一个DQL,数据查询语言,代表指令: select
- DCL【Data Control Language】 数据控制语言,主要负责权限管理和事务 代表指令: grant,revoke,***mit
5. 存储引擎
存储引擎是:数据库管理系统如何存储数据、如何为存储的数据建立索引和如何更新、查询数据以及并发控制等技术的实现方法。不同引擎如同不同的 “工具”,适用于不同的业务场景。
常用的:MyISAM、 InnoDB
二、mysql 服务登陆和退出操作
1. 启动mysqld
检查mysql的服务端mysqld的运行状态
sudo systemctl start mysql # 启动服务
sudo systemctl status mysql # 检查状态
2. 登陆操作
mysql -h 127.0.0.1 -P 3306 -u root -p # 这个操作登陆的是mysql的客户端,mysqld 应该早被启动好了
-
参数解读:
-h:主机IP,-P:端口号,-u:用户,-p:密码(如果后面不跟密码,则在按回车以后输入) -
如果没有写
-h默认是连接本地 -
如果没有写
-P默认是 3306 端口
3. 退出操作
Quit
三、库的操作(DDL)
1. 创建、删除和修改
- 创建:
create database [if exists] 库名 // 这些sql用大写也可以 - 删除:
drop database 库名 - 修改:
alter database 库名 修改内容
上述操作的选项不具体展开讲了
2. 字符集和校验集
- 字符集:是存入数据的格式规则
- 校验集:是我们在查询/管理数据时的比较格式规则
- 每个字符集对应多个校验集,命名格式通常为:
字符集名称_语言_比较规则
- 每个字符集对应多个校验集,命名格式通常为:
示例
如在创建database时指定字符集gbk和校验集gbk_chinese_ci(ci表示不区分大小写)
create database data1 charset=gbk collate=gbk_chinese_ci;
show databases;查看当前库:
show create database data1可以查看创建data1时的语句和配置信息(把;换成\G可以让查看的结果更好看一点)
在该库下创建的表会默认使用该库的字符集和校验集
3. 使用库
使用库
use databasename
查看当前在哪个库
select database();
4. 备份和还原
备份:其实就是把在该库中执行过的所有指令记录在了一个文件里
mysqldump -P 3306 -u root -p 密码 -B 数据库名 > 数据库备份存储的文件路径
如:将data1库备份到文件(这条语句要在mysql外,即:Linux终端执行)
mysqldump -P 3306 -u root -p -B data1 > /var/lib/mysql/mytest.sql;
还原(在mysql里面执行),当然也可以在外面还原(不过不是这个命令)
source /var/lib/mysql/mytest.sql;
注意事项
-
如果备份一个数据库时,没有带上
-B参数, 在恢复数据库时,需要先创建空数据库,然后使用数据库,再使用source来还原 -
如果备份的是数据库中的一张表
mysqldump -u root -p 数据库名 表名1 表名2 > 备份存储的文件路径 -
同时备份多个数据库
mysqldump -u root -p -B 数据库名1 数据库名2 ... > 备份数据库存放路径
5. 查看连接情况
show processlist
四、表的操作(DDL)
1. 创建表
CREATE TABLE table_name (
field1 datatype,
field2 datatype,
field3 datatype
);
- 表名区分大小写
-
field:列名(默认不区分大小写) -
datatype:数据类型 - 也可以在创建表的时候设置表的字符集和校验集,如果没有就使用库的
- 也可以设置表引擎(默认继承库的引擎),不同的存储引擎,创建表的文件不一样
create table table_name like xxx表:可以创建和xxx表一样表结构的空表
销毁表也是drop
2. 查看表结构
desc 表名
3. 修改表
也是使用alter:
- 往表里新增列
ALTER TABLE tablename ADD (column datatype [DEFAULT expr][,column
datatype]...);
- 对已有列进行修改(
column是列名)
ALTER TABLE tablename MODIfy (column datatype [DEFAULT expr][,column
datatype]...);
ADD和MODIFY后面跟的语句就跟我们创建表的时候定义列一样(语句要完整)
- 删除列
ALTER TABLE tablename DROP (column);
- 修改表名
alter table tablename rename to newtablename;
- 修改列名
alter table tablename change col_name new_col_name varchar(60); -- 新字段需要完整定义
五、数据类型
数据类型是一种约束,不符合数据类型的数据无法插入表,凡是表中的数据一定是符合约束的数据
1. 数据类型分类
2. 数值类型
-
越界插入就会报错
-
使用的时候,在类型名后加
unsigned就说明是无符号的了
3. bit类型
-
以二进制方式存储,每一位存储
0或1 -
bit[(M)]: 位字段类型。M表示bit位个数,范围从1到64。如果M被忽略,默认为1。 -
我们往
bit类型插入的数据本质上是ASCII值
示例:
4. 小数类型
float[(m, d)] [unsigned] : M指定显示长度,d指定小数位数(每个位的取值范围是0-9),占用空间4个字节
-
严格模式:要求小数位数必须为 d 位,所有位数加起来 <= m
-
非严格:小数位会四舍五入
-
unsigned类型是直接把负数范围给去掉,只保留正数(且正数不会扩展)
float会有精度丢失问题,想用高精准的可以用decimal(牺牲空间换取精确性)
decimal(m, d) [unsigned] : 定点数m指定长度,d表示小数点的位数
5. 字符串类型
char(L): 定长字符串,L是最大长度限制(单位字符)最大长度值(字符数)可以为255
varchar(L): 变长字符串,L是最大长度限制
注意
- 行长度限制:65,535 字节
- 一个中文汉字,占三个字节(和C不一样),但是只算一个字符
区别
-
char每个字符串都开固定的L个字符 -
varchar根据字符串实际占用来分配大小(但是需要额外的1-2个字节来存储该字符串长度)。
| 特性 | CHAR(L) | VARCHAR(L) |
|---|---|---|
| 长度单位 | 字符(最大 255,特殊情况扩展) | 字符(最大受行长度和字符集限制) |
| 存储方式 | 固定长度,右侧补空格 | 可变长度,需 1-2 字节存储长度 |
| 空间效率 | 适合长度固定的数据(如 UUID) | 适合长度可变的数据(如用户名) |
| 性能 | 读写更快(无需计算长度) | 节省空间,但需额外开销 |
6. 时间类型
-
date:日期'yyyy-mm-dd',占用三字节 -
datetime时间日期格式'yyyy-mm-dd HH:ii:ss',占用八字节 -
timestamp:时间戳,从1970年开始的yyyy-mm-dd HH:ii:ss格式和datetime完全一致,占用
四字节(设置自动更新:ON UPDATE CURRENT_TIMESTAMP,当我们插入或更新记录,该记录的timestamp会自动更新)
示例
mysql> create table t2(
-> d1 date,
-> d2 datetime,
-> d3 timestamp ON UPDATE CURRENT_TIMESTAMP
-> );
7. enum和set
-
enum:多选一,set:多选多(可以不选) - 选择
enum中的元素可用数组下标代替,下标从1开始 - 选择
set中的元素可用位图代替:如有五个元素:3 == 00011(相当于选前两个),5 == 00101(相当于选第一个和第三个)
find_ in_ set
我们在查询的时候where=xxx是绝对查询,如果我们想要查询在set里选了某个元素的记录可以用find_in_set
-
find_in_set(sub,str_list):如果sub在str_list中,则返回下标;如果不在,返回0; -
str_list是用逗号分隔的字符串。
如
select * from votes where find_in_set('登山', hobby);
则记录在hobby这一列有选'登山'的就会被选出来。
六、约束
创建表时,我们可以后跟约束,来对要插入的数据进行规范(约束)
1. NULL
-
NULL不等于’ '(空串),空串代表有内容,内容为空串。但是 NULL代表无内容,无意义。
- NULL 的运算结果(比如
+/-)也是NULL(无意义)
- NULL 的运算结果(比如
-
当列不指明为 not null,插入时,如果省略该列,则:默认为 NULL(因为 DEFAULT 为 NULL)
-
当列指明为 not NULL,我们就不能显式的插入 NULL。且如果插入时省略该列,必须要求设置了 DEFAULT 值,否则会报错(因为原来的DEFAULT 的 NULL 不适用了)
2. DEFAULT
缺省值,设置以后,如果插入时省略该列,就自动用DEFAULT 值。
语法:
create table t1(
id int defult 10, # 设置缺省值 10
name varchar(10) not null# 设置 not null 约束
);
3. ***ment
列描述:***ment,不是强约束,只是描述字段,会根据表创建语句保存,用来给程序员或DBA 来进行了解
使用 desc看不到,用show可以看到具体的创表语句。
4. zerofill
-
我们在创表的时候,使用 int 类型后面可以带数字,来设置显示宽度(但是不能超过取值范围)
-
MySQL 会用默认的显示宽度(一般是
INT(11))
我们在显示的时候,如果想要填满宽度,可以设置 zerofill
- 当原数据位数不够时,左边补 0,
- 如果够了 / 超了原来设置的宽度,则正常显示
示例:
# 创建
create table t5(
-> id int(5) zerofill ***ment "这是一个有zerofill的列"
-> );
# 插入
insert t5 values(10),(10000000);
查询结果:
5. 主键
-
一张表中只能有一个主键,主键可以是由单个属性构成的(即单列),也可以有多列共同构成(复合主键,此时把多列当一个整体)
-
主键用来标识记录的唯一性,任意两条记录的主键不能相同(即同一列上不能有相同的单元格,如果是单列构成的主键的话)
-
主键不能为空
设置方式:
-
在创建的时候后跟:
primary key -
当表创建好以后但是没有主键的时候,可以再次追加主键
alter table 表名 add primary key(字段列表) # 字段列表即:(id)单列 或者 (id, name)多列组合 -
删除主键
alter table 表名 drop primary key;
6. auto_increment
- 自增长:当对应的字段不给值,系统会从当前字段(列)中已经有的最大值
+1操作,得到一个新的不同的值设置在该位置 - 通常和主键搭配使用,作为逻辑主键(能够确保唯一性)
auto_increment的特点:
- 任何一个字段要做自增长,前提是本身是一个索引(key一栏有值)
- 自增长字段必须是整数
- 一张表最多只能有一个自增长
示例:
# 建表
mysql> create table t6(
-> id int primary key auto_increment, # 设置 primary key 和 auto_increment
-> name varchar(10) unique # 设置唯一键(下面有介绍)
-> );
# 插入
insert t6(name) values('Tom'),('Jim'),('Sam');
结果:
如果你企图往这两个有唯一性的列再插入相同数据:
7. 唯一键
用unique标记
-
和主键一样,该列不能出现重复数据。
-
意义:主键用来标识记录的唯一性,但是一条记录的其他单元格也可能需要确保唯一性(比如:身份证做主键,但是电话号码也要有唯一性)
-
唯一键可以为空
8. 外键
- 外键是表与表之间的一种连接机制,用于建立和强制两个表中数据的参照完整性,通过外键,可以确保一个表中的数据在另一个表中存在对应记录,从而维护数据的一致性。
- 外键约束主要定义在从表上,主表则必须是有主键约束或 unique 约束。
- 当定义外键后,要求外键列数据必须在主表的 unique 列存在 或 该外键列数据为 null。
语法:
foreign key (字段名) references 主表(列)
示例:
学生表是从表,班级表是主表。我们可以通过stu表的class_id关联到myclass中唯一的班级id,进而得到有且唯一的班级name
insert into stu values(103, '王五',30);# 创建 myclass 表(主表)
create table myclass (
-> id int primary key,
-> name varchar(30) not null ***ment'班级名'
-> );
# 创建 stu 从表
create table stu (
-> id int primary key,
-> name varchar(30) not null ***ment '学生名',
-> class_id int,
-> foreign key (class_id) references myclass(id) # 该从表的 class_id 这一列 受到 id 的约束
-> );
# 合理插入数据
insert into myclass values(10, 'C++大牛班'),(20, 'java大神班');
insert into stu values(100, '张三', 10),(101, '李四',20);
# 非法插入数据
insert into stu values(103, '王五',30); # 因为没有 id 为 30 的班级(这就是一种约束,交给 mysql 检查)
# 但是可以外键为NULL(如果外键字段允许为 NULL 的话)
insert into stu values(103, '赵六',NULL);
七,表的增删改查(DML)(重点)
CURD : Create(创建), Update(更新),Retrieve(读取),Delete(删除)
1. create
create是从语义上来的,指的是Insert操作,Insert插入数据就是 → 创建用户
1.1 Insert
有:单行全列,单行指定列,多行全列,多行指定列…
INSERT [INTO] table_name [(column [, column] ...)] # 不指定列就是全列
VALUES (value_list) [, (value_list)] ... # 多列之间用 , 分割
value_list: value, [, value] ...
示例
insert stu values(1, 'Tom', 68,75,80), (2,'Jim',80,59,96),(3,'Sam',98,95,93);
insert也可以直接插入表(查询结果后的表也行),反正合理就行。(mysql下一切皆表)
INSERT INTO table_name [(column [, column ...])] SELECT ... # 插入select的查询结果表
1.2 插入否则更新
我们的insert可能由于主键或唯一键的值已经存在而导致插入失败,这时候我们可以用 UPDATE来同步更新(如果存在的话)
INSERT ... ON DUPLICATE KEY UPDATE column = value [, column = value] ... # duplicate 单词意思:重复
- 如果这个 KEY 不存在,直接 INSERT
- 如果 KEY 存在,则执行 update(把原来数据覆盖)
示例
insert into stu values(3, 'David',88,75,23) on duplicate key update sno = 4, name = 'David', Chinesenese = 88, Math = 75, English = 23;
# 结果:Query OK, 2 rows affected (0.01 sec) : 有数据冲突(键存在),然后更新(即:1. 检测到冲突,2. 更新)
直接把原来冲突的数据记录给覆盖式更新了。
# 还可能出现:
# 1 rows affected : 没有数据冲突(主键不存在),直接 INSERT(即:1. 插入一条新数据)
# 0 rows affected : 主键存在且 update 后的内容和原来表中数据一样,mysql会认为没有冲突(即,无冲突 无插入)
1.3 replace
-- 主键 或者 唯一键 没有冲突,则直接插入;
-- 主键 或者 唯一键 如果冲突,则删除后再插入
replace into stu values(5,'Sam', 93, 92, 93);
# 结果:Query OK, 1 row affected (0.01 sec)
-- 1 row affected: 表中没有冲突数据,数据被插入
-- 2 row affected: 表中有冲突数据,删除后重新插入
2. update
对查询到的列值更新
语法
UPDATE table_name SET column = expr [, column = expr ...] # 选择指定的列更新
[WHERE ...] [ORDER BY ...] [LIMIT ...] # 后面也可以跟判断语句...(来选择具体的行)(如果不带的话,全列都会被更新,很危险)
示例(把David的English成绩改成43)
update stu set English = 43 where name = 'David';
3. retrieve(select)
- SELECT查询,除了基本的查询,还可以作为函数的触发语句,select 会执行后面的表达式
- 单独的函数不是完整的 sql 语句(需要由
select来触发)
- 单独的函数不是完整的 sql 语句(需要由
- select会将函数返回的结果转换成查询结果,然后把查询结果按照结果集(表格形式)展示。
select的结果依然是一张表,依然可以被使用。 - 查询方式:1.
*全列查询; 2. 指定列查询 -
as可以给查询后的结果取别名(不会影响原表,只是对展示结果影响) -
select后跟DISTINCT可以对查询结果去重(不会影响原表)
基本用法
select * from stu; # 全列查询
select sno, name from stu; # 指定列查询
给查询结果指定别名:
SELECT column [AS] alias_name [...] FROM table_name;
from 后面也可以重命名,并且因为 from 的语句执行在前,所以select中可以用 stu2 这个新表名
4. delete
4.1 delete
DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ...] # 用来删除记录,如果不筛选则是整表内容删除,但是表结构还在(即:表为空)
4.2 TRUNCATE
截断表
TRUNCATE [TABLE] table_name # 只能删除整表,不能用where筛选单条记录
- 比 delete 更快
- 但是,删除数据的时候,并不经过真正的事物,所以无法回滚
- 会重置 AUTO_INCREMENT 项
5. where
进行条件筛选,select可用,update也可用…,合理就行。
比较运算符
| 运算符 | 说明(MySQL 环境下) |
|---|---|
| >, >=, <, <= | 大于,大于等于,小于,小于等于 |
| = | 等于,NULL 不安全,例如 NULL = NULL 的结果是 NULL |
<=> |
等于,NULL 安全,例如 NULL <=> NULL 的结果是 TRUE (1) |
| !=, <> | 不等于 |
| BETWEEN a0 AND a1 | 范围匹配,[a0, a1],如果 a0 <= value <= a1,返回 TRUE (1) |
| IN (option, …) | 如果是 option 中的任意一个,返回 TRUE (1) |
| IS NULL | 是 NULL |
| IS NOT NULL | 不是 NULL |
| LIKE |
模糊匹配。% 表示任意多个(包括 0 个)任意字符;_ 表示任意一个字符 |
注意:
- 等于是一个
= -
mysql的字符串支持使用=直接比较 - NULL 本身代表无内容,无意义,最好用 is NULL / is not null 比较
逻辑运算符(和 C 差不多)
| AND | 多个条件必须都为 TRUE (1),结果才是 TRUE (1) |
|---|---|
| OR | 任意一个条件为 TRUE (1),结果为 TRUE (1) |
| NOT | 条件为 TRUE (1),结果为 FALSE (0) |
6. order by
order by 用于对查询后的结果排序
语法
-- ASC 为升序(从小到大)
-- DESC 为降序(从大到小)
-- 默认为 ASC
SELECT ... FROM table_name [WHERE ...]ORDER BY column [ASC|DESC], [...]; # order by 中也可以用表达式
注意:
-
没有 ORDER BY 子句的查询,返回的顺序是未定义的,永远不要依赖这个未定义的顺序
-
NULL视为比任何值都小
**LIMIT **筛选结果
语法:
LIMIT [偏移量,] 行数 # 或者:LIMIT 行数 OFFSET 偏移量;
-
LIMIT n,默认从第 0 条记录开始,筛选 n 条记录 -
LIMIT 3 OFFSET 2,从第 2 条记录开始(offset从 0 开始记录),展示 3 条记录
示例
# 错误示范
select sno, name, Chinese+Math+English total from stu where total > 240 order by total asc;
# 报错:Unknown column 'total' in 'where clause'
# 因为执行顺序为:1. from; 2. where; 3. select (这时候才设置好 total 别名); 4. order by
# 正确写法
select sno, name, Chinese+Math+English total from stu where Chinese+Math+English > 240 order by
total asc;
结果:
我们可以用降序,然后加一个limit 1筛选出第一名
7. 聚合函数
| 函数 | 说明(MySQL 聚合函数) |
|---|---|
COUNT([DISTINCT] expr) |
返回查询结果集中的记录数(处理 NULL 值) 示例:COUNT(id) 统计 id 列的非空值数量 |
SUM([DISTINCT] expr) |
计算数值列的总和(忽略 NULL 值) 示例:SUM(sales) 计算销售总额 |
AVG([DISTINCT] expr) |
计算数值列的平均值(忽略 NULL 值) 示例:AVG(price) 计算平均价格 |
MAX([DISTINCT] expr) |
返回列中的最大值(支持数值、字符串、日期类型) 示例:MAX(date) 返回最新日期 |
MIN([DISTINCT] expr) |
返回列中的最小值(支持数值、字符串、日期类型) 示例:MIN(score) 返回最低分数 |
常用于把同一组内的数据进行统计(都不区分大小写)。一般结合group by
示例:
select MIN(Math) from stu; # 把整张表当成一组来聚合
结果:
但是:
select name, MIN(Math) from stu; # 错误,name 各不相同,无法聚合成一个值
8. group by
先分组,再聚合!
示例
准备工作,创建一个雇员信息表(来自 oracle 9i 的经典测试表) ,可以让 AI 生成,省得找
- EMP员工表
- DEPT部门表
- SALGRADE工资等级表
需求1:显示每个部门的平均工资和最高工资
select DEPTNO, AVG(SAL) avg_sal, MAX(SAL) from EMP group by DEPTNO;
需求2:显示每个部门的每种岗位的平均工资和最低工资
select DEPTNO, JOB, AVG(SAL) avg_sal, MIN(SAL) min_sal from EMP group by DEPTNO, JOB order by DEPTNO, JOB;
# order by 将结果排序
需求3:显示平均工资低于 2000 的部门和它的平均工资
select DEPTNO, AVG(SAL) avg_sal from EMP group by DEPTNO having avg_sal < 2000;
# having 也是条件筛选(是分组后过滤,即:最后执行,所以才可以使用新列名 avg_sal )
# where 是分组前筛选,反正:group by 就用 having,外面用 where
9. 各个关键字执行顺序
SQL查询中各个关键字的执行先后顺序:
from > on> join > where > group by > with > having > select > distinct > order by > limit
八、内置函数
1. 时间函数
| 函数名称 | 功能 |
|---|---|
| current_date() | 返回当前日期 |
| current_time() | 返回当前时间 |
| current_timestamp() | 返回当前时间戳(日期 + 时间) |
| date(datetime) | 返回 datetime 参数的日期部分 |
| date_add(date, interval d_value_type) | 在 date 中添加日期或时间,interval 后的数值单位可以是:year、minute、second、day |
| date_sub(date, interval d_value_type) | 在 date 中减去日期或时间,interval 后的数值单位可以是:year、minute、second、day |
| datediff(date1, date2) | 两个日期的差,单位是天 |
| now() | 当前日期时间 |
-
可以理解为所有的时间函数底层用的都是同一个函数,只是输出结果不同。
-
字符串符合
YYYY-MM-DD HH:MM:SS这种标准格式,插入timestamp类型会自动发生转换,插入date类型(会截取日期部分)…也会(有点像隐式类型转换)。
2. 字符串函数
| 函数名称 | 功能 |
|---|---|
| charset(str) | 返回字符串字符集 |
| concat(string2 [, …]) | 连接字符串 |
| instr(string, substring) | 返回 substring 在 string 中出现的位置(从 1 开始),没有返回 0 |
| ucase(string2) | 转换成大写 |
| lcase(string2) | 转换成小写 |
| left(string2, length) | 从 string2 中的左边起取 length 个字符 |
| right(string2, length) | 从 string2 中的右边起取 length 个字符(还是正序的) |
| length(string) | string 的长度 |
| replace(str, search_str, replace_str) | 在 str 中用 replace_str 替换 search_str |
| strcmp(string1, string2) | 逐字符比较两字符串大小 |
| substring(str, position [, length]) | 从 str 的 postion 开始,取 length 个字符 |
| ltrim(string)、rtrim(string)、trim(string) | 去除前空格或后空格 |
3. 数学函数
| abs(number) | 绝对值函数 |
|---|---|
| bin(decimal_number) | 十进制转换二进制 |
| hex(decimalNumber) | 转换成十六进制 |
| conv(number, from_base, to_base) | 进制转换 |
| ceiling(number) | 向上去整 |
| floor(number) | 向下去整 |
| format(number, decimal_places) | 格式化,保留decimal_places位小数 |
| hex(decimalNumber) | 转换成十六进制 (注:与前面函数名重复,可能是表格录入失误 ) |
| rand() | 返回随机浮点数,范围 [0.0, 1.0) |
| mod(number, denominator) | 取模,求余 |
-
ceiling:4.1 取 5,-3.2 取 - 3 -
floor:3.8 取 3, -1.3 取 - 2 - MySQL 里
MOD(a, b)运算结果符号遵循a - (b × FLOOR(a / b))的计算逻辑 -
rand() * 100,即得到范围为[0.0, 100.0)的随机数
4. 其他函数
| 函数名称 | 功能描述 | 示例及结果展示 |
|---|---|---|
user() |
查询当前用户 |
select user(); 执行后返回当前数据库连接的用户信息,如 root@localhost(因环境而异 ) |
md5(str) |
对一个字符串进行 MD5 摘要,摘要后得到一个 32 位字符串 |
select md5('admin'); 结果如 21232f297a57a5a743894a0e4a801fc3
|
database() |
显示当前正在使用的数据库 |
select database(); 若当前使用数据库为 test_db,则返回 test_db(无当前库则返回 NULL ) |
ifnull(val1, val2) |
如果 val1 为 null,返回 val2,否则返回 val1 的值 |
select ifnull('abc', '123'); 返回 abc select ifnull(null, '123'); 返回 123
|
- 密码通常不同用明文保存,要加密,比如用
md5()转换
九、复合查询(重点)
-
子查询,即:嵌套查询。我们可以把
select语句(或其他语句)的结果也当做一张表来嵌套在其他sql语句(from、where…都可以)。 -
多表查询:当我们需要的数据不只来源于一张表时,就会用到多表查询,
-
select * from table1, table2,同时选两张表,两张表做的是笛卡尔积运算,假如table1有n个元素,table2有m个,select出的结果有n * m个元素(table1的每一行都与table2进行拼接)【但是有一说一,笛卡尔积 + where 不好用,后面会介绍内外连接更好用】
- 笛卡尔积的结果中可能存在很多无效信息,我们可以通过
where来筛选
-
-
自连接:在同一张表内嵌套查询
示例(还是继续用oracle 9i 的经典测试表)
1. 基本查询练习
-
查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的
Jselect ENAME, SAL, JOB from EMP where (SAL>500 or JOB='MANAGER') and ENAME like 'J%'; -
使用年薪进行降序排序
select ename, sal*12 + ifnull(***m,0) "年薪" from EMP order by 年薪 desc; -
显示工资最高的员工的名字和工作岗位
# 错误示范(where 子句是在分组和聚合前执行的,MAX(sal)还没有结果) select ename, job from EMP where sal=MAX(sal); # 我们可以嵌套一个子查询 select 先把结果算出来(相当于我们把整张表作为一个分组,然后用聚合函数得到一个MAX(sal)结果) select ename, job from EMP where sal=(select MAX(sal) from EMP); -
显示工资高于平均工资的员工信息
select * from EMP where sal>(select AVG(sal) from EMP); -
显示每个部门的平均工资和最高工资
select DEPTNO, avg(sal), max(sal) from EMP group by DEPTNO; -
显示平均工资低于2000的部门号和它的平均工资
select DEPTNO, avg(sal) from EMP group by DEPTNO having avg(sal) < 2000; -
显示每种岗位的雇员总数,平均工资
select job, count(*), avg(sal) from EMP group by job;
2. 多表查询练习
-
显示雇员名、雇员工资以及所在部门的名字
select ename, sal, dname from DEPT, EMP where DEPT.deptno= EMP.deptno; -
显示部门号为10的部门名,员工名和工资
select dname, ename, sal from DEPT, EMP where DEPT.deptno= EMP.deptno and EMP.deptno=10; -
显示各个员工的姓名,工资,及工资级别
# 选择了多表以后,我们就拥有了两张表的列字段,再通过 where 来删选记录 select ename, sal, grade from SALGRADE, EMP where sal < hisal and sal >= losal;
3. 自连接练习
-
显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号–empno)
-
方法1(使用子查询)
select empno, ename from EMP where empno=(select mgr from EMP where ename='FORD'); # 子查询选出领导编号,然后再在表中选出标号和领导编号相同的人的编号和姓名 -
方法2(使用多表查询,自连接)
# 自连接必须要给同一张表取别名来区分不同实体 select leader.empno, leader.ename from EMP leader, EMP worker where worker.mgr=leader.empno and worker.ename = 'FORD';
-
4. 子查询练习
4.1 单行子查询(返回一行记录的子查询)
-
显示SMITH同一部门的员工
select ename from EMP where job=(select job from EMP where ename='SMITH');
4.2 多行子查询(返回多行记录的子查询)
常用关键字:1. in:属于(也支持not in); 2. all:和全部比较,全称量词; 3. any:和任意比较,存在量词
-
查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的
select ename, job, sal, deptno from EMP where (job in (select job from EMP where deptno=10)) and deptno != 10; -
显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
select ename, sal, deptno from EMP where sal > all(select sal from EMP where deptno=30); -
显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门 的员工)
select ename, sal, deptno from EMP where sal > any(select sal from EMP where deptno=30);
4.3 多列子查询(返回多列的子查询)
- 查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
4.4 在from子句中使用子查询(把select的结果当做一张表来使用)
-
显示每个高于自己部门平均工资的员工的姓名、部门、工资,以及部门的平均工资
select ename, deptno, sal, avg_sal from EMP, (select deptno dt, AVG(sal) avg_sal from EMP groupby deptno) tmp # 临时表生成每个部门的平均工资 where EMP.sal > tmp.avg_sal and EMP.deptno=tmp.dt -
查找每个部门工资最高的人的姓名、工资、部门
select ename, sal, deptno from (select deptno dt, MAX(sal) ms from EMP group by deptno) tmp, EMP where EMP.sal=tmp.ms; # 在临时表中对列进行取别名是为了防止在外层select的时候有歧义
4.5 合并查询
合并多个select的执行结果,可以使用集合操作符 union,union all(纵向拼接)
union:用于取得两个结果集的并集,会自动去掉结果集中的重复行
-
将工资大于2500或职位是MANAGER的人找出来
select ename, sal, job from EMP where sal > 2500 union select ename, sal, job from EMP where job='MANAGE='MANAGER';
union all:取得两个结果集的并集,不会去掉结果集中的重复行
-
将工资大于2500或职位是MANAGER的人找出来
select ename, sal, job from EMP where sal > 2500 union all select ename, sal, job from EMP where job='MANAGER';
十、内外连接(重点)
内外连接就是在提前做好筛选的笛卡尔积
1. 内连接
select 字段 from 表1 inner join 表2 on 连接条件 [and 其他条件];
对 表1 和 表2 的笛卡尔积做连接条件的删选(我们上面的复合查询都可以用内连接来优化)
示例(显示SMITH的名字和部门名称):
select ename, dname from EMP join DEPT on DEPT.deptno=EMP.deptno where ename='SMITH';
# 两个表连接的时候确保是有意义的,即:仅把和自己部门号对应的部门的名字拼接到自己的记录上
2. 外连接
语法(分为左外连接和右外连接)
select 字段 from 表1 left(或者 right) join 表2 on 连接条件 [and 其他条件];
左外连接:确保左表的记录都被保留,如果和右表匹配不上的记录,字段内容可为空。(即:以左表为基础,把右表拼过来的感觉),右外连接同理。
示例(列出部门名称和这些部门的员工信息,没有员工的部门也要列出)
# 方法一(左外连接):
select dname, ename from DEPT d left join EMP e on d.deptno=e.deptno;
# 方法二(右外连接):
select ename, dname from EMP right join DEPT on DEPT.deptno=EMP.deptno;
十一、索引(重点)
1. mysql与存储
-
我们的 CURD 操作需要通过计算找到对应的插入位置(因为要用磁盘的
LAB地址),涉及计算就需要CPU,就需要把数据加载到内存里(因为冯诺依曼体系结构) -
mysql是一个软件,mysql 服务器在内存中运行的时候,会申请大内存空间 Buffer Pool 用来进行各种缓存。当数据加载到mysql,需要:磁盘 → OS → mysql 客户端(缓冲区)。
-
当我们查找一个数据的时候,要 IO。假如查找一条 IO 把数据从磁盘加载到缓冲区一次,那太慢了。为了减少 IO 的次数(IO影响效率的是IO的次数,不是IO单次的数据量),mysql中数据的存储 / 读取,也是以 页(page) 为单位的(大小 4 * 4 KB)。(InnoDB 引擎存储)
2. 理解索引
没有索引:
-
第一步:定位表的数据范围
- MySQL 通过表名找到对应的数据文件(如
.ibd),确定该表在磁盘上的物理存储范围
- MySQL 通过表名找到对应的数据文件(如
-
第二步:按顺序访问数据页
-
从第一个数据页开始,检查页是否在 Buffer Pool 中(如果不在:触发磁盘 IO 加载该页到 Buffer Pool)
-
遍历页内记录(检查/加载 一页,遍历一页)
-
-
时间复杂度: O ( n ) O(n) O(n)
有索引:索引就像书本的目录,先找索引,一次淘汰多页(利用索引的有序性)。时间复杂度: O ( l o g n ) O (log n) O(logn)(在 mysql 中通常用 B+ 树来作为索引数据结构)
-
单个page中的记录(如果有索引,记录中会存放索引)用链表组织起来,page内会存在页内目录。
-
page和page之间用链表链接起来
3. 索引原理
-
索引本质就是 mysql 的一种数据结构,mysql 中用B+ 树来存储索引。
-
索引可以提高效率,但是会降低 insert/update等操作的效率(并且会额外使用空间存储 B+ 树,空间换时间)
-
为了迅速寻找page,可以使用多级目录(目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址)
-
目录页只放各个下级page的最小键值(其实也就是指向下级page的地址)
-
查找的时候,自顶向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数
B+ vs B
B树
B+ 树(一个索引一颗 B+ 树)
- B树节点,既有数据,又有Page指针。而B+,只有叶子节点有数据,其他目录页,只有键值和Page指针
- B+叶子节点,全部相连,而B没有
选择 B+ 的优点:
- 节点不存储data,只用于导航,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。
- 叶子节点相连(且按索引值升序排序,形成有序链表,即:创建索引的时候就是自动有序的),更便于进行范围查找
- 哈希表存储不支持范围查找
聚簇索引 VS 非聚簇索引
-
InnoDB这种直接在叶子节点上存数据的,叫做聚簇索引。(MyISAM:在叶子节点处存放数据的地址,叫做非聚簇索引)
-
对于 MyISAM ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复
-
主键索引(讨论的是 InnoDB)
-
叶子节点:存储 整行数据(包括所有列),{索引值 : 数据}
-
非叶子节点:存储 {索引值:指向子节点的指针}
-
-
非主键索引
- 叶子节点:存储索引键值和对应的主键值,即:{普通索引值:主键值}(用于回表)
- 非叶子节点:存储{普通索引值:指向叶子节点的指针}
- 所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询
4. 索引操作
1. 添加主键 / 唯一键索引
方法一
create table t1(
id int primary key, # mysql 会针对主键, 自动创建主键索引
name varchar(20),
tele varchar(11) unique # mysql 针对唯一键, 自动创建唯一键索引
);
方法二
-- 在表创建最后指明主键和唯一键
create table t1(
id int,
name varchar(20),
tele varchar(11),
primary key(id), # 创建主键索引
unique(tele) # 创建唯一键索引
);
方法三
-- 建表以后添加
ALTER TABLE users ADD PRIMARY KEY (id);
ALTER TABLE users ADD unique (tele);
-- 当然也可以是复合键索引: ALTER TABLE users ADD unique(name, tele)
2. 普通索引
方法一
-- 在表创建最后指明普通索引
create table t2(
id int,
index(id)
)
方法二
-- 创建完表以后指定某列为普通索引
alter table t2 add index(id); -- 会自动生成索引名
方法三
create index idx_name on t2(id); # 注意 idx_name 和 id不同(一个是索引名,一个是字段名)
- 索引都有索引名,主键的索引名固定位
primary
3. 索引对比
| 索引类型 | 唯一性 | NULL 值 | 数量限制 | 自动创建 | 主要用途 |
|---|---|---|---|---|---|
| 主键索引 | 强制唯一 | 不允许 | 1 个 / 表 | 是 | 行标识、关联外键 |
| 唯一键索引 | 强制唯一 | 允许 1 个 | 多个 / 表 | 否 | 数据唯一性约束 |
| 普通索引 | 不强制 | 允许 | 多个 / 表 | 否 | 提高查询速度 |
注意事项
-
复合索引顺序:遵循 “最左前缀原则”,例如
INDEX (a, b, c)支持WHERE a=1、WHERE a=1 AND b=2,但不支持WHERE b=2。 -
避免冗余索引:如已有
INDEX (a, b),则无需单独创建INDEX (a)。 -
索引覆盖:指的是查询所需的数据全部来自索引本身,无需回表(访问数据表)获取数据,提高了查询效率
- 如(用复合索引实现):
-- 创建复合索引 CREATE INDEX idx_name_age_email ON users(name, age, email); -- 查询可被覆盖 SELECT name, age, email FROM users WHERE name = 'Alice';
索引创建原则
- 比较频繁作为查询条件的字段应该创建索引
- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
- 更新非常频繁的字段不适合作创建索引
- 不会出现在where子句中的字段不该创建索引
4. 查询和删除
查询索引
show index from 表名; # 或者:show keys from 表名
删除索引
drop index 索引名 on 表名 # 索引名就是show keys from 表名中的 Key_name 字段
5. 全文索引
全文索引(Full-Text Index) 是 MySQL 中专门用于快速检索文本内容的索引类型,与普通索引(如 B+ 树索引)相比,它能够高效处理模糊匹配和语义搜索,特别适合大型文本字段(如文章、评论、日志)的搜索场景。(要求表的存储引擎是MyISAM,且默认的全文索引只支持英文,如果对中文进 行全文检索,可以使用sphinx的中文版)
ps:选项比较多,在这里不细讲了
十二、事务
可以参见这篇文章:【mysql】事务
十三、视图
视图(View) 是一种虚拟表,它不存储实际数据,而是基于 SQL 查询定义的结果集。同真实的表一样(在database数据目录下是会创建对应的表文件的)
在数据访问、安全性和简化复杂查询等方面有重要作用。(比如:你不想让别人看到原表,那可以直接把查询结果视图给他;比如你有一个查询很复杂,你查询出来结果了,就可以保存到视图里,下次直接看视图)
视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表,基表的数据变化也会影响到视图。
创建视图
create view [新列名, ...] 视图名 as (select子句);
示例::
create view v1 as (select sno, name from stu where English>80) order by asc;
# 查询, 直接查视图
mysql> select * from v1;
# 结果
+-----+-------+
| sno | name |
+-----+-------+
| 5 | Sam |
| 2 | Jim |
| 3 | James |
+-----+-------+
注意事项
-
与表一样,必须唯一命名(不能出现同名视图)
-
创建视图数目无限制,但要考虑复杂查询创建为视图之后的性能影响
-
视图不能添加索引,也不能有关联的触发器或者默认值
-
视图可以提高安全性,必须具有足够的访问权限
-
order by 可以用在视图中。如果外层查询中使用了
ORDER BY,它会完全取代视图中的排序规则(就是生成视图时数据的排序规则) -
视图可以和表一起使用
十四、用户管理
1. 管理用户
用户信息
MySQL中的用户,都存储在系统数据库mysql的user表中
select host, user, authentication_string from user; # 直接查这个表就能看到
-
host:表示这个用户可以从哪个主机登陆,如果是localhost,表示只能从本机登陆- 对于mysql,不仅要确认登陆用户的密码正确,还要会限制用户登录的IP
-
user:用户名 -
authentication_string用户的密码(脱敏后的)
创建用户
create user '用户名'@'登陆主机/ip' identified by '密码';
示例
create user 'tr'@'localhost' identified by '123456';
-
这时候 mysql 库中的 user表就会多一条记录(所以创建用户本质也就是往表里面插入数据)
-
不要轻易添加一个可以从任意地方登陆的user。
删除用户
drop user '用户名'@'主机名'
示例
drop user 'tr'@'localhost';
修改用户密码
修改密码影响的只是登录mysql服务器的“凭证”
- 自己改自己密码
ALTER USER CURRENT_USER IDENTIFIED BY '密码'
- root用户修改指定用户的密码
ALTER USER '用户名'@'主机名' IDENTIFIED BY '密码';
新增用户登录的IP
GRANT ALL PRIVILEGES ON *.* TO 'tr'@'新IP' IDENTIFIED BY '密码';3
-
如果这个用户和 ip 存在,覆盖原这个IP的用户权限(如密码、权限范围)
-
如果用户不存在,则创建新的用户,并且赋予这些权限【grant的万能之处】
2. 管理用户权限
数据库提供的权限列表
查看权限
-
SHOW GRANTS; # 查看当前用户权限 -
SHOW GRANTS FOR 'app_user'@'localhost'; # root 查看指定用户权限 -
新创建的用户默认没有任何权限
授予权限
grant 权限列表 on 库.对象名 to '用户名'@'主机号';
-
权限列表
- 权限之间用
,隔开 - 也可以是:
ALL PRIVILEGES代表所有权限,不过危险
- 权限之间用
-
库.*这个*代表通配,代表所有表,如果是:*.*代表所有库和所有表 -
在主机号里,可以用
%代表通配,如127.0.0.% -
还可以列级别细粒度权限:如
grant update(id, name) ...
示例:
grant select, insert on test1.* to 'tr'@'127.0.0.1';
- 授权完就能多看到一个库了,就可以对里面的库进行符合权限的操作
权限生效
发现没有生效可以刷新:
flush privileges;
- MySQL 中,当前已建立的数据库连接(会话)会保留权限变更前的状态。即使执行了
REVOKE和FLUSH PRIVILEGES,当前登录的tr用户会话仍能继续使用旧权限,需重新登录才会加载新权限
回收权限
revoke 权限列表 on 库.对象名 from '用户名'@'主机号';
-
REVOKE语句要和GRANT语句权限层级和对象范围完全对应,否则会报错- 比如对于
grant select, insert on test1.* to 'tr'@'127.0.0.1';,如果单独revoke select on test1.stu from 'tr'@'127.0.0.1';就会报错
- 比如对于
-
MySQL 的权限分为全局 > 数据库 > 表 > 列四个层级,
- 之前的
test1.*就是数据库级权限,具体到特定的表是表层级,具体到特定的列是列层级
- 之前的
示例
revoke select on test1.* from 'tr'@'127.0.0.1';
- 重新登陆
tr后权限更新就select不了test1的表了
十五、mysql连接
mysql除了可以在命令行连接,还可以实现其他连接,便于以后更方便的使用
1. C/C++连接
1. 安装开发包
- 要使用C语言连接mysql,需要下载mysql官网提供的库Connector/C++(软件),找到符合自己系统版本的安装包,然后下载并安装到自己的操作系统上(查看系统和应用程序的配置文件可以去
/etc目录下看) - 也可以:
apt install libmysqlcppconn-dev(注意这是 C++ 的, C的是:libmysqlclient-dev)直接用apt自动从配置好的软件源中查找指定的软件包,并自动下载和安装(前提是你要知道自己要安装什么包,apt会自动安装符合系统版本的软件包,以及处理依赖关系,甚至都不用去官网下载安装包了)【强推】
如果你去了官网找包,会发现一堆包:
这些包有什么区别呢?
-
前两个是动态库:开发好的程序要在其他机器部署,就仅需这个动态库包
-
第三个
-dev结尾的是:开发库包,包含编译、开发依赖 Connector/C++ 程序所需的文件(比如头文件,以及库):开发、编译新的 C++ 连接 MySQL 的程序,必须装这个包 -
后面两个调试包:需要调试,配合调试工具的时候,需要这个包
使用:
- 当准备好库和有效的mysql服务后,我们就可以在C语言中通过包含mysql的头文件
mysql.h,来调用对应的接口访问mysql - 编译时要连接上库(C的动静态库链接)
- 我们可以在官网的:C API Basic Function Reference文档里面查看具体的接口
在语言中使用 mysql_get_client_info 函数(其实就是随便调一个函数),来验证我们的引入是否成功
#include <mysql/mysql.h>
#include <iostream>
int main()
{
printf("mysql client Version: %s\n", mysql_get_client_info());
return 0;
}
正常输出版本信息就是成功了
2. C API 介绍
我们介绍C的 API,因为封装度小,便于理解
2.1 初始化和连接
要使用库,必须先进行初始化
MYSQL *mysql_init(MYSQL *mysql);
- 作用:初始化一个用于连接 MySQL 数据库的句柄(MYSQL 结构体,类似
FILE) - 用法:传入参数
NULL,函数会自动分配一个新的MYSQL结构体并返回其指针
初始化后,连接数据库(像登陆一样)
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, // 本地 socket 文件路径,远程连接或不需要时传 NULL
unsigned long clientflag); // 连接选项标志,没特殊需求传 0
关闭连接
void mysql_close(MYSQL *sock);
2.2 中文乱码
如果出现中文是乱码,代表客户端字符集和服务端不匹配,手动设置客户端字符集:
mysql_set_character_set(myfd, "utf8");
2.3 下发命令
mysql_query用于下发sql请求
int mysql_query(MYSQL *mysql, const char *q); // query: 请求,查询
-
q为要下发的sql命令,如select * from stu -
正确执行返回
0,错误执行返回错误码 -
对应增删改操作直接执行完,对于
select查询操作,查询结果会被保存在MYSQL结构体里
2.4 获取结果属性
mysql_store_result:获取全部查询结果
MYSQL_RES *mysql_store_result(MYSQL *mysql); // store: 存储,保存
- 调用此函数可以一次性把所有查询结果从MYSQL服务器取回到本地内存(会在本地
malloc一块空间存放结果) - 返回一个结果集指针(
MYSQL_RES*),查询失败或结果为空时,返回值为NULL - 用完后要调用
mysql_free_result释放内存 - 如果结果集很大,可以用
mysql_use_result,它是边取边处理,不会一次性加载全部数据
mysql_num_rows:获取结果行数
mysql_num_fields:获取结果列数
my_ulonglong mysql_num_rows(MYSQL_RES *res); // rows: 行 columns: 列
unsigned int mysql_num_fields(MYSQL_RES *res); // field: 字段, 属性,即:列
- 返回值不一样是因为列有字节上限,但是行没有
mysql_fetch_fields:获取所有列的信息
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res); // fetch: 获取,提取
- 返回一个指向字段信息数组的指针,每个元素是一个
MYSQL_FIELD结构体,描述一列的元数据(如列名、类型、长度等) - 常用成员(假如用
fields接收)-
fields[i].name:字段名 -
fields[i].type:字段类型 -
fields[i].length:字段长度 - 其他元数据信息
-
2.5 获取结果内容
mysql_fetch_row:用于获取结果集下一行数据
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); // MYSQL_RES是mysql_store_result获取到的结果集指针
- 每调用一次,返回结果集的下一行数据,类型为
MYSQL_ROW(本质是char **,即字符串数组)。- 就像“文件指针”一样,每调用一次就读取结果集的下一行,指针自动往下移动,不能回头(除非重新查询,或者
mysql_data_seek移动指针)
- 就像“文件指针”一样,每调用一次就读取结果集的下一行,指针自动往下移动,不能回头(除非重新查询,或者
- 如果没有更多数据,返回
NULL。
结果集指针长啥样呢?结果是怎么存储的呢?如下:
2.6 示例
#include <mysql/mysql.h>
#include <iostream>
int main()
{
// printf("mysql client Version: %s\n", mysql_get_client_info());
MYSQL *my = mysql_init(nullptr);
mysql_real_connect(my, LOCAL_HOST, "tr", "123456", "test1", 3306, nullptr, 0);
mysql_set_character_set(my, "utf8");
std::cout << "---------插入数据前查询---------" << std::endl;
mysql_query(my, "select * from stu"); // 这里的sql 可以不带 ;号 (懒得做返回值判断了)
MYSQL_RES *res = mysql_store_result(my); // 获得完整的查询结果
// 获得结果的行列数信息
unsigned int r = mysql_num_rows(res);
unsigned int c = mysql_num_fields(res);
std::cout << "结果有: " << r << " 行 " << c << " 列" << std::endl;
MYSQL_FIELD *my_fd = mysql_fetch_fields(res);
// 打印表头
for (int i = 0; i < c; i++)
std::cout << my_fd[i].name << " "; // 获取每一列的列名
std::cout << std::endl;
// 读取每一行结果
MYSQL_ROW row;
while (row = mysql_fetch_row(res))
{
for (unsigned int i = 0; i < c; i++)
std::cout << (row[i] ? row[i] : "NULL") << " ";
std::cout << std::endl;
}
mysql_free_result(res);
std::cout << "------------修改数据-----------" << std::endl;
mysql_query(my, "insert into stu values(7, 'Yao', 99, 99, 99)");
mysql_query(my, "insert into stu values(8, 'Curry', 88, 88, 88)");
// mysql_query(my, "delete stu where name='Tom'"); 如果写错了, 就不会被执行
mysql_query(my, "delete from stu where name='Tom'");
std::cout << "---------插入数据后查询---------" << std::endl;
mysql_query(my, "select * from stu");
MYSQL_RES *res2 = mysql_store_result(my);
unsigned int r2 = mysql_num_rows(res2);
unsigned int c2 = mysql_num_fields(res2);
MYSQL_FIELD *my_fd2 = mysql_fetch_fields(res2);
for (int i = 0; i < c2; i++)
std::cout << my_fd2[i].name << " "; // 获取每一列的列名
std::cout << std::endl;
MYSQL_ROW row2;
while (row2 = mysql_fetch_row(res2))
{
for (int i = 0; i < c2; i++)
std::cout << (row2[i] ? row2[i] : "NULL") << " ";
std::cout << std::endl;
}
mysql_free_result(res2);
mysql_close(my);
return 0;
}
运行结果:
2. 图形化界面的mysql
以navicat为例,安装下载好以后,连接mysql
连接时要搞清楚几个概念
- 本地windows的localhost和云服务器的localhost不是一个东西
- 如果你要用localhost连接:那要确保你启动的Navicat的机器和mysql服务运行的机器是同一台。
- 如果你人在 本地电脑(想远程连云服务器的 MySQL),直接填云服务器公网 IP,会被权限拦截(因为
tr的host不允许远程 IP 连接)
解决方法:
- 把windows的IP绑定到mysql登录的用户上,比如绑定
%(不安全) - 或者绑定你自己windows电脑的内网 / 公网IP
- windows 和 云服务器在同一内网的时候绑定内网IP
- windows 和 云服务器在不同内网的时候可以绑定公网IP,查看windows公网IP
遇到其他连接失败问题可以看这篇文章
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!