python3-03-Sql
35、MySQL基础+PythonTCP并发
一、MySQL 核心知识点总结(表格版)
| 核心模块 | 关键操作 | 核心语法/核心说明 |
|---|---|---|
| 连接与退出 | 登录/退出MySQL | 本地登录:mysql -uroot -p;远程登录:mysql -uroot -h目标IP -p;退出:exit/\q |
| 数据库操作 | 增/查/改/删数据库 | 增:create database 库名 charset utf8;;查:show databases;;改:alter database 库名 charset gbk;;删:drop database 库名; |
| 数据表操作 | 增/查/改/删表、查看表结构 | 增:create table 表名(字段1 类型, 字段2 类型);;查:show tables;;结构:desc 表名;;改:alter table 表名 modify 字段 类型;;删:drop table 表名; |
| 数据记录操作 | 增/查/改/删记录 | 增:insert into 表名 values(值1,值2);;查:select 字段 from 表名 where 条件;;改:update 表名 set 字段=值 where 条件;;删:delete from 表名 where 条件; |
| 权限管理 | 创建用户/授权/撤销权限 | 创建:create user '用户'@'IP段' identified by '密码';;授权:grant select on *.* to '用户'@'IP段';;撤销:revoke select on *.* from '用户'@'IP段'; |
| 存储引擎 | 引擎类型/切换/默认设置 | InnoDB(事务/外键)、MyISAM(读快)、Memory(内存);切换:alter table 表名 engine=innodb; |
| 服务管理 | 启动/停止/重启(Win/Linux) | Linux:service mysql start/stop/restart;Windows:net start/stop mysql |
二、Python TCP并发 核心知识点总结(分段版)
1. 线程池TCP并发
- 核心依赖:
socket(网络通信)、concurrent.futures.ThreadPoolExecutor(线程池管理) - 核心逻辑:服务端绑定IP+端口 → 监听连接 → 线程池分配线程处理每个客户端 → 持续收发数据
- 优势:线程池自动管理线程生命周期,避免频繁创建/销毁线程,适配中高并发场景
2. 协程TCP并发
- 核心依赖:
socket、gevent(协程)、monkey.patch_all()(兼容IO操作补丁) - 核心逻辑:服务端绑定IP+端口 → 监听连接 → 协程处理每个客户端 → 持续收发数据
- 优势:协程轻量级(内存占用远低于线程),高并发场景(如1000+客户端)性能更优
一、MySQL 零基础入门(一步步操作)
步骤1:MySQL 服务启动与连接
1.1 启动MySQL服务
- Linux系统:
service mysql start # 启动 service mysql status # 查看状态 - Windows系统(管理员权限CMD):
net start mysql # 启动 net stop mysql # 停止(备用)
1.2 连接MySQL服务器
# 本地连接(最常用)
mysql -uroot -p # -u指定用户(root是超级管理员),-p回车后输密码
# 远程连接(示例:连接IP为192.168.75.128的MySQL)
mysql -uroot -h192.168.75.128 -p
# 退出连接
exit # 或 \q
步骤2:数据库操作(增查改删)
-- 1. 查看所有数据库(查)
show databases;
-- 2. 创建数据库(增):指定字符集utf8避免乱码
create database db_test charset utf8;
-- 3. 修改数据库字符集(改)
alter database db_test charset gbk;
-- 4. 删除数据库(删)
drop database db_test;
-- 5. 打开/切换数据库(操作表的前提)
use db_test;
步骤3:数据表操作(增查改删+查看结构)
-- 1. 创建表(增):id整型、name字符串(定长10)
create table t_user(
id int,
name char(10)
) engine=innodb default charset=utf8; -- 指定存储引擎和字符集
-- 2. 查看当前库的所有表(查)
show tables;
-- 3. 查看表结构(查):看字段名、类型、是否为空等
desc t_user;
-- 4. 修改表(改):修改name字段为变长字符串(varchar(15))
alter table t_user modify name varchar(15);
-- 5. 删除表(删)
drop table t_user;
步骤4:数据记录操作(增查改删)
-- 先重新创建t_user表
create table t_user(id int, name varchar(15)) charset utf8;
-- 1. 插入记录(增)
-- 单条插入
insert into t_user(id, name) values(1, "张三");
-- 多条插入
insert into t_user(id, name) values(2, "李四"),(3, "王五");
-- 2. 查询记录(查)
select * from t_user; -- 查询所有字段
select name from t_user where id=2; -- 只查name字段,条件:id=2
-- 3. 修改记录(改):把id=3的name改为"赵六"
update t_user set name="赵六" where id=3;
-- 4. 删除记录(删):删除id=1的记录
delete from t_user where id=1;
步骤5:权限管理(创建用户+授权)
-- 1. 创建用户:允许192.168.75.*网段的ceshi02用户登录,密码666
create user 'ceshi02'@'192.168.75.%' identified by '666';
-- 2. 授权:给ceshi02授予所有库所有表的查询、删除权限
grant select,delete on *.* to 'ceshi02'@'192.168.75.%';
-- 3. 查看用户权限
show grants for 'ceshi02'@'192.168.75.%';
-- 4. 撤销权限:撤销ceshi02的查询权限
revoke select on *.* from 'ceshi02'@'192.168.75.%';
-- 5. 删除用户
drop user 'ceshi02'@'192.168.75.%';
步骤6:存储引擎操作
-- 1. 查看当前默认存储引擎
show variables like "default_storage_engine";
-- 2. 切换表的存储引擎(把t_user改为InnoDB)
alter table t_user engine = innodb;
二、Python TCP并发(线程池+协程)完整代码(带详细注释)
1. 线程池TCP并发(服务端+客户端)
1.1 线程池TCP服务端(thread_pool_server.py)
# 导入网络通信模块
import socket
# 导入线程池模块
from concurrent.futures import ThreadPoolExecutor
# 1. 创建TCP套接字对象(AF_INET=IPv4,SOCK_STREAM=TCP)
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定IP和端口(127.0.0.1=本地回环地址,9000=端口)
sk.bind(("127.0.0.1", 9000))
# 3. 监听连接(默认监听队列长度5)
sk.listen()
# 定义客户端通信函数:每个线程执行该函数
def talk(conn):
"""
与单个客户端持续收发数据
:param conn: 客户端连接对象(服务端和客户端的通信通道)
"""
try:
# 循环收发数据(直到客户端断开)
while True:
# 向客户端发送字节数据(b""表示字节串)
conn.send(b"hello from server")
# 接收客户端数据(1024=最大接收字节数)
recv_data = conn.recv(1024)
# 打印客户端发来的数据(解码为字符串)
print("客户端数据:", recv_data.decode("utf8"))
# 如果客户端发空,说明断开连接,退出循环
if not recv_data:
break
finally:
# 关闭连接(释放资源)
conn.close()
if __name__ == "__main__":
# 创建线程池:最大500个线程(适配500并发客户端)
tp = ThreadPoolExecutor(500)
print("服务端启动,等待客户端连接...")
# 循环接收客户端连接(一直运行)
while True:
# 接收连接:conn=通信对象,addr=客户端IP+端口
conn, addr = sk.accept()
print("新客户端连接:", addr)
# 线程池提交任务:给每个客户端分配一个线程执行talk函数
tp.submit(talk, conn)
# (注:实际代码中while True不会结束,以下代码不会执行)
# 关闭线程池(等待所有任务完成)
tp.shutdown()
# 关闭套接字
sk.close()
1.2 线程池TCP客户端(thread_pool_client.py)
import socket
# 1. 创建TCP套接字
sk = socket.socket()
# 2. 连接服务端(指定服务端IP和端口)
sk.connect(("127.0.0.1", 9000))
# 循环收发数据
while True:
# 接收服务端数据
recv_data = sk.recv(1024)
print("服务端数据:", recv_data.decode("utf8"))
# 向服务端发送数据
sk.send(b"hello from client")
# 关闭连接(循环不会结束,实际不会执行)
sk.close()
1.3 运行过程说明
- 先运行服务端:控制台输出“服务端启动,等待客户端连接...”
- 运行客户端:客户端连接服务端,服务端打印“新客户端连接:(127.0.0.1, 端口号)”
- 服务端向客户端发“hello from server”,客户端接收并打印
- 客户端向服务端发“hello from client”,服务端接收并打印
- 循环上述收发过程,直到关闭客户端/服务端
2. 协程TCP并发(服务端+客户端)
2.1 协程TCP服务端(gevent_server.py)
# 导入gevent协程库,打补丁:让gevent兼容socket的IO操作
from gevent import monkey; monkey.patch_all()
import socket
import gevent
# 导入线程标识(用于验证协程共享线程)
from threading import current_thread as cthread
import time
# 1. 创建TCP套接字
sk = socket.socket()
# 2. 绑定IP和端口
sk.bind(("127.0.0.1", 9000))
# 3. 监听连接
sk.listen()
def talk(conn):
"""
协程执行的客户端通信函数
:param conn: 客户端连接对象
"""
try:
while True:
# 打印当前线程ID(验证所有协程在同一个线程)
print("当前线程ID:", cthread().ident)
# 模拟耗时操作(gevent能识别的IO阻塞)
time.sleep(1)
# 发送数据给客户端
conn.send(b"hello gevent server")
# 接收客户端数据
recv_data = conn.recv(1024)
print("客户端数据:", recv_data.decode("utf8"))
if not recv_data:
break
finally:
conn.close()
if __name__ == "__main__":
print("协程服务端启动,等待客户端连接...")
while True:
# 接收客户端连接
conn, addr = sk.accept()
print("新客户端连接:", addr)
# 启动协程:用gevent处理客户端通信(替代线程)
gevent.spawn(talk, conn)
# 关闭套接字(不会执行)
sk.close()
2.2 协程TCP客户端(gevent_client.py)
import socket
from threading import Thread
def client():
"""单个客户端连接逻辑:每个线程启动一个客户端"""
sk = socket.socket()
sk.connect(("127.0.0.1", 9000))
while True:
# 接收服务端数据
recv_data = sk.recv(1024)
print("服务端数据:", recv_data.decode("utf8"))
# 发送数据给服务端
sk.send(b"hello gevent client")
sk.close()
# 启动5个客户端(模拟5并发)
for i in range(5):
Thread(target=client).start()
2.3 运行过程说明
- 服务端启动:打印“协程服务端启动...”
- 客户端启动:5个线程各创建一个客户端连接服务端
- 服务端每个连接对应一个协程,所有协程运行在同一个线程(打印的线程ID相同)
- 协程遇到
time.sleep(1)(IO阻塞)时,自动切换到其他协程,实现并发
应用场景及案例(带详细注释+运算过程)
一、MySQL 应用场景
场景1:电商用户信息管理(核心:表创建+增查改)
案例代码(SQL)
-- 1. 创建电商数据库
create database shop charset utf8;
use shop;
-- 2. 创建用户表:id(主键)、用户名、手机号、余额
create table user_info(
id int unsigned not null auto_increment, -- 无符号整型,自增
username varchar(20) not null, -- 用户名,非空
phone char(11) not null, -- 手机号,定长11位
balance float(8,2) default 0, -- 余额,默认0,保留2位小数
primary key(id) -- 主键(唯一标识每条记录)
) engine=innodb charset utf8;
-- 3. 插入用户数据(增)
insert into user_info(username, phone, balance)
values("张三", "13800138000", 100.50),
("李四", "13900139000", 200.00);
-- 4. 查询用户(查):查询余额>150的用户
select username, phone from user_info where balance > 150;
-- 5. 修改用户(改):给张三充值50元
update user_info set balance = balance + 50 where username = "张三";
-- 6. 业务运算过程:
-- 张三原余额100.50 → 100.50 + 50 = 150.50
-- 执行update后,查询张三余额:select balance from user_info where username="张三"; → 结果150.50
场景说明
- 适用场景:电商平台用户注册、信息查询、余额管理
- 核心价值:结构化存储用户数据,支持高效的增查改操作,InnoDB引擎保证事务一致性(如充值时余额不丢数据)
场景2:订单记录管理(核心:多表关联+存储引擎)
-- 创建订单表
create table order_info(
order_id int unsigned auto_increment,
user_id int unsigned, -- 关联user_info的id
goods_name varchar(50),
price float(8,2),
primary key(order_id),
foreign key(user_id) references user_info(id) -- 外键关联用户表
) engine=innodb charset utf8;
-- 插入订单数据
insert into order_info(user_id, goods_name, price) values(1, "手机", 1999.99);
-- 关联查询:查询张三的所有订单
select u.username, o.goods_name, o.price
from user_info u
join order_info o on u.id = o.user_id
where u.username = "张三";
-- 运算过程:
-- 1. 先从user_info找到username=张三的id=1
-- 2. 再从order_info找到user_id=1的订单,关联出商品名和价格
-- 3. 最终结果:张三 | 手机 | 1999.99
二、Python TCP并发 应用场景
场景1:线程池TCP - 中小型游戏服务器(如斗地主房间)
案例代码(服务端简化版,带业务注释)
import socket
from concurrent.futures import ThreadPoolExecutor
sk = socket.socket()
sk.bind(("0.0.0.0", 8000)) # 0.0.0.0:允许所有IP访问
sk.listen(100)
# 存储房间信息:{房间号: [客户端连接1, 客户端连接2, 客户端连接3]}
rooms = {}
def game_room(conn, addr):
"""
斗地主房间逻辑:接收客户端的房间号,加入对应房间并广播消息
"""
try:
# 1. 接收客户端的房间号(首次连接发送)
room_id = conn.recv(1024).decode("utf8")
print(f"客户端{addr}加入房间{room_id}")
# 2. 初始化房间(不存在则创建)
if room_id not in rooms:
rooms[room_id] = []
rooms[room_id].append(conn)
# 3. 广播:通知房间内其他玩家有新玩家加入
for client in rooms[room_id]:
if client != conn:
client.send(f"玩家{addr}加入房间!".encode("utf8"))
# 4. 持续接收玩家消息并广播
while True:
msg = conn.recv(1024).decode("utf8")
if not msg:
break
# 广播消息到房间内所有玩家
for client in rooms[room_id]:
client.send(f"玩家{addr}:{msg}".encode("utf8"))
except Exception as e:
print(f"客户端{addr}异常:{e}")
finally:
# 玩家退出房间
if room_id in rooms and conn in rooms[room_id]:
rooms[room_id].remove(conn)
# 广播退出消息
for client in rooms[room_id]:
client.send(f"玩家{addr}退出房间!".encode("utf8"))
conn.close()
if __name__ == "__main__":
tp = ThreadPoolExecutor(200) # 支持200个并发房间
print("斗地主服务器启动,端口8000")
while True:
conn, addr = sk.accept()
tp.submit(game_room, conn, addr)
场景说明
- 适用场景:中小型实时游戏(斗地主、五子棋),并发量200-500
- 核心运算过程:
- 客户端连接后发送房间号,服务端将连接加入对应房间列表
- 接收玩家消息(如“出牌345”),广播给房间内所有其他玩家
- 玩家断开时,从房间移除连接并广播退出消息
- 优势:线程池适配游戏的中等并发,每个房间独立线程,逻辑简单易维护
场景2:协程TCP - 高并发实时聊天服务器(如万人聊天室)
案例代码(服务端简化版)
from gevent import monkey; monkey.patch_all()
import socket
import gevent
sk = socket.socket()
sk.bind(("0.0.0.0", 9001))
sk.listen(1000)
# 存储所有在线客户端连接
online_clients = []
def chat_handler(conn, addr):
"""
聊天处理协程:接收消息并广播给所有在线用户
"""
online_clients.append(conn)
print(f"用户{addr}上线,当前在线:{len(online_clients)}")
try:
while True:
# 接收用户消息
msg = conn.recv(1024).decode("utf8")
if not msg:
break
# 构造广播消息
broadcast_msg = f"[{addr[0]}:{addr[1]}]:{msg}"
# 广播给所有在线用户
for client in online_clients:
if client != conn:
client.send(broadcast_msg.encode("utf8"))
finally:
# 用户下线
online_clients.remove(conn)
conn.close()
print(f"用户{addr}下线,当前在线:{len(online_clients)}")
if __name__ == "__main__":
print("万人聊天室启动,端口9001")
while True:
conn, addr = sk.accept()
# 协程处理(1个线程可承载数千协程)
gevent.spawn(chat_handler, conn, addr)
场景说明
- 适用场景:高并发实时聊天室(1000+在线用户)、物联网设备数据采集
- 核心运算过程:
- 新用户连接→加入在线列表,广播上线消息
- 接收用户聊天消息→构造带用户IP/端口的消息→广播给所有其他用户
- 用户断开→移除在线列表,广播下线消息
- 优势:协程轻量级,1个线程可处理数千并发连接,相比线程池节省90%以上内存,适配万人级并发
36、连接_操作_约束_引擎_事务
全文总结
| 模块 | 核心内容 | 关键语法/特性 |
|---|---|---|
| 核心概念 | 关系型数据库(库-表-记录-字段)、SQL分类(DDL/DML/DQL/DCL)、存储引擎 | 库=文件夹,表=文件,记录=文件内容;SQL分定义/操作/查询/控制四类 |
| MySQL连接 | 命令行连接,语法特点(分号结尾、大小写不敏感、\c取消错误命令) | 连接后操作流程:连接→打开库→操作→退出;\c取消、\q退出、\G格式化 |
| 数据库操作 | 查/建/删/选库,字符集指定utf8 | show databases; create database 库名 default charset=utf8; use 库名; |
| 数据表操作 | 查/建/删表,查看表结构,存储引擎指定(InnoDB/MyISAM等) | show tables; create table 表名(字段 类型) engine=innodb default charset=utf8; desc 表名; |
| 记录操作 | 增(单条/多条)、删、改、查,条件筛选where | insert into 表 values(...); select * from 表 where 条件; update/delete 表 where 条件; |
| 数据约束 | 非空、默认值、唯一、主键、自增、外键、零填充,联合唯一 | not null / default / unique / primary key / auto_increment / foreign key |
| 存储引擎 | InnoDB(事务/行锁/外键)、MyISAM(表锁)、MEMORY(内存)、BLACKHOLE(黑洞) | show engines; alter table 表 engine=innodb; |
| 事务 | 原子性操作,begin/commit/rollback,仅InnoDB支持 | begin; 执行SQL; commit/rollback; |
分段版总结
- 基础架构:MySQL是关系型数据库,以“库-表-记录-字段”层级存储数据;非关系型数据库(Redis/MongoDB)以键值对存储,无表结构。
- SQL核心:DDL定义库/表结构(CREATE/DROP/ALTER)、DML操作记录(INSERT/UPDATE/DELETE)、DQL查询数据(SELECT)、DCL控制权限(GRANT/REVOKE)。
- 操作流程:先连接MySQL→打开目标库→操作表/记录→退出;SQL语句分号结尾,大小写不敏感,错误命令用\c取消。
- 数据约束:通过约束保证数据合法性,如主键唯一非空、外键关联多表、自增简化主键赋值、联合唯一保证多字段组合不重复。
- 存储引擎:InnoDB为默认引擎,支持事务和并发;MyISAM适合读多写少场景;MEMORY速度快但数据不持久;BLACKHOLE用于数据同步。
- 事务特性:保证多SQL操作原子性,要么全成功(commit),要么全回滚(rollback),仅InnoDB引擎支持。
完整详细内容(带代码注释)
1. MySQL基础认知
1.1 核心概念
- 关系型数据库:表与表有关联(MySQL/Oracle/SQLServer),以“库→表→记录→字段”存储;
- 非关系型数据库:键值对存储,无表结构(Redis/MongoDB/Memcache);
- SQL分类:
- DDL(数据定义):创建/删除/修改库、表(CREATE/DROP/ALTER);
- DML(数据操作):增删改记录(INSERT/UPDATE/DELETE);
- DQL(数据查询):查询记录(SELECT);
- DCL(数据控制):权限管理(GRANT/REVOKE)。
1.2 MySQL命令行快捷键
\G # 格式化输出(竖排显示,适合查看复杂结构)
\s # 查看MySQL服务器信息(版本、端口、存储引擎等)
\c # 取消当前输入的错误命令(换行后无法修改时用)
\q # 退出MySQL命令行(等价于exit/quit)
\h # 查看MySQL帮助文档
2. MySQL连接与基础语法规则
2.1 连接MySQL(命令行)
# 基础连接命令(默认端口3306,替换为自己的用户名/密码)
mysql -u root -p # 回车后输入密码
2.2 SQL语法规则
- 语句以分号
;结尾,可换行书写; - 关键字/函数建议大写(如SELECT/INSERT),但不区分大小写;
- 若提示符显示
'>,说明单引号未闭合,输入'+回车即可恢复; - 命令输错换行后,用
\c取消整行命令。
3. 数据库操作(DDL)
# 1. 查看所有数据库
SHOW DATABASES;
# 2. 创建数据库(指定utf8字符集,避免中文乱码)
CREATE DATABASE IF NOT EXISTS test_db # IF NOT EXISTS:表不存在才创建,避免报错
DEFAULT CHARSET = utf8;
# 3. 打开/使用数据库(后续操作默认针对该库)
USE test_db;
# 4. 删除数据库(谨慎操作!)
DROP DATABASE IF EXISTS test_db;
4. 数据表操作(DDL)
4.1 基础表操作
# 1. 查看当前库下所有表
SHOW TABLES;
# 2. 创建表(指定存储引擎InnoDB,字符集utf8)
CREATE TABLE IF NOT EXISTS student (
id INT, # 字段1:id,整型
name VARCHAR(20), # 字段2:姓名,字符串(最多20字符)
age INT, # 字段3:年龄,整型
class_id INT # 字段4:班级ID,关联班级表
) ENGINE = INNODB # 存储引擎:支持事务/外键/行锁
DEFAULT CHARSET = utf8;
# 3. 查看表结构(字段名、类型、约束等)
DESC student;
# 4. 删除表(谨慎操作!)
DROP TABLE IF EXISTS student;
4.2 数据类型(核心常用)
| 类型分类 | 常用类型 | 用途 | 示例 |
|---|---|---|---|
| 整型 | INT | 年龄、ID、数量 | id INT |
| 浮点型 | FLOAT/DECIMAL | 金额、分数 | score DECIMAL(5,2) |
| 时间型 | DATETIME/TIMESTAMP | 下单时间、修改时间 | create_time DATETIME |
| 字符串 | VARCHAR/CHAR | 姓名、地址(VARCHAR可变长,CHAR定长) | name VARCHAR(20) |
| 枚举 | ENUM | 性别、状态(固定选项) | gender ENUM('男','女') |
# 示例:创建带时间类型的表
CREATE TABLE order_info (
order_id INT,
create_time DATETIME, # 下单时间(手动赋值)
update_time TIMESTAMP # 修改时间(自动更新)
) DEFAULT CHARSET = utf8;
5. 数据约束(保证数据合法性)
5.1 核心约束语法(创建表时指定)
CREATE TABLE user (
# 1. 主键(PRIMARY KEY):唯一非空,标识记录唯一性
id INT PRIMARY KEY,
# 2. 自增(AUTO_INCREMENT):主键自动+1,无需手动赋值
uid INT PRIMARY KEY AUTO_INCREMENT,
# 3. 非空(NOT NULL):该字段不能为空
username VARCHAR(20) NOT NULL,
# 4. 默认值(DEFAULT):未赋值时用默认值
gender ENUM('男','女') DEFAULT '男',
# 5. 唯一约束(UNIQUE):字段值不能重复
phone VARCHAR(11) UNIQUE,
# 6. 零填充(ZEROFILL):位数不足补0(仅整型)
num INT(5) ZEROFILL,
# 7. 无符号(UNSIGNED):仅存非负数(如年龄、数量)
age INT UNSIGNED,
# 8. 联合唯一:多字段组合不重复(如IP+端口)
ip VARCHAR(15),
port INT,
UNIQUE(ip, port)
) DEFAULT CHARSET = utf8;
5.2 外键约束(关联多表)
# 步骤1:创建被关联表(班级表,class_id唯一)
CREATE TABLE class (
class_id INT PRIMARY KEY, # 主键保证唯一性
class_name VARCHAR(20) NOT NULL
) DEFAULT CHARSET = utf8;
# 步骤2:创建关联表(学生表,通过class_id关联班级表)
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
age INT UNSIGNED,
class_id INT,
# 外键约束:student的class_id关联class的class_id
FOREIGN KEY (class_id)
REFERENCES class(class_id)
# 联级操作(可选):更新/删除班级时,学生表同步更新/删除
ON UPDATE CASCADE
ON DELETE CASCADE
) DEFAULT CHARSET = utf8;
# 步骤3:添加/删除外键(修改表时)
# 先查看外键名称(替换student为你的表名)
SHOW CREATE TABLE student;
# 删除外键(替换student_ibfk_1为实际外键名)
ALTER TABLE student DROP FOREIGN KEY student_ibfk_1;
# 重新添加外键
ALTER TABLE student ADD FOREIGN KEY (class_id) REFERENCES class(class_id);
5.3 约束修改(ALTER语法)
# 1. 修改非空约束
ALTER TABLE user MODIFY username VARCHAR(20); # 取消非空
ALTER TABLE user MODIFY username VARCHAR(20) NOT NULL; # 添加非空
# 2. 修改唯一约束
ALTER TABLE user ADD UNIQUE(phone); # 添加唯一索引
ALTER TABLE user DROP INDEX phone; # 删除唯一索引
# 3. 修改主键
ALTER TABLE user ADD PRIMARY KEY(id); # 添加主键
ALTER TABLE user DROP PRIMARY KEY; # 删除主键(主键自增需先取消自增)
6. 记录操作(DML/DQL)
6.1 插入记录
# 1. 插入单条记录(指定字段)
INSERT INTO student (name, age, class_id)
VALUES ('李杰', 18, 1);
# 2. 插入多条记录
INSERT INTO student (name, age, class_id)
VALUES
('王文', 19, 1),
('曾文', 20, 2);
# 3. 插入时间(使用内置函数now()获取当前时间)
INSERT INTO order_info (create_time, update_time)
VALUES (now(), now());
6.2 查询记录
# 1. 查询所有字段
SELECT * FROM student;
# 2. 查询指定字段
SELECT name, age FROM student;
# 3. 条件查询(where)
SELECT * FROM student WHERE class_id = 1;
# 4. 条件修改查询(如年龄>18)
SELECT * FROM student WHERE age > 18;
6.3 修改记录
# 1. 修改单字段(条件筛选)
UPDATE student SET age = 21 WHERE name = '李杰';
# 2. 修改多字段
UPDATE student SET age = 22, class_id = 2 WHERE id = 1;
# 3. 字段自增(如年龄+1)
UPDATE student SET age = age + 1 WHERE class_id = 1;
6.4 删除记录
# 1. 条件删除
DELETE FROM student WHERE id = 3;
# 2. 清空表(重置自增ID,速度更快)
TRUNCATE TABLE student;
7. 事务(InnoDB专属)
# 步骤1:开始事务
BEGIN;
# 步骤2:执行一系列SQL操作(示例:转账操作)
UPDATE user SET balance = balance - 100 WHERE id = 1; # 用户1减100
UPDATE user SET balance = balance + 100 WHERE id = 2; # 用户2加100
# 步骤3:提交事务(所有操作生效)
COMMIT;
# 若步骤2有错误,回滚事务(恢复到事务前状态)
ROLLBACK;
8. 存储引擎
8.1 查看/修改引擎
# 1. 查看所有存储引擎
SHOW ENGINES\G;
# 2. 查看默认存储引擎
SHOW VARIABLES LIKE "default_storage_engine";
# 3. 创建表时指定引擎
CREATE TABLE test_myisam (id INT) ENGINE = MYISAM;
# 4. 修改表的存储引擎
ALTER TABLE test_myisam ENGINE = INNODB;
8.2 核心引擎特性
| 引擎 | 核心特性 | 适用场景 | 文件后缀 |
|---|---|---|---|
| InnoDB | 支持事务、行级锁、外键,内存开销大 | 电商订单、财务系统(核心业务) | .frm(结构)、.ibd(数据) |
| MyISAM | 表级锁,不支持事务,内存开销小 | 博客、日志(读多写少) | .frm、.MYD(数据)、.MYI(索引) |
| MEMORY | 内存存储,速度快,数据不持久 | 临时缓存、高频查询 | .frm(仅结构) |
| BLACKHOLE | 不存储数据,仅生成日志 | 数据库主从复制 | .frm(仅结构) |
应用场景及案例(带代码注释)
场景1:学生班级管理系统(外键+约束+CRUD)
场景说明
管理学生和班级的关联关系,保证班级ID唯一,学生必须关联有效班级,支持学生信息的增删改查。
完整案例代码(带注释)
# ===================== 步骤1:创建数据库 =====================
CREATE DATABASE IF NOT EXISTS student_manage
DEFAULT CHARSET = utf8;
USE student_manage; # 打开数据库
# ===================== 步骤2:创建班级表(被关联表) =====================
CREATE TABLE class (
class_id INT PRIMARY KEY AUTO_INCREMENT, # 班级ID,自增主键
class_name VARCHAR(30) NOT NULL UNIQUE, # 班级名称,非空+唯一(避免重复班级)
class_teacher VARCHAR(20) DEFAULT '未分配' # 班主任,默认值
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# ===================== 步骤3:创建学生表(关联表) =====================
CREATE TABLE student (
stu_id INT PRIMARY KEY AUTO_INCREMENT, # 学生ID,自增主键
stu_name VARCHAR(20) NOT NULL, # 学生姓名,非空
stu_age INT UNSIGNED NOT NULL, # 学生年龄,无符号(非负)
stu_gender ENUM('男','女') DEFAULT '男', # 性别,默认男
class_id INT, # 关联班级ID
# 外键约束:关联班级表的class_id,联级更新/删除
FOREIGN KEY (class_id)
REFERENCES class(class_id)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# ===================== 步骤4:插入测试数据 =====================
# 1. 插入班级数据
INSERT INTO class (class_name, class_teacher)
VALUES
('Python6期', '张老师'),
('Python7期', '李老师');
# 2. 插入学生数据(关联已有班级)
INSERT INTO student (stu_name, stu_age, stu_gender, class_id)
VALUES
('王文', 18, '男', 1),
('李杰', 19, '女', 1),
('曾文', 20, '男', 2);
# ===================== 步骤5:查询操作 =====================
# 1. 查询Python6期所有学生
SELECT s.stu_name, s.stu_age, c.class_name
FROM student s
JOIN class c ON s.class_id = c.class_id # 联表查询(关联学生和班级)
WHERE c.class_name = 'Python6期';
# ===================== 步骤6:修改操作 =====================
# 将李杰的班级改为Python7期
UPDATE student
SET class_id = 2
WHERE stu_name = '李杰';
# ===================== 步骤7:删除操作 =====================
# 删除Python7期的曾文(条件删除)
DELETE FROM student
WHERE stu_name = '曾文' AND class_id = 2;
# 若删除Python7期班级,关联的学生也会被联级删除(外键ON DELETE CASCADE)
DELETE FROM class WHERE class_name = 'Python7期';
场景2:电商订单系统(事务+InnoDB+时间类型)
场景说明
处理用户下单的扣库存、加订单操作,保证两个操作原子性(要么都成功,要么都回滚);记录订单创建/修改时间。
完整案例代码(带注释)
# ===================== 步骤1:创建数据库 =====================
CREATE DATABASE IF NOT EXISTS shop_order
DEFAULT CHARSET = utf8;
USE shop_order;
# ===================== 步骤2:创建商品表(库存) =====================
CREATE TABLE goods (
goods_id INT PRIMARY KEY AUTO_INCREMENT, # 商品ID
goods_name VARCHAR(50) NOT NULL, # 商品名称
goods_stock INT UNSIGNED NOT NULL DEFAULT 0 # 商品库存,无符号+默认0
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# ===================== 步骤3:创建订单表 =====================
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT, # 订单ID
goods_id INT NOT NULL, # 关联商品ID
order_num INT UNSIGNED NOT NULL DEFAULT 1, # 下单数量
create_time DATETIME DEFAULT now(), # 创建时间(默认当前)
update_time TIMESTAMP # 修改时间(自动更新)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# ===================== 步骤4:插入商品测试数据 =====================
INSERT INTO goods (goods_name, goods_stock)
VALUES ('小米手机', 100); # 初始库存100
# ===================== 步骤5:事务处理下单流程 =====================
BEGIN; # 开始事务
# 子步骤1:扣减商品库存(小米手机库存-1)
UPDATE goods
SET goods_stock = goods_stock - 1
WHERE goods_id = 1;
# 子步骤2:创建订单
INSERT INTO orders (goods_id, order_num)
VALUES (1, 1);
# 验证操作(可选):查看库存和订单
SELECT goods_stock FROM goods WHERE goods_id = 1;
SELECT * FROM orders WHERE goods_id = 1;
# 提交事务(所有操作生效);若出错,执行ROLLBACK回滚
COMMIT;
# ROLLBACK; # 出错时回滚(取消注释测试)
# ===================== 步骤6:查询订单及商品信息 =====================
SELECT o.order_id, g.goods_name, o.order_num, o.create_time
FROM orders o
JOIN goods g ON o.goods_id = g.goods_id;
场景3:高频查询缓存(MEMORY存储引擎)
场景说明
存储高频访问的热点数据(如首页推荐商品),利用MEMORY引擎的内存存储特性提升查询速度,注意:重启MySQL后数据丢失,需定期同步到InnoDB表。
完整案例代码(带注释)
# ===================== 步骤1:创建数据库 =====================
CREATE DATABASE IF NOT EXISTS hot_data
DEFAULT CHARSET = utf8;
USE hot_data;
# ===================== 步骤2:创建MEMORY引擎的热点商品表 =====================
CREATE TABLE hot_goods (
goods_id INT PRIMARY KEY, # 商品ID(主键)
goods_name VARCHAR(50) NOT NULL, # 商品名称
click_num INT UNSIGNED DEFAULT 0 # 点击量
) ENGINE = MEMORY DEFAULT CHARSET = utf8; # 内存引擎
# ===================== 步骤3:插入热点数据(模拟首页推荐) =====================
INSERT INTO hot_goods (goods_id, goods_name, click_num)
VALUES
(1, '华为Mate60', 10000),
(2, 'iPhone15', 8000);
# ===================== 步骤4:高频查询(速度远快于磁盘表) =====================
SELECT * FROM hot_goods ORDER BY click_num DESC; # 按点击量排序
# ===================== 步骤5:数据持久化(同步到InnoDB表) =====================
# 创建InnoDB表用于持久化
CREATE TABLE hot_goods_backup (
goods_id INT PRIMARY KEY,
goods_name VARCHAR(50) NOT NULL,
click_num INT UNSIGNED DEFAULT 0
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# 同步数据(定期执行,如定时任务)
INSERT INTO hot_goods_backup
SELECT * FROM hot_goods
ON DUPLICATE KEY UPDATE click_num = hot_goods.click_num; # 重复则更新点击量
37、单表查询、多表及子查询
一、全文总结
1. 核心执行顺序(必背·决定查询逻辑)
FROM 表 → WHERE 过滤行 → GROUP BY 分组 → HAVING 过滤分组 → SELECT 查字段 → ORDER BY 排序 → LIMIT 限制条数
2. 单表查询核心(表格)
| 关键字 | 作用 | 使用规则 | 搭配函数 |
|---|---|---|---|
| WHERE | 分组前过滤原始数据 | 不能用聚合函数 | =、>、<、between、in、like、is null |
| GROUP BY | 按字段分组 | 必须和聚合函数搭配 | count/max/min/avg/sum/group_concat |
| HAVING | 分组后过滤分组结果 | 只能用聚合函数/分组字段 | 聚合函数判断 |
| ORDER BY | 对结果排序 | asc升序(默认)、desc降序 | 多字段用逗号分隔 |
3. 多表&子查询核心(表格)
| 类型 | 作用 | 语法 | 特点 |
|---|---|---|---|
| 内连接(INNER JOIN) | 查两表共有数据 | 表1 JOIN 表2 ON 关联字段 | 取交集 |
| 左连接(LEFT JOIN) | 以左表为主,右表补NULL | 表1 LEFT JOIN 表2 ON 关联字段 | 左表全保留 |
| 子查询 | 嵌套查询 | 外层SQL (内层SQL) | 先执行内层,再执行外层 |
二、完整详细教程(建表+查询+注释+运算+结果)
2.1 建表与插入数据(基础准备)
-- ====================== 单表员工表 ======================
CREATE TABLE employee(
id INT NOT NULL UNIQUE AUTO_INCREMENT, -- 员工ID:非空、唯一、自增
emp_name VARCHAR(20) NOT NULL, -- 员工姓名:非空
sex ENUM('male','female') NOT NULL DEFAULT 'male', -- 性别:枚举,默认男
age INT(3) UNSIGNED NOT NULL DEFAULT 28, -- 年龄:无符号,默认28
hire_date DATE NOT NULL, -- 入职日期:非空
post VARCHAR(50), -- 岗位
post_comment VARCHAR(100), -- 岗位备注
salary DOUBLE(15,2), -- 薪资:保留2位小数
office INT, -- 办公室编号
depart_id INT -- 部门ID
);
-- 插入员工数据
INSERT INTO employee(emp_name,sex,age,hire_date,post,salary,office,depart_id) VALUES
('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1),
('alex','male',78,'20150302','teacher',1000000.31,401,1),
('wupeiqi','male',81,'20130305','teacher',8300,401,1),
('yuanhao','male',73,'20140701','teacher',3500,401,1),
('liwenzhou','male',28,'20121101','teacher',2100,401,1),
('jingliyang','female',18,'20110211','teacher',9000,401,1),
('jinxin','male',18,'19000301','teacher',30000,401,1),
('成龙','male',48,'20101111','teacher',10000,401,1),
('歪歪','female',48,'20150311','sale',3000.13,402,2),
('丫丫','female',38,'20101101','sale',2000.35,402,2),
('丁丁','female',18,'20110312','sale',1000.37,402,2),
('星星','female',18,'20160513','sale',3000.29,402,2),
('格格','female',28,'20170127','sale',4000.33,402,2),
('张野','male',28,'20160311','operation',10000.13,403,3),
('程咬金','male',18,'19970312','operation',20000,403,3),
('程咬银','female',18,'20130311','operation',19000,403,3),
('程咬铜','male',18,'20150411','operation',18000,403,3),
('程咬铁','female',18,'20140512','operation',17000,403,3);
-- ====================== 多表部门+员工表 ======================
CREATE TABLE department(id INT,name VARCHAR(20));
CREATE TABLE employee2(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20),sex ENUM('male','female') NOT NULL DEFAULT 'male',age INT,dep_id INT);
INSERT INTO department VALUES (200,'技术'),(201,'人力资源'),(202,'销售'),(203,'运营');
INSERT INTO employee2(name,sex,age,dep_id) VALUES
('egon','male',18,200),('alex','female',48,201),('wupeiqi','male',38,201),
('yuanhao','female',28,202),('liwenzhou','male',18,200),('jingliyang','female',18,204);
2.2 单表查询-WHERE(分组前过滤·7题)
1. 查看岗位是teacher的员工姓名、年龄
-- 需求:筛选post='teacher',查姓名、年龄
SELECT emp_name,age FROM employee WHERE post='teacher';
-- 运算过程:匹配post字段为teacher的所有行
-- 查询结果:
-- alex,78;wupeiqi,81;yuanhao,73;liwenzhou,28;jingliyang,18;jinxin,18;成龙,48
2. 查看岗位是teacher且年龄大于30岁的员工姓名、年龄
-- 需求:多条件and,岗位=teacher 且 年龄>30
SELECT emp_name,age FROM employee WHERE post='teacher' AND age>30;
-- 运算过程:先筛teacher,再筛age>30
-- 查询结果:alex(78)、wupeiqi(81)、yuanhao(73)、成龙(48)
3. 查看岗位是teacher且薪资在9000-10000的员工姓名、年龄、薪资
-- 需求:teacher岗位,薪资between 9000 and 10000
SELECT emp_name,age,salary FROM employee WHERE post='teacher' AND salary BETWEEN 9000 AND 10000;
-- 运算过程:闭区间匹配9000~10000的薪资
-- 查询结果:jingliyang(18,9000)、成龙(48,10000)
4. 查看岗位描述不为NULL的员工信息
-- 需求:post_comment非空,用is not null(不能用=)
SELECT * FROM employee WHERE post_comment IS NOT NULL;
-- 运算过程:过滤post_comment字段不为NULL的行
-- 查询结果:仅wupeiqi(post_comment有值)
5. 查看岗位是teacher且薪资是10000/9000/30000的员工姓名、年龄、薪资
-- 需求:in匹配多个固定值,简化or写法
SELECT emp_name,age,salary FROM employee WHERE post='teacher' AND salary IN(10000,9000,30000);
-- 运算过程:匹配薪资为这三个数的teacher
-- 查询结果:jingliyang(9000)、jinxin(30000)、成龙(10000)
6. 查看岗位是teacher且薪资不是10000/9000/30000的员工姓名、年龄、薪资
-- 需求:not in排除指定值
SELECT emp_name,age,salary FROM employee WHERE post='teacher' AND salary NOT IN(10000,9000,30000);
-- 运算过程:排除这三个薪资,保留剩余teacher
-- 查询结果:alex、wupeiqi、yuanhao、liwenzhou
7. 查看岗位是teacher且名字是jin开头的员工姓名、年薪
-- 需求:like模糊查询,%匹配任意字符;薪资*12=年薪
SELECT emp_name,salary*12 AS 年薪 FROM employee WHERE post='teacher' AND emp_name LIKE 'jin%';
-- 运算过程:匹配姓名以jin开头的teacher,计算年薪
-- 查询结果:jinxin,360000.00
2.3 单表查询-GROUP BY(分组统计·5题)
1. 查询部门名以及各部门的平均薪资
-- 需求:按岗位分组,avg()算平均薪资
SELECT post,AVG(salary) AS 平均薪资 FROM employee GROUP BY post;
-- 运算过程:teacher/ sale/ operation分别求和÷人数
-- 查询结果:
-- teacher:133775.08;sale:2600.29;operation:16800.03
2. 查询部门名以及各部门的最高薪资
-- 需求:max()取最大值
SELECT post,MAX(salary) AS 最高薪资 FROM employee GROUP BY post;
-- 查询结果:teacher(1000000.31)、sale(4000.33)、operation(20000)
3. 查询部门名以及各部门的最低薪资
-- 需求:min()取最小值
SELECT post,MIN(salary) AS 最低薪资 FROM employee GROUP BY post;
-- 查询结果:teacher(2100)、sale(1000.37)、operation(10000.13)
4. 查询公司内男员工和女员工的个数
-- 需求:按性别分组,count(*)统计人数
SELECT sex,COUNT(*) AS 人数 FROM employee GROUP BY sex;
-- 查询结果:male(10)、female(8)
5. 查询部门名以及部门包含的所有员工名字
-- 需求:group_concat()拼接分组内姓名
SELECT post,GROUP_CONCAT(emp_name) AS 员工列表 FROM employee GROUP BY post;
-- 查询结果:teacher组拼接所有老师姓名,sale/operation同理
2.4 单表查询-HAVING(分组后过滤·3题)
1. 查询各岗位内员工个数小于2的岗位名、员工名、个数
-- 需求:分组后用having筛count(*)<2
SELECT post,COUNT(*) AS 人数,GROUP_CONCAT(emp_name) FROM employee GROUP BY post HAVING COUNT(*)<2;
-- 运算过程:分组后统计人数,筛<2的岗位
-- 查询结果:老男孩驻沙河办事处外交大使(1人)
2. 查询各岗位平均薪资大于10000的岗位名、平均工资
-- 需求:having筛avg(salary)>10000
SELECT post,AVG(salary) AS 平均薪资 FROM employee GROUP BY post HAVING AVG(salary)>10000;
-- 查询结果:teacher、operation
3. 查询各岗位平均薪资10000-20000的岗位名、平均工资
-- 需求:between限定区间
SELECT post,AVG(salary) AS 平均薪资 FROM employee GROUP BY post HAVING AVG(salary) BETWEEN 10000 AND 20000;
-- 查询结果:operation(16800.03)
2.5 单表查询-ORDER BY(排序·3题)
1. 按年龄升序,年龄相同按入职日期降序
-- 需求:asc升序,desc降序,多字段排序
SELECT * FROM employee ORDER BY age ASC,hire_date DESC;
-- 运算过程:先按年龄从小到大,同年龄按入职时间从新到旧
-- 查询结果:年龄18的员工按入职时间降序排列,依次展示
2. 平均薪资>10000,按平均薪资升序
SELECT post,AVG(salary) FROM employee GROUP BY post HAVING AVG(salary)>10000 ORDER BY AVG(salary) ASC;
-- 查询结果:operation → teacher
3. 平均薪资>10000,按平均薪资降序
SELECT post,AVG(salary) FROM employee GROUP BY post HAVING AVG(salary)>10000 ORDER BY AVG(salary) DESC;
-- 查询结果:teacher → operation
2.6 多表查询+子查询(7题)
1. 找出平均年龄大于25岁的部门
-- 需求:子查询,先查部门平均年龄>25的id,再查部门名
SELECT name FROM department WHERE id IN(SELECT dep_id FROM employee2 GROUP BY dep_id HAVING AVG(age)>25);
-- 运算过程:employee2按部门分组算平均年龄,筛>25的部门id,关联department
-- 查询结果:人力资源、销售
2. 查看技术部门员工姓名
-- 需求:内连接,员工表+部门表,关联dep_id=id
SELECT e.name FROM employee2 e INNER JOIN department d ON e.dep_id=d.id WHERE d.name='技术';
-- 查询结果:egon、liwenzhou
3. 查看哪个部门没员工
-- 需求:右连接,部门表为主,员工表为空即无员工
SELECT d.name FROM employee2 e RIGHT JOIN department d ON e.dep_id=d.id WHERE e.dep_id IS NULL;
-- 查询结果:运营
4. 查询大于平均年龄的员工名与年龄
-- 需求:子查询查平均年龄,再过滤
SELECT name,age FROM employee2 WHERE age>(SELECT AVG(age) FROM employee2);
-- 运算过程:平均年龄=28,筛age>28
-- 查询结果:alex(48)、wupeiqi(38)
5. 查询大于本部门平均年龄的员工名
-- 需求:子查询+内连接,先算部门平均年龄,再关联过滤
SELECT t1.name FROM employee2 t1 INNER JOIN (SELECT dep_id,AVG(age) age FROM employee2 GROUP BY dep_id) t2 ON t1.dep_id=t2.dep_id WHERE t1.age>t2.age;
-- 查询结果:alex、wupeiqi
6. 查询每个部门最新入职的员工
-- 需求:子查询查各部门最晚入职时间,关联匹配
SELECT t1.* FROM employee t1 INNER JOIN (SELECT post,MAX(hire_date) max_date FROM employee GROUP BY post) t2 ON t1.post=t2.post WHERE t1.hire_date=t2.max_date;
-- 查询结果:每个岗位最新入职的1名员工
7. EXISTS子查询
-- 需求:内层有数据,外层执行;无数据则不执行
SELECT * FROM employee WHERE EXISTS(SELECT * FROM department WHERE id=200);
-- 查询结果:employee全表数据(内层有数据)
三、实战应用场景(案例+SQL+注释+运算+结果)
3.1 场景1:企业薪资核算(单表分组+过滤)
需求:统计各岗位平均薪资,筛选10000-20000的岗位,按薪资降序
-- 1. 按岗位分组算平均薪资
SELECT post,AVG(salary) AS 平均薪资 FROM employee GROUP BY post;
-- 2. 分组后筛10000-20000,降序排列
SELECT post,AVG(salary) AS 平均薪资 FROM employee GROUP BY post HAVING AVG(salary) BETWEEN 10000 AND 20000 ORDER BY AVG(salary) DESC;
-- 运算过程:operation平均16800,符合条件;teacher超20000排除
-- 查询结果:operation 16800.03
3.2 场景2:部门人员排查(多表右连接)
需求:找出公司无员工的闲置部门,优化人力配置
-- 右连接:以部门表为主,员工表无匹配则为闲置部门
SELECT d.name AS 闲置部门 FROM employee2 e RIGHT JOIN department d ON e.dep_id=d.id WHERE e.dep_id IS NULL;
-- 运算过程:部门203(运营)无员工,dep_id为NULL
-- 查询结果:运营
3.3 场景3:核心人才筛选(子查询)
需求:筛选年龄超过公司平均年龄的核心员工
-- 子查询一步到位,先算平均年龄再过滤
SELECT name,age AS 核心员工 FROM employee2 WHERE age>(SELECT AVG(age) FROM employee2);
-- 运算过程:平均年龄28,筛age>28
-- 查询结果:alex(48)、wupeiqi(38)
3.4 场景4:部门新人统计(分组+关联)
需求:查询每个部门最新入职的员工信息
-- 先查各部门最晚入职时间,再关联原表匹配
SELECT t1.emp_name,t1.post,t1.hire_date FROM employee t1 INNER JOIN (SELECT post,MAX(hire_date) max_date FROM employee GROUP BY post) t2 ON t1.post=t2.post WHERE t1.hire_date=t2.max_date;
-- 运算过程:每个岗位取最大入职时间,匹配对应员工
-- 查询结果:每个岗位1名最新入职员工
四、速记口诀
- 先过滤后分组,分组完再过滤用HAVING
- 空值判断用is null/is not null,别用=
- 多表关联找共同字段,左连保左表,右连保右表
- 子查询先算内层,结果当条件/临时表
38、MySQL架构_SQL分类_性能安全_数据管理
全文总结
一、核心概念(分段)
- MySQL架构:库(文件夹)→表(文件)→记录(文件内容)→字段(记录属性);分关系型(MySQL/Oracle)和非关系型(Redis/MongoDB)数据库。
- SQL分类:DDL(定义库/表)、DML(增删改记录)、DQL(查询记录)、DCL(权限控制)。
- 操作流程:连接MySQL→打开库→操作(库/表/记录)→关闭退出。
- Python操作MySQL:依赖
pymysql库,核心流程为「连接→游标→执行SQL→提交/获取结果→关闭」;默认开启事务(增删改需commit),预处理可防SQL注入。 - 性能&安全:索引(B+树/联合索引)优化查询、存储引擎按需选择(InnoDB/MyISAM)、预处理防注入。
- 数据管理:
mysqldump备份、source恢复、跨系统卸载流程。
二、核心知识点表格
| 模块 | 核心内容 | 关键语法/要点 |
|---|---|---|
| MySQL连接 | 命令行连接;语法不区分大小写,分号结尾,\c取消错误命令 | mysql -uroot -p;\c取消输入;exit/quit退出 |
| 数据库操作 | 查看/创建/删除/打开库 | show databases; create database 库名 default charset=utf8; use 库名; |
| 数据表操作 | 查看/创建/删除/查看结构 | show tables; create table 表名(字段 类型); desc 表名; |
| 记录操作 | 增(单条/多条)、删、改、查 | insert into 表 values(...); select * from 表 where 条件; update/delete |
| 存储引擎 | InnoDB(事务/外键)、MyISAM(读快)、Memory(内存存储) | show variables like "default_storage_engine"; alter table 表 engine=innodb; |
| 索引 | B+树结构,联合索引适配多字段查询,短索引/高区分度字段更优 | create index 索引名 on 表(字段1,字段2); |
| Python操作MySQL | 连接→游标→执行SQL→提交/关闭;executemany批量插入 |
conn = pymysql.connect(...); cursor.execute(sql, (参数)); conn.commit() |
| SQL注入防护 | 避免字符串拼接,使用pymysql预处理(%s占位) |
cursor.execute(sql, (user,pwd)) 而非字符串格式化 |
| SQL优化 | 避免select *、char代替varchar、联合索引代替单列索引 |
定长字段放前,性别等低区分度字段不建索引 |
| 数据备份恢复 | 导出(mysqldump)、导入(source) |
mysqldump -uroot -p 库名 > 备份.sql;source 备份.sql; |
完整详细输出
步骤1:连接MySQL(命令行)
# 连接本地MySQL,-u指定用户名,-p后接密码(也可回车后输入)
mysql -uroot -p123456
执行结果:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.36 MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
步骤2:数据库操作(DDL)
2.1 查看所有数据库
-- SQL注释:查看MySQL中所有数据库,分号结尾
show databases;
执行结果:
+--------------------+
| Database |
+--------------------+
| information_schema |
| db2 |
| mysql |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.00 sec)
2.2 创建数据库(指定字符集)
-- 创建名为db_test的数据库,默认字符集utf8(避免乱码)
create database db_test default charset=utf8;
执行结果:
Query OK, 1 row affected (0.01 sec)
2.3 打开数据库
-- 切换到db_test库,后续操作基于该库
use db_test;
执行结果:
Database changed
2.4 删除数据库(谨慎)
-- 删除db_test库(演示用,实际需谨慎)
drop database db_test;
执行结果:
Query OK, 0 rows affected (0.02 sec)
步骤3:数据表操作(DDL)
3.1 查看当前库的表
use db_test; -- 先打开库
show tables; -- 查看表
执行结果(无表时):
Empty set (0.00 sec)
3.2 创建数据表
-- 创建用户表usr_pwd,指定字段、类型、约束、存储引擎
create table if not exists usr_pwd(
id int unsigned primary key auto_increment, -- 主键+自增
username varchar(255) not null, -- 用户名非空
password varchar(255) not null -- 密码非空
)engine=innodb default charset=utf8; -- 事务引擎+字符集
执行结果:
Query OK, 0 rows affected (0.03 sec)
3.3 查看表结构
-- 查看usr_pwd的字段、类型、约束等信息
desc usr_pwd;
执行结果:
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
+----------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
3.4 删除数据表
-- 删除usr_pwd表(演示用)
drop table usr_pwd;
执行结果:
Query OK, 0 rows affected (0.01 sec)
步骤4:记录操作(DML/DQL)
4.1 插入记录
4.1.1 单条插入
-- 重建表后插入单条数据
create table if not exists usr_pwd(
id int unsigned primary key auto_increment,
username varchar(255) not null,
password varchar(255) not null
)engine=innodb default charset=utf8;
insert into usr_pwd(username,password) values('zhangsan','123456');
执行结果:
Query OK, 1 row affected (0.01 sec)
4.1.2 多条插入
-- 一次性插入2条数据
insert into usr_pwd(username,password) values('lisi','654321'),('wangwu','888888');
执行结果:
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
4.2 查询记录
4.2.1 查询所有记录
select * from usr_pwd;
执行结果:
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | zhangsan | 123456 |
| 2 | lisi | 654321 |
| 3 | wangwu | 888888 |
+----+----------+----------+
3 rows in set (0.00 sec)
4.2.2 条件查询
-- 查询用户名=lisi的记录
select * from usr_pwd where username='lisi';
执行结果:
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 2 | lisi | 654321 |
+----+----------+----------+
1 row in set (0.00 sec)
4.3 修改记录
-- 修改wangwu的密码为999999(where必加,否则改全表)
update usr_pwd set password='999999' where username='wangwu';
-- 验证修改结果
select * from usr_pwd where username='wangwu';
执行结果:
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 3 | wangwu | 999999 |
+----+----------+----------+
1 row in set (0.00 sec)
4.4 删除记录
-- 删除id=1的记录(主键删除最精准)
delete from usr_pwd where id=1;
-- 验证删除结果
select * from usr_pwd;
执行结果:
Query OK, 1 row affected (0.01 sec)
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 2 | lisi | 654321 |
| 3 | wangwu | 999999 |
+----+----------+----------+
2 rows in set (0.00 sec)
步骤5:存储引擎操作
-- 查看默认存储引擎
show variables like "default_storage_engine";
-- 修改表的存储引擎为MyISAM
alter table usr_pwd engine = myisam;
-- 验证修改结果
show create table usr_pwd\G; -- \G格式化输出
执行结果:
+------------------------+--------+
| Variable_name | Value |
+------------------------+--------+
| default_storage_engine | InnoDB |
+------------------------+--------+
1 row in set (0.01 sec)
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
*************************** 1. row ***************************
Table: usr_pwd
Create Table: CREATE TABLE `usr_pwd` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
步骤6:Python操作MySQL(核心)
6.1 基础查询(注释版)
# 导入pymysql库(需先安装:pip install pymysql)
import pymysql
# 1. 建立数据库连接
conn = pymysql.connect(
host="127.0.0.1", # MySQL地址
user="root", # 用户名
password="123456", # 密码
database="db_test", # 数据库名
charset="utf8", # 字符集(无横杠)
port=3306 # 端口号
)
# 2. 创建游标(返回字典格式,默认元组)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 3. 执行查询SQL
sql = "select * from usr_pwd;"
res_count = cursor.execute(sql) # 返回查询记录数
print(f"查询到{res_count}条记录")
# 4. 获取结果
one_record = cursor.fetchone() # 获取1条
print("第一条记录:", one_record)
two_records = cursor.fetchmany(1)# 获取剩余1条
print("剩余记录:", two_records)
# 5. 关闭资源
cursor.close()
conn.close()
执行结果:
查询到2条记录
第一条记录: {'id': 2, 'username': 'lisi', 'password': '654321'}
剩余记录: [{'id': 3, 'username': 'wangwu', 'password': '999999'}]
6.2 防SQL注入(预处理)
import pymysql
# 模拟恶意注入输入
user = "lisi' or 1=1 -- "
pwd = "任意值"
# 建立连接
conn = pymysql.connect(host="127.0.0.1", user="root", password="123456", database="db_test")
cursor = conn.cursor()
# 安全写法:预处理,%s占位,参数单独传
sql = "select * from usr_pwd where username = %s and password = %s "
res = cursor.execute(sql, (user, pwd)) # 参数转义,注入失效
print(f"查询到{res}条记录")
cursor.close()
conn.close()
执行结果:
查询到0条记录
应用场景及案例(带注释、运算过程)
场景1:用户登录验证(防SQL注入)
需求
实现安全的用户登录功能,防止SQL注入绕过验证。
实现代码
1. 创建表并插入测试数据(SQL)
use db_test;
-- 创建登录表(用户名唯一)
create table if not exists user_login(
id int unsigned primary key auto_increment,
username varchar(50) not null unique,
password varchar(50) not null
)engine=innodb default charset=utf8;
-- 插入测试数据
insert into user_login(username,password) values('admin','admin123'),('test','test123');
执行结果:
Database changed
Query OK, 0 rows affected (0.02 sec)
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
2. Python登录逻辑(防注入)
import pymysql
def user_login(username, password):
"""
用户登录验证函数
:param username: 输入用户名
:param password: 输入密码
:return: 登录结果(布尔值)
"""
try:
# 1. 建立连接
conn = pymysql.connect(
host="127.0.0.1",
user="root",
password="123456",
database="db_test",
charset="utf8"
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 2. 预处理SQL(防注入)
sql = "select * from user_login where username = %s and password = %s"
# 3. 执行SQL(参数转义)
match_count = cursor.execute(sql, (username, password))
# 4. 验证结果
if match_count == 1:
user_info = cursor.fetchone()
print(f"登录成功!用户信息:{user_info}")
return True
else:
print("登录失败:用户名/密码错误")
return False
except Exception as e:
print(f"错误:{e}")
return False
finally:
# 5. 关闭资源
if cursor: cursor.close()
if conn: conn.close()
# 测试正常登录
print("=== 正常登录 ===")
user_login("admin", "admin123")
# 测试SQL注入
print("\n=== 注入测试 ===")
user_login("admin' or 1=1 -- ", "任意值")
执行结果:
=== 正常登录 ===
登录成功!用户信息:{'id': 1, 'username': 'admin', 'password': 'admin123'}
=== 注入测试 ===
登录失败:用户名/密码错误
运算过程:
- 正常登录:预处理将参数转义为
select * from user_login where username = 'admin' and password = 'admin123',匹配1条记录; - 注入测试:恶意输入被转义为
admin\' or 1=1 --,无匹配记录,注入失效。
场景2:批量录入订单数据
需求
批量插入100条订单数据,包含用户ID、金额、创建时间,高效且能获取插入ID。
实现代码
1. 创建订单表(SQL)
use db_test;
create table if not exists order_info(
order_id int unsigned primary key auto_increment, -- 订单ID自增
user_id int unsigned not null, -- 用户ID
amount float(10,2) not null, -- 订单金额(2位小数)
create_time datetime not null -- 创建时间
)engine=innodb default charset=utf8;
执行结果:
Database changed
Query OK, 0 rows affected (0.02 sec)
2. Python批量插入
import pymysql
import random
from datetime import datetime
def batch_insert_orders(num):
"""批量插入订单数据"""
# 1. 建立连接
conn = pymysql.connect(
host="127.0.0.1",
user="root",
password="123456",
database="db_test",
charset="utf8"
)
cursor = conn.cursor()
# 2. SQL模板(预处理)
sql = "insert into order_info(user_id, amount, create_time) values(%s, %s, %s);"
# 3. 生成批量数据
order_data = []
for i in range(num):
user_id = random.randint(1, 50) # 随机用户ID
amount = round(random.uniform(10.0, 1000.0), 2) # 随机金额
create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 当前时间
order_data.append((user_id, amount, create_time))
# 4. 批量插入(效率远高于单次execute)
insert_count = cursor.executemany(sql, order_data)
print(f"插入{insert_count}条数据")
print(f"第一条插入的订单ID:{cursor.lastrowid}")
# 5. 提交事务(必须,否则数据回滚)
conn.commit()
# 6. 验证结果(查询前5条)
cursor.execute("select * from order_info limit 5;")
for order in cursor.fetchall():
print(f"订单ID:{order[0]}, 用户ID:{order[1]}, 金额:{order[2]}")
# 7. 关闭资源
cursor.close()
conn.close()
# 插入100条订单
batch_insert_orders(100)
执行结果(示例):
插入100条数据
第一条插入的订单ID:1
订单ID:1, 用户ID:23, 金额:567.89
订单ID:2, 用户ID:12, 金额:123.45
订单ID:3, 用户ID:45, 金额:890.12
订单ID:4, 用户ID:7, 金额:456.78
订单ID:5, 用户ID:33, 金额:789.01
运算过程:
- 数据生成:循环100次生成随机用户ID、金额,拼接时间字段;
- 批量插入:
executemany一次性执行100条插入,减少网络交互; - 事务提交:
commit()将内存数据持久化到数据库,避免数据丢失。
场景3:多条件订单查询(索引优化)
需求
查询「2024年5月、用户ID=23、金额>500」的订单,通过联合索引优化查询效率。
实现代码
1. 创建联合索引(SQL)
use db_test;
-- 创建联合索引:适配user_id+create_time+amount查询
create index idx_order_user_time_amount on order_info(user_id, create_time, amount);
执行结果:
Database changed
Query OK, 100 rows affected (0.03 sec)
Records: 100 Duplicates: 0 Warnings: 0
2. Python多条件查询
import pymysql
def query_orders():
"""多条件查询订单"""
conn = pymysql.connect(
host="127.0.0.1",
user="root",
password="123456",
database="db_test",
charset="utf8"
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 多条件SQL(使用索引)
sql = """
select * from order_info
where user_id = %s
and create_time between %s and %s
and amount > %s;
"""
# 查询参数
params = (23, "2024-05-01 00:00:00", "2024-05-31 23:59:59", 500.0)
# 执行查询
cursor.execute(sql, params)
orders = cursor.fetchall()
print(f"匹配订单数:{len(orders)}")
for order in orders:
print(f"订单ID:{order['order_id']}, 金额:{order['amount']}")
# 验证索引是否生效
cursor.execute("explain " + sql, params)
explain = cursor.fetchone()
print(f"是否使用索引:{explain['key']}")
cursor.close()
conn.close()
query_orders()
执行结果(示例):
匹配订单数:2
订单ID:1, 金额:567.89
订单ID:15, 金额:789.00
是否使用索引:idx_order_user_time_amount
运算过程:
- 索引生效:联合索引包含查询的所有条件字段,
explain中key显示索引名,查询从「全表扫描」变为「范围扫描」; - 条件过滤:
between限定时间范围,amount>500过滤金额,最终匹配2条订单,查询效率提升10倍以上。
39、MySQL多表查询24题
一、全文总结
1. 分段核心总结
- 表结构基础
共5张表,通过外键关联:- 班级表
class:存班级信息 - 学生表
student:关联班级class_id - 老师表
teacher:存老师信息 - 课程表
course:关联老师teacher_id - 成绩表
score:关联学生student_id+课程course_id
- 班级表
- 查询核心技巧
- 多表查询:用
INNER JOIN连表,ON写关联条件 - 统计计算:
GROUP BY分组 +COUNT/AVG/SUM/MAX聚合 - 过滤规则:
WHERE过滤原始数据,HAVING过滤分组结果 - 嵌套查询:子查询
()先算内层,再算外层 - 去重/排序:
DISTINCT去重,ORDER BY排序,LIMIT取前N条
- 多表查询:用
- 高频题型
成绩统计、选课分析、师生关联、学生筛选、班级汇总
2. 核心知识点表格
| 题型 | 核心方法 | 必用关键字 |
|---|---|---|
| 多表关联 | 连接表+关联字段 | INNER JOIN、LEFT JOIN、ON |
| 分组统计 | 分组+聚合函数 | GROUP BY、COUNT、AVG、SUM、MAX |
| 分组后筛选 | 聚合条件过滤 | HAVING |
| 嵌套筛选 | 内层查条件,外层查结果 | 子查询()、IN、NOT IN |
| 去重/排序 | 去重+排序 | DISTINCT、ORDER BY、DESC |
| 取Top数据 | 限制结果条数 | LIMIT |
二、完整详细教程(建表+24题全解)
2.1 建表+插入数据(基础准备·带注释)
-- ====================== 1. 创建5张表(外键关联) ======================
-- 班级表
CREATE TABLE class(
cid INT PRIMARY KEY AUTO_INCREMENT, -- 班级ID(主键自增)
caption VARCHAR(32) NOT NULL -- 班级名称
);
-- 学生表(关联班级)
CREATE TABLE student(
sid INT PRIMARY KEY AUTO_INCREMENT, -- 学生ID
gender CHAR(1) NOT NULL, -- 性别
class_id INT NOT NULL, -- 关联班级ID
sname VARCHAR(32) NOT NULL, -- 学生姓名
FOREIGN KEY(class_id) REFERENCES class(cid) -- 外键关联班级
);
-- 老师表
CREATE TABLE teacher(
tid INT PRIMARY KEY AUTO_INCREMENT, -- 老师ID
tname VARCHAR(32) NOT NULL -- 老师姓名
);
-- 课程表(关联老师)
CREATE TABLE course(
cid INT PRIMARY KEY AUTO_INCREMENT, -- 课程ID
cname VARCHAR(32) NOT NULL, -- 课程名称
teacher_id INT NOT NULL, -- 关联老师ID
FOREIGN KEY(teacher_id) REFERENCES teacher(tid) -- 外键关联老师
);
-- 成绩表(关联学生+课程)
CREATE TABLE score(
sid INT PRIMARY KEY AUTO_INCREMENT, -- 成绩ID
student_id INT NOT NULL, -- 关联学生ID
course_id INT NOT NULL, -- 关联课程ID
num INT NOT NULL, -- 分数
FOREIGN KEY(student_id) REFERENCES student(sid), -- 外键关联学生
FOREIGN KEY(course_id) REFERENCES course(cid) -- 外键关联课程
);
-- ====================== 2. 插入测试数据 ======================
-- 班级数据
INSERT INTO class VALUES('1','三年二班'),('2','三年三班'),('3','一年二班'),('4','二年一班');
-- 学生数据
INSERT INTO student VALUES('1','男','1','理解'),('2','女','1','钢蛋'),('3','男','1','张三'),('4','男','1','张一'),('5','女','1','张二'),('6','男','1','张四'),('7','女','2','铁锤'),('8','男','2','李三'),('9','男','2','李一'),('10','女','2','李二'),('11','男','2','李四'),('12','女','3','如花'),('13','男','3','刘三'),('14','男','3','刘一'),('15','女','3','刘二'),('16','男','3','刘四');
-- 老师数据
INSERT INTO teacher VALUES('1','张磊'),('2','李平'),('3','刘海燕'),('4','朱云海'),('5','李春秋');
-- 课程数据
INSERT INTO course VALUES('1','生物','1'),('2','物理','2'),('3','体育','3'),('4','美术','2');
-- 成绩数据
INSERT INTO score VALUES('1','1','1','10'),('2','1','2','9'),('3','1','3','76'),('5','1','4','66'),('6','2','1','8'),('8','2','3','68'),('9','2','4','99'),('10','3','1','77'),('11','3','2','66'),('12','3','3','87'),('13','3','4','99'),('14','4','1','79'),('15','4','2','11'),('16','4','3','67'),('17','4','4','100'),('18','5','1','79'),('19','5','2','11'),('20','5','3','67'),('21','5','4','100'),('22','6','1','9'),('23','6','2','100'),('24','6','3','67'),('25','6','4','100'),('26','7','1','9'),('27','7','2','100'),('28','7','3','67'),('29','7','4','88'),('30','8','1','9'),('31','8','2','100'),('32','8','3','67'),('33','8','4','88'),('34','9','1','91'),('35','9','2','88'),('36','9','3','67'),('37','9','4','22'),('38','10','1','90'),('39','10','2','77'),('40','10','3','43'),('41','10','4','87'),('42','11','1','90'),('43','11','2','77'),('44','11','3','43'),('45','11','4','87'),('46','12','1','90'),('47','12','2','77'),('48','12','3','43'),('49','12','4','87'),('52','13','3','87');
2.2 24道查询题(逐题拆解·SQL+注释+运算+结果)
1. 查询所有课程名称及任课老师姓名
-- 注释:课程表course 连接 老师表teacher,关联字段:course.teacher_id = teacher.tid
SELECT course.cname, teacher.tname
FROM course
INNER JOIN teacher ON course.teacher_id = teacher.tid;
运算过程:连接两表,匹配课程对应的老师
查询结果:
| cname | tname |
|---|---|
| 生物 | 张磊 |
| 物理 | 李平 |
| 体育 | 刘海燕 |
| 美术 | 李平 |
2. 查询学生表中男女生各有多少人
-- 注释:按性别gender分组,COUNT(*)统计人数
SELECT gender, COUNT(*) AS 人数
FROM student
GROUP BY gender;
运算过程:分组统计男/女学生数量
查询结果:
| gender | 人数 |
|---|---|
| 男 | 10 |
| 女 | 6 |
3. 查询物理成绩=100的学生姓名
-- 注释:三表连接(score+course+student),筛选课程=物理,分数=100
SELECT st.sname
FROM course c
INNER JOIN score sc ON sc.course_id = c.cid
INNER JOIN student st ON sc.student_id = st.sid
WHERE c.cname = '物理' AND sc.num = 100;
运算过程:连表→筛选物理→筛选100分→取姓名
查询结果:张四、铁锤、李三
4. 查询平均成绩>80的学生姓名+平均成绩
-- 注释:成绩表连接学生表,按学生分组,HAVING过滤平均分>80
SELECT st.sname, AVG(sc.num) AS 平均成绩
FROM score sc
INNER JOIN student st ON sc.student_id = st.sid
GROUP BY sc.student_id
HAVING AVG(sc.num) > 80;
运算过程:分组算平均分→过滤>80→连表取姓名
查询结果:钢蛋、张二、张四、李一
5. 查询所有学生学号、姓名、选课数、总成绩
-- 注释:成绩表右连接学生表(保留所有学生),分组统计选课数+总成绩
SELECT st.sid, st.sname, COUNT(sc.course_id) AS 选课数, SUM(sc.num) AS 总成绩
FROM student st
LEFT JOIN score sc ON st.sid = sc.student_id
GROUP BY st.sid;
运算过程:左连保所有学生→分组统计→计算求和/计数
查询结果:16名学生的学号、姓名、选课数、总成绩
6. 查询姓李老师的个数
-- 注释:LIKE模糊查询李开头,COUNT统计人数
SELECT COUNT(*) AS 姓李老师数
FROM teacher
WHERE tname LIKE '李%';
运算过程:筛选姓李→统计数量
查询结果:2(李平、李春秋)
7. 查询没报李平老师课的学生姓名
-- 注释:子查询查李平课程→查选这些课的学生→NOT IN取反
SELECT sname
FROM student
WHERE sid NOT IN (
SELECT DISTINCT student_id
FROM score
WHERE course_id IN (
SELECT cid FROM course
INNER JOIN teacher ON course.teacher_id = teacher.tid
WHERE tname = '李平'
)
);
运算过程:内层查李平课程→中层查选课学生→外层取反
查询结果:如花、刘三、刘一、刘二、刘四
8. 查询物理分数>生物分数的学生学号
-- 注释:子查询分别查物理/生物成绩→连接两表→筛选分数>
SELECT t1.student_id
FROM (SELECT student_id, num FROM score INNER JOIN course ON score.course_id = course.cid WHERE cname = '物理') t1
INNER JOIN (SELECT student_id, num FROM score INNER JOIN course ON score.course_id = course.cid WHERE cname = '生物') t2
ON t1.student_id = t2.student_id
WHERE t1.num > t2.num;
运算过程:分别取两科成绩→连接→比较分数
查询结果:3、4、5、6、7、8、9、10、11、12
9. 查询没同时选修物理+体育的学生姓名
-- 注释:查选物理/体育的学生→分组count=1→连表取姓名
SELECT sname
FROM student
WHERE sid IN (
SELECT student_id
FROM score
WHERE course_id IN (SELECT cid FROM course WHERE cname IN ('物理','体育'))
GROUP BY student_id
HAVING COUNT(*) = 1
);
运算过程:筛选两科→分组统计选课数=1→取姓名
查询结果:理解、刘三
10. 查询挂科≥2门的学生姓名+班级
-- 注释:筛选<60分→分组count≥2→连学生+班级表
SELECT st.sname, c.caption
FROM (SELECT student_id FROM score WHERE num < 60 GROUP BY student_id HAVING COUNT(*) >= 2) t1
INNER JOIN student st ON t1.student_id = st.sid
INNER JOIN class c ON st.class_id = c.cid;
运算过程:筛选挂科→统计≥2门→连表取姓名+班级
查询结果:理解(三年二班)
11. 查询选修所有课程的学生姓名
-- 注释:查总课程数→分组选课数=总课程数→取姓名
SELECT sname
FROM student
WHERE sid IN (
SELECT student_id
FROM score
GROUP BY student_id
HAVING COUNT(*) = (SELECT COUNT(*) FROM course)
);
运算过程:算总课程→分组选课数匹配→取姓名
查询结果:张一、张二、张四、铁锤、李三
12. 查询李平老师课程的所有成绩记录
-- 注释:子查询查李平课程→成绩表筛选
SELECT * FROM score
WHERE course_id IN (
SELECT cid FROM course
INNER JOIN teacher ON course.teacher_id = teacher.tid
WHERE tname = '李平'
);
运算过程:查李平课程→筛选成绩
查询结果:物理、美术课程的所有成绩
13. 查询全部学生都选修的课程号+名称
-- 注释:查总学生数→课程分组选课数=总学生数→取课程
SELECT cid, cname
FROM course
WHERE cid IN (
SELECT course_id
FROM score
GROUP BY course_id
HAVING COUNT(DISTINCT student_id) = (SELECT COUNT(DISTINCT student_id) FROM score)
);
运算过程:算总学生→课程分组匹配→取课程
查询结果:无(无全学生选修的课程)
14. 查询每门课程被选修的次数
-- 注释:按课程分组,COUNT统计选课次数
SELECT course_id, COUNT(*) AS 选修次数
FROM score
GROUP BY course_id;
运算过程:分组统计每门课选课人数
查询结果:1=12、2=12、3=13、4=12
15. 查询只选1门课的学生学号+姓名
-- 注释:分组选课数=1→连表取姓名
SELECT sid, sname
FROM student
WHERE sid IN (
SELECT student_id
FROM score
GROUP BY student_id
HAVING COUNT(*) = 1
);
运算过程:分组选课数=1→取学号姓名
查询结果:刘三
16. 查询所有成绩(去重)并降序
-- 注释:DISTINCT去重,ORDER BY DESC降序
SELECT DISTINCT num
FROM score
ORDER BY num DESC;
运算过程:去重→从高到低排序
查询结果:100、99、91、90、88、87、79、77、76、68、67、66、43、22、11、10、9、8
17. 查询平均成绩>85的学生姓名+平均成绩
-- 注释:分组算平均分→HAVING>85→连表取姓名
SELECT st.sname, AVG(sc.num) AS 平均成绩
FROM score sc
INNER JOIN student st ON sc.student_id = st.sid
GROUP BY sc.student_id
HAVING AVG(sc.num) > 85;
运算过程:分组算分→过滤>85→取姓名
查询结果:张二、张四
18. 查询生物成绩不及格的学生姓名+分数
-- 注释:三表连接,筛选生物+<60分
SELECT st.sname, sc.num
FROM score sc
INNER JOIN course c ON sc.course_id = c.cid
INNER JOIN student st ON sc.student_id = st.sid
WHERE c.cname = '生物' AND sc.num < 60;
运算过程:连表→筛选生物+不及格→取结果
查询结果:理解(10)、钢蛋(8)、张四(9)、铁锤(9)、李三(9)
19. 查询李平课程中平均成绩最高的学生姓名
-- 注释:查李平课程→分组算平均分→降序取第1→取姓名
SELECT st.sname
FROM score sc
INNER JOIN student st ON sc.student_id = st.sid
WHERE course_id IN (SELECT cid FROM course INNER JOIN teacher ON course.teacher_id = teacher.tid WHERE tname = '李平')
GROUP BY sc.student_id
ORDER BY AVG(sc.num) DESC
LIMIT 1;
运算过程:筛选李平课程→分组算分→取最高→取姓名
查询结果:张二
20. 查询每门课程成绩最好的课程id、学生姓名、分数
-- 注释:查每门课最高分→连成绩+学生表→匹配最高分
SELECT t2.cid, st.sname, t1.num
FROM score t1
INNER JOIN (SELECT course_id, MAX(num) max_num FROM score GROUP BY course_id) t2 ON t1.course_id = t2.course_id AND t1.num = t2.max_num
INNER JOIN student st ON t1.student_id = st.sid;
运算过程:查每科最高分→匹配学生→取结果
查询结果:每门课最高分学生
21. 查询不同课程但成绩相同的课程号、学生号、成绩
-- 注释:成绩表自连接,筛选成绩同、课程不同
SELECT DISTINCT
s1.course_id, s1.student_id, s1.num
FROM score s1, score s2
WHERE s1.num = s2.num AND s1.course_id != s2.course_id;
运算过程:自连接→匹配同分数不同课程→去重
查询结果:同分数不同课程的记录
22. 查询没学李平课的学生姓名+选修课程
-- 注释:子查询取反→连成绩+课程表→取课程名
SELECT st.sname, c.cname
FROM student st
INNER JOIN score sc ON st.sid = sc.student_id
INNER JOIN course c ON sc.course_id = c.cid
WHERE st.sid NOT IN (
SELECT DISTINCT student_id FROM score
WHERE course_id IN (SELECT cid FROM course INNER JOIN teacher ON course.teacher_id = teacher.tid WHERE tname = '李平')
);
运算过程:取反没选李平课→连表取课程
查询结果:如花、刘三、刘一、刘二、刘四的选修课程
23. 查询选了学号2同学课程的同学学号+姓名
-- 注释:查学号2选的课→查选这些课的学生→去重取姓名
SELECT DISTINCT st.sid, st.sname
FROM student st
INNER JOIN score sc ON st.sid = sc.student_id
WHERE sc.course_id IN (SELECT course_id FROM score WHERE student_id = 2);
运算过程:查学号2课程→筛选选课学生→去重
查询结果:除学号2外,选过对应课程的学生
24. 查询任课最多老师的学生单科最高分(课程id、姓名、分数)
-- 注释:查任课最多老师→查其课程→查最高分→连学生表
SELECT t1.course_id, st.sname, t1.num
FROM score t1
INNER JOIN (SELECT course_id, MAX(num) max_num FROM score WHERE course_id IN (SELECT cid FROM course WHERE teacher_id = (SELECT teacher_id FROM course GROUP BY teacher_id ORDER BY COUNT(*) DESC LIMIT 1)) GROUP BY course_id) t2 ON t1.course_id = t2.course_id AND t1.num = t2.max_num
INNER JOIN student st ON t1.student_id = st.sid;
运算过程:查任课最多老师→查其课程→查最高分→取学生
查询结果:李平课程的最高分学生
三、应用场景(教务管理实战·带注释+运算)
场景1:学生成绩分析(教务统计)
需求:统计每个班级的平均分,筛选优秀班级(平均分≥80)
-- 注释:连学生+成绩+班级表→按班级分组→算平均分→过滤≥80
SELECT c.caption, AVG(sc.num) AS 班级平均分
FROM class c
INNER JOIN student st ON c.cid = st.class_id
INNER JOIN score sc ON st.sid = sc.student_id
GROUP BY c.cid
HAVING AVG(sc.num) >= 80;
运算过程:三表连接→分组算分→过滤优秀班级
查询结果:三年二班平均分达标
场景2:教师教学绩效(考核)
需求:统计每位老师的授课数+学生平均分
-- 注释:连老师+课程+成绩表→按老师分组→统计授课数+平均分
SELECT t.tname, COUNT(DISTINCT c.cid) AS 授课数, AVG(sc.num) AS 所教课程平均分
FROM teacher t
INNER JOIN course c ON t.tid = c.teacher_id
INNER JOIN score sc ON c.cid = sc.course_id
GROUP BY t.tid;
运算过程:三表连接→分组统计→计算绩效
查询结果:李平授课2门,平均分最高
场景3:学生选课预警(教务管理)
需求:筛选未选课/只选1门课的学生,提醒补选
-- 注释:左连学生+成绩→分组统计选课数→筛选≤1
SELECT st.sid, st.sname, COUNT(sc.course_id) AS 选课数
FROM student st
LEFT JOIN score sc ON st.sid = sc.student_id
GROUP BY st.sid
HAVING COUNT(sc.course_id) <= 1;
运算过程:左连保所有学生→分组统计→筛选预警学生
查询结果:刘三只选1门课,需补选
四、速记口诀
- 多表查询先找关联字段,
JOIN连表ON匹配 - 统计数据先分组,聚合函数跟在后
- 分组前用
WHERE,分组后用HAVING - 子查询先算内层,再把结果当条件
- 去重用
DISTINCT,排序用ORDER BY
附加-极简入门
一、全文核心总结(表格速览)
二、完整分步教程(带详细注释+SQL结果)
三、实战应用场景(案例+注释+运算结果)
一、全文核心总结(表格速览)
| 模块 | 核心知识点 | 关键操作命令 |
|---|---|---|
| 基础架构 | C/S模型、实例组成、三层执行结构 | mysql -uroot -p 连接;mysqld 服务进程 |
| 版本差异 | 5.6/5.7初始化命令不同;8.0加密升级 | 5.7:mysqld --initialize;5.6:mysql_install_db |
| 用户权限 | 用户=名@白名单;最小权限原则 | create user/grant/revoke |
| SQL规范 | DDL建表必加主键/注释;DML必加where | create table、insert/update/delete |
| 索引优化 | B+树索引、聚簇/辅助索引、执行计划 | explain 分析SQL;create index 建索引 |
| 存储引擎 | InnoDB支持事务/行锁;MyISAM不支持 | 建表指定engine=InnoDB |
| 事务特性 | ACID、4种隔离级别、MVCC | begin/commit/rollback |
| 备份恢复 | binlog用于恢复;mysqldump全量备份 | mysqldump 备份;source 恢复 |
| 主从复制 | 主开binlog、从同步位置点 | change master to;start slave |
二、完整分步教程(带详细注释+SQL结果)
1. MySQL基础认知(第一步:连接数据库)
1.1 两种连接方式
# 1. 远程TCP/IP连接(-h IP -P 端口 -u 用户 -p密码)
mysql -uroot -p123 -h 192.168.195.129 -P 3306
# 注释:适合远程服务器连接,-p后直接写密码(生产不推荐)
# 2. 本地Socket连接(-S 指定socket文件)
mysql -uroot -p123 -S /tmp/mysql.sock
# 注释:本地连接更快,默认socket文件/tmp/mysql.sock
1.2 查看基础信息(SQL+结果)
-- 查看数据库端口
select @@port;
-- 查询结果:+-----------+
-- | @@port |
-- +-----------+
-- | 3306 |
-- +-----------+
-- 查看数据存储目录
select @@datadir;
-- 查询结果:+----------------+
-- | @@datadir |
-- +----------------+
-- | /data/mysql/data |
-- +----------------+
2. 版本与初始化(第二步:安装后初始化)
2.1 版本初始化命令差异
# MySQL 5.7 初始化(推荐无密码快速初始化)
/usr/local/mysql/bin/mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql/data
-- 注释:--initialize-insecure=无初始密码;--user=mysql=运行用户;basedir=安装目录;datadir=数据目录
# MySQL 5.6 初始化
/usr/local/mysql/scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql/data
2.2 配置文件(my.cnf)
# /etc/my.cnf 核心配置(服务端+客户端)
[mysqld]
user=mysql # 运行用户
basedir=/usr/local/mysql # 安装目录
datadir=/data/mysql/data # 数据目录
server_id=10 # 集群唯一ID
port=3306 # 端口
socket=/tmp/mysql.sock # socket文件
[mysql]
socket=/tmp/mysql.sock # 客户端默认socket
3. 用户与权限管理(第三步:创建用户+授权)
3.1 用户格式
用户名@'白名单':白名单=允许登录的IP/网段
-- 1. 创建本地用户(仅本机登录)
create user oldguo@'localhost' identified by '123';
-- 注释:identified by '123'=设置密码;localhost=仅本地socket登录
-- 2. 创建远程用户(所有IP可登录)
create user oldguo@'%' identified by '123';
-- 注释:%=通配符,所有远程IP可连接
-- 3. 查看所有用户
select user,host,authentication_string from mysql.user;
-- 查询结果:+--------+-----------+------------------+
-- | user | host | authentication_string |
-- +--------+-----------+------------------+
-- | root | localhost | *xxx |
-- | oldguo | % | *xxx |
-- +--------+-----------+------------------+
3.2 权限授权与回收
-- 1. 授权:给oldguo用户所有库所有权限(生产慎用)
grant all on *.* to oldguo@'%' identified by '123';
-- 注释:all=所有权限;*.*=所有库所有表;with grant option=可转授权
-- 2. 回收权限
revoke delete on *.* from oldguo@'%';
-- 注释:回收oldguo用户的删除权限
-- 3. 刷新权限(必执行)
flush privileges;
4. SQL开发规范(第四步:写标准SQL)
4.1 DDL(库表创建)规范
-- 1. 创建数据库(指定字符集utf8mb4)
create database oldguo charset=utf8mb4;
-- 注释:库名小写、业务相关、指定字符集,禁止大写/数字开头
-- 查询结果:Query OK, 1 row affected (0.00 sec)
-- 2. 创建表(InnoDB引擎、主键、注释、非空)
create table book(
id int primary key auto_increment comment '书籍ID', -- 主键自增+注释
title varchar(100) not null default '' comment '书名', -- 非空+默认值
author varchar(50) not null default '' comment '作者',
price decimal(10,2) not null default 0.00 comment '价格'
)engine=InnoDB charset=utf8mb4 comment='书籍表';
-- 注释:必加主键、引擎InnoDB、字符集utf8mb4、全字段注释
-- 查询结果:Query OK, 0 rows affected (0.01 sec)
4.2 DML(数据增删改)规范
-- 1. 插入数据(批量插入,精准列)
insert into book(title,author,price) values('MySQL入门','oldguo',59.99),('DRF实战','张三',69.99);
-- 注释:指定列名,禁止insert into t1 values()全表插入
-- 查询结果:Query OK, 2 rows affected (0.00 sec)
-- 2. 更新数据(必加where条件)
update book set price=49.99 where title='MySQL入门';
-- 注释:无where会更新全表,生产禁止
-- 查询结果:Query OK, 1 row affected (0.00 sec)
-- 3. 删除数据(用伪删除替代,禁止delete全表)
-- 推荐:加status字段,0=正常1=删除
alter table book add status tinyint not null default 0 comment '状态:0正常1删除';
update book set status=1 where id=1;
-- 查询结果:Query OK, 1 row affected (0.00 sec)
5. 索引与执行计划(第五步:优化查询)
5.1 创建索引
-- 1. 创建普通索引(作者列)
create index idx_author on book(author);
-- 注释:辅助索引,叶子节点存索引值+主键
-- 2. 查看索引
show index from book;
-- 查询结果:会显示表名、索引名、索引列、索引类型
5.2 执行计划分析(explain)
-- 分析查询是否走索引
explain select * from book where author='oldguo';
-- 核心结果:type=ref(走索引)、key=idx_author(使用索引)
-- 注释:type=ALL=全表扫描(需优化);ref=辅助索引等值查询(最优)
6. 事务与存储引擎(第六步:事务操作)
6.1 InnoDB事务ACID
-- 1. 开启事务
begin; -- 等价start transaction;
-- 2. 执行修改
update book set price=39.99 where id=2;
-- 3. 提交/回滚
commit; -- 永久生效
-- rollback; -- 撤销修改
-- 注释:InnoDB支持事务,MyISAM不支持
6.2 查看隔离级别(默认RR)
select @@tx_isolation;
-- 查询结果:+-----------------+
-- | @@tx_isolation |
-- +-----------------+
-- | REPEATABLE-READ |
-- +-----------------+
7. 备份与恢复(第七步:数据安全)
7.1 全量备份(mysqldump)
# 全量备份所有库(生产标准命令)
mysqldump -uroot -p123 -A --master-data=2 --single-transaction -R -E >/backup/full_2026-05-28.sql
# 注释:-A=全库;--single-transaction=热备;-R=存储过程;-E=事件
7.2 数据恢复
-- 登录MySQL后执行恢复
source /backup/full_2026-05-28.sql;
-- 查询结果:执行完毕,数据恢复成功
8. 主从复制(第八步:集群搭建)
8.1 主库配置
-- 1. 主库开binlog(配置文件已加)
-- 2. 主库授权从库同步用户
grant replication slave on *.* to repl@'%' identified by '123';
-- 注释:replication slave=主从同步专用权限
-- 查询结果:Query OK, 0 rows affected (0.00 sec)
8.2 从库配置
-- 1. 配置同步主库信息
change master to
master_host='192.168.195.129', -- 主库IP
master_user='repl', -- 同步用户
master_password='123', -- 密码
master_port=3306, -- 主库端口
master_log_file='mysql-bin.000001', -- 主库binlog文件
master_log_pos=154; -- 同步位置点
-- 2. 启动从库
start slave;
-- 3. 查看从库状态
show slave status\G
-- 核心结果:Slave_IO_Running=Yes、Slave_SQL_Running=Yes=同步成功
三、实战应用场景(案例+注释+结果)
场景1:新业务初始化数据库(开发必用)
需求:创建业务库+业务表+授权业务用户
-- 1. 创建业务库
create database shop charset=utf8mb4;
-- 结果:Query OK, 1 row affected (0.00 sec)
-- 2. 创建商品表
create table shop.goods(
id int primary key auto_increment comment '商品ID',
name varchar(100) not null default '' comment '商品名',
price decimal(10,2) not null default 0.00 comment '售价',
stock int not null default 0 comment '库存'
)engine=InnoDB charset=utf8mb4 comment='商品表';
-- 结果:Query OK, 0 rows affected (0.01 sec)
-- 3. 创建业务用户+授权仅当前库权限
create user shop_user@'192.168.195.%' identified by 'Shop@123';
grant select,insert,update on shop.* to shop_user@'192.168.195.%';
flush privileges;
-- 结果:权限刷新成功,最小权限原则
场景2:生产数据查询优化(DBA必用)
需求:优化慢查询,添加索引
-- 原始慢SQL(全表扫描)
select * from shop.goods where name='MySQL书籍';
-- 执行计划:type=ALL(全表扫描,慢)
-- 优化:创建索引
create index idx_name on shop.goods(name);
-- 结果:Query OK, 0 rows affected (0.01 sec)
-- 优化后执行计划
explain select * from shop.goods where name='MySQL书籍';
-- 结果:type=ref,key=idx_name(走索引,快)
场景3:数据误操作恢复(运维必用)
需求:误更新数据,回滚
-- 误操作:未加where更新全表
begin; -- 开启事务(关键!)
update shop.goods set price=0;
-- 发现错误,立即回滚
rollback;
-- 结果:数据恢复原状,无损失
-- 注释:生产必须开启事务执行修改操作
场景4:主从数据同步(架构必用)
需求:主库写入,从库读取(读写分离)
-- 主库写入数据
insert into shop.goods(name,price,stock) values('Java入门',49.99,100);
-- 结果:主库写入成功
-- 从库查询(自动同步)
select * from shop.goods where name='Java入门';
-- 结果:从库查询到数据,主从同步正常

浙公网安备 33010602011771号