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并发

  • 核心依赖:socketgevent(协程)、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 运行过程说明
  1. 先运行服务端:控制台输出“服务端启动,等待客户端连接...”
  2. 运行客户端:客户端连接服务端,服务端打印“新客户端连接:(127.0.0.1, 端口号)”
  3. 服务端向客户端发“hello from server”,客户端接收并打印
  4. 客户端向服务端发“hello from client”,服务端接收并打印
  5. 循环上述收发过程,直到关闭客户端/服务端

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 运行过程说明
  1. 服务端启动:打印“协程服务端启动...”
  2. 客户端启动:5个线程各创建一个客户端连接服务端
  3. 服务端每个连接对应一个协程,所有协程运行在同一个线程(打印的线程ID相同)
  4. 协程遇到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
  • 核心运算过程:
    1. 客户端连接后发送房间号,服务端将连接加入对应房间列表
    2. 接收玩家消息(如“出牌345”),广播给房间内所有其他玩家
    3. 玩家断开时,从房间移除连接并广播退出消息
  • 优势:线程池适配游戏的中等并发,每个房间独立线程,逻辑简单易维护

场景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+在线用户)、物联网设备数据采集
  • 核心运算过程:
    1. 新用户连接→加入在线列表,广播上线消息
    2. 接收用户聊天消息→构造带用户IP/端口的消息→广播给所有其他用户
    3. 用户断开→移除在线列表,广播下线消息
  • 优势:协程轻量级,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;

分段版总结

  1. 基础架构:MySQL是关系型数据库,以“库-表-记录-字段”层级存储数据;非关系型数据库(Redis/MongoDB)以键值对存储,无表结构。
  2. SQL核心:DDL定义库/表结构(CREATE/DROP/ALTER)、DML操作记录(INSERT/UPDATE/DELETE)、DQL查询数据(SELECT)、DCL控制权限(GRANT/REVOKE)。
  3. 操作流程:先连接MySQL→打开目标库→操作表/记录→退出;SQL语句分号结尾,大小写不敏感,错误命令用\c取消。
  4. 数据约束:通过约束保证数据合法性,如主键唯一非空、外键关联多表、自增简化主键赋值、联合唯一保证多字段组合不重复。
  5. 存储引擎:InnoDB为默认引擎,支持事务和并发;MyISAM适合读多写少场景;MEMORY速度快但数据不持久;BLACKHOLE用于数据同步。
  6. 事务特性:保证多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语法规则

  1. 语句以分号;结尾,可换行书写;
  2. 关键字/函数建议大写(如SELECT/INSERT),但不区分大小写;
  3. 若提示符显示'>,说明单引号未闭合,输入'+回车即可恢复;
  4. 命令输错换行后,用\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名最新入职员工

四、速记口诀

  1. 先过滤后分组,分组完再过滤用HAVING
  2. 空值判断用is null/is not null,别用=
  3. 多表关联找共同字段,左连保左表,右连保右表
  4. 子查询先算内层,结果当条件/临时表


38、MySQL架构_SQL分类_性能安全_数据管理

全文总结

一、核心概念(分段)

  1. MySQL架构:库(文件夹)→表(文件)→记录(文件内容)→字段(记录属性);分关系型(MySQL/Oracle)和非关系型(Redis/MongoDB)数据库。
  2. SQL分类:DDL(定义库/表)、DML(增删改记录)、DQL(查询记录)、DCL(权限控制)。
  3. 操作流程:连接MySQL→打开库→操作(库/表/记录)→关闭退出。
  4. Python操作MySQL:依赖pymysql库,核心流程为「连接→游标→执行SQL→提交/获取结果→关闭」;默认开启事务(增删改需commit),预处理可防SQL注入。
  5. 性能&安全:索引(B+树/联合索引)优化查询、存储引擎按需选择(InnoDB/MyISAM)、预处理防注入。
  6. 数据管理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 库名 > 备份.sqlsource 备份.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

运算过程

  • 索引生效:联合索引包含查询的所有条件字段,explainkey显示索引名,查询从「全表扫描」变为「范围扫描」;
  • 条件过滤:between限定时间范围,amount>500过滤金额,最终匹配2条订单,查询效率提升10倍以上。


39、MySQL多表查询24题

一、全文总结

1. 分段核心总结

  1. 表结构基础
    共5张表,通过外键关联:
    • 班级表class:存班级信息
    • 学生表student:关联班级class_id
    • 老师表teacher:存老师信息
    • 课程表course:关联老师teacher_id
    • 成绩表score:关联学生student_id+课程course_id
  2. 查询核心技巧
    • 多表查询:用INNER JOIN连表,ON写关联条件
    • 统计计算:GROUP BY分组 + COUNT/AVG/SUM/MAX聚合
    • 过滤规则:WHERE过滤原始数据,HAVING过滤分组结果
    • 嵌套查询:子查询()先算内层,再算外层
    • 去重/排序:DISTINCT去重,ORDER BY排序,LIMIT取前N条
  3. 高频题型
    成绩统计、选课分析、师生关联、学生筛选、班级汇总

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门课,需补选


四、速记口诀

  1. 多表查询先找关联字段JOIN连表ON匹配
  2. 统计数据先分组,聚合函数跟在后
  3. 分组前用WHERE,分组后用HAVING
  4. 子查询先算内层,再把结果当条件
  5. 去重用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入门';
-- 结果:从库查询到数据,主从同步正常



posted @ 2026-05-28 18:46  爱折腾的大臭臭  阅读(8)  评论(0)    收藏  举报