Bootstrap

数据库(DataBase)

MySQL

简介

数据库

数据库:DataBase,简称 DB,存储和管理数据的仓库

数据库的优势:

  • 可以持久化存储数据
  • 方便存储和管理数据
  • 使用了统一的方式操作数据库 SQL

数据库、数据表、数据的关系介绍:

  • 数据库

    • 用于存储和管理数据的仓库
    • 一个库中可以包含多个数据表
  • 数据表

    • 数据库最重要的组成部分之一
    • 由纵向的列和横向的行组成(类似 excel 表格)
    • 可以指定列名、数据类型、约束等
    • 一个表中可以存储多条数据
  • 数据:想要永久化存储的数据

参考视频:https://www.bilibili.com/video/BV1zJ411M7TB

参考专栏:https://time.geekbang.org/column/intro/139

参考书籍:https://book.douban.com/subject/35231266/


MySQL

MySQL 数据库是一个最流行的关系型数据库管理系统之一,关系型数据库是将数据保存在不同的数据表中,而且表与表之间可以有关联关系,提高了灵活性

缺点:数据存储在磁盘中,导致读写性能差,而且数据关系复杂,扩展性差

MySQL 所使用的 SQL 语句是用于访问数据库最常用的标准化语言

MySQL 配置:

  • MySQL 安装:https://www.jianshu.com/p/ba48f1e386f0

  • MySQL 配置:

    • 修改 MySQL 默认字符集:安装 MySQL 之后第一件事就是修改字符集编码

      vim /etc/mysql/my.cnf
      
      添加如下内容:
      [mysqld]
      character-set-server=utf8
      collation-server=utf8_general_ci
      
      [client]
      default-character-set=utf8
      
    • 启动 MySQL 服务:

      systemctl start/restart mysql
      
    • 登录 MySQL:

      mysql -u root -p  敲回车,输入密码
      初始密码查看:cat /var/log/mysqld.log
      在root@localhost:   后面的就是初始密码
      
    • 查看默认字符集命令:

      SHOW VARIABLES LIKE 'char%';
      
    • 修改MySQL登录密码:

      set global validate_password_policy=0;
      set global validate_password_length=1;
        
      set password=password('密码');
      
    • 授予远程连接权限(MySQL 内输入):

      -- 授权
      grant all privileges on *.* to 'root' @'%' identified by '密码';
      -- 刷新
      flush privileges;
      
  • 修改 MySQL 绑定 IP:

    cd /etc/mysql/mysql.conf.d
    sudo chmod 666 mysqld.cnf 
    vim mysqld.cnf 
    # bind-address = 127.0.0.1注释该行
    
  • 关闭 Linux 防火墙

    systemctl stop firewalld.service
    # 放行3306端口
    

体系架构

整体架构

体系结构详解:

  • 第一层:网络连接层
    • 一些客户端和链接服务,包含本地 Socket 通信和大多数基于客户端/服务端工具实现的 TCP/IP 通信,主要完成一些类似于连接处理、授权认证、及相关的安全方案
    • 在该层上引入了连接池 Connection Pool 的概念,管理缓冲用户连接,线程处理等需要缓存的需求
    • 在该层上实现基于 SSL 的安全链接,服务器也会为安全接入的每个客户端验证它所具有的操作权限
  • 第二层:核心服务层
    • 查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,所有的内置函数(日期、数学、加密函数等)
      • Management Serveices & Utilities:系统管理和控制工具,备份、安全、复制、集群等
      • SQL Interface:接受用户的 SQL 命令,并且返回用户需要查询的结果
      • Parser:SQL 语句分析器
      • Optimizer:查询优化器
      • Caches & Buffers:查询缓存,服务器会查询内部的缓存,如果缓存空间足够大,可以在大量读操作的环境中提升系统性能
    • 所有跨存储引擎的功能在这一层实现,如存储过程、触发器、视图等
    • 在该层服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询顺序,是否利用索引等, 最后生成相应的执行操作
    • MySQL 中服务器层不管理事务,事务是由存储引擎实现的
  • 第三层:存储引擎层
    • Pluggable Storage Engines:存储引擎接口,MySQL 区别于其他数据库的重要特点就是其存储引擎的架构模式是插件式的(存储引擎是基于表的,而不是数据库)
    • 存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 和存储引擎进行通信
    • 不同的存储引擎具有不同的功能,共用一个 Server 层,可以根据开发的需要,来选取合适的存储引擎
  • 第四层:系统文件层
    • 数据存储层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互
    • File System:文件系统,保存配置文件、数据文件、日志文件、错误文件、二进制文件等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dZiI4tu-1686862213557)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-体系结构.png)]


建立连接

连接器

池化技术:对于访问数据库来说,建立连接的代价是比较昂贵的,因为每个连接对应一个用来交互的线程,频繁的创建关闭连接比较耗费资源,有必要建立数据库连接池,以提高访问的性能

连接建立 TCP 以后需要做权限验证,验证成功后可以进行执行 SQL。如果这时管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限,只有再新建的连接才会使用新的权限设置

MySQL 服务器可以同时和多个客户端进行交互,所以要保证每个连接会话的隔离性(事务机制部分详解)

整体的执行流程:


权限信息

grant 语句会同时修改数据表和内存,判断权限的时候使用的是内存数据

flush privileges 语句本身会用数据表(磁盘)的数据重建一份内存权限数据,所以在权限数据可能存在不一致的情况下使用,这种不一致往往是由于直接用 DML 语句操作系统权限表导致的,所以尽量不要使用这类语句

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFKOGaNl-1686862213559)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-权限范围.png)]


连接状态

客户端如果长时间没有操作,连接器就会自动断开,时间是由参数 wait_timeout 控制的,默认值是 8 小时。如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒:Lost connection to MySQL server during query

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接;短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个

为了减少连接的创建,推荐使用长连接,但是过多的长连接会造成 OOM,解决方案:

  • 定期断开长连接,使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连

    KILL CONNECTION id
    
  • MySQL 5.7 版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源,这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态

SHOW PROCESSLIST:查看当前 MySQL 在进行的线程,可以实时地查看 SQL 的执行情况,其中的 Command 列显示为 Sleep 的这一行,就表示现在系统里面有一个空闲连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHeIFPGJ-1686862213559)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-SHOW_PROCESSLIST命令.png)]

参数 含义
ID 用户登录 mysql 时系统分配的 connection_id,可以使用函数 connection_id() 查看
User 显示当前用户,如果不是 root,这个命令就只显示用户权限范围的 sql 语句
Host 显示这个语句是从哪个 ip 的哪个端口上发的,可以用来跟踪出现问题语句的用户
db 显示这个进程目前连接的是哪个数据库
Command 显示当前连接的执行的命令,一般取值为休眠 Sleep、查询 Query、连接 Connect 等
Time 显示这个状态持续的时间,单位是秒
State 显示使用当前连接的 sql 语句的状态,以查询为例,需要经过 copying to tmp table、sorting result、sending data等状态才可以完成
Info 显示执行的 sql 语句,是判断问题语句的一个重要依据

Sending data 状态表示 MySQL 线程开始访问数据行并把结果返回给客户端,而不仅仅只是返回给客户端,是处于执行器过程中的任意阶段。由于在 Sending data 状态下,MySQL 线程需要做大量磁盘读取操作,所以是整个查询中耗时最长的状态


执行流程

查询缓存
工作流程

当执行完全相同的 SQL 语句的时候,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存

查询过程:

  1. 客户端发送一条查询给服务器
  2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果(一般是 K-V 键值对),否则进入下一阶段
  3. 分析器进行 SQL 分析,再由优化器生成对应的执行计划
  4. 执行器根据优化器生成的执行计划,调用存储引擎的 API 来执行查询
  5. 将结果返回给客户端

大多数情况下不建议使用查询缓存,因为查询缓存往往弊大于利

  • 查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能费力地把结果存起来,还没使用就被一个更新全清空了,对于更新压力大的数据库来说,查询缓存的命中率会非常低
  • 除非业务就是有一张静态表,很长时间才会更新一次,比如一个系统配置表,那这张表上的查询才适合使用查询缓存

缓存配置
  1. 查看当前 MySQL 数据库是否支持查询缓存:

    SHOW VARIABLES LIKE 'have_query_cache';	-- YES
    
  2. 查看当前 MySQL 是否开启了查询缓存:

    SHOW VARIABLES LIKE 'query_cache_type';	-- OFF
    

    参数说明:

    • OFF 或 0:查询缓存功能关闭

    • ON 或 1:查询缓存功能打开,查询结果符合缓存条件即会缓存,否则不予缓存;可以显式指定 SQL_NO_CACHE 不予缓存

    • DEMAND 或 2:查询缓存功能按需进行,显式指定 SQL_CACHE 的 SELECT 语句才缓存,其它不予缓存

      SELECT SQL_CACHE id, name FROM customer; -- SQL_CACHE:查询结果可缓存
      SELECT SQL_NO_CACHE id, name FROM customer;-- SQL_NO_CACHE:不使用查询缓存
      
  3. 查看查询缓存的占用大小:

    SHOW VARIABLES LIKE 'query_cache_size';-- 单位是字节 1048576 / 1024 = 1024 = 1KB
    
  4. 查看查询缓存的状态变量:

    SHOW STATUS LIKE 'Qcache%';
    
    参数 含义
    Qcache_free_blocks 查询缓存中的可用内存块数
    Qcache_free_memory 查询缓存的可用内存量
    Qcache_hits 查询缓存命中数
    Qcache_inserts 添加到查询缓存的查询数
    Qcache_lowmen_prunes 由于内存不足而从查询缓存中删除的查询数
    Qcache_not_cached 非缓存查询的数量(由于 query_cache_type 设置而无法缓存或未缓存)
    Qcache_queries_in_cache 查询缓存中注册的查询数
    Qcache_total_blocks 查询缓存中的块总数
  5. 配置 my.cnf:

    sudo chmod 666 /etc/mysql/my.cnf
    vim my.cnf
    # mysqld中配置缓存
    query_cache_type=1
    

    重启服务既可生效,执行 SQL 语句进行验证 ,执行一条比较耗时的 SQL 语句,然后再多执行几次,查看后面几次的执行时间;获取通过查看查询缓存的缓存命中数,来判定是否走查询缓存


缓存失效

查询缓存失效的情况:

  • SQL 语句不一致,要想命中查询缓存,查询的 SQL 语句必须一致,因为缓存中 key 是查询的语句,value 是查询结构

    select count(*) from tb_item;
    Select count(*) from tb_item;	-- 不走缓存,首字母不一致
    
  • 当查询语句中有一些不确定查询时,则不会缓存,比如:now()、current_date()、curdate()、curtime()、rand()、uuid()、user()、database()

    SELECT * FROM tb_item WHERE updatetime < NOW() LIMIT 1;
    SELECT USER();
    SELECT DATABASE();
    
  • 不使用任何表查询语句:

    SELECT 'A';
    
  • 查询 mysql、information_schema、performance_schema 等系统表时,不走查询缓存:

    SELECT * FROM information_schema.engines;
    
  • 跨存储引擎的存储过程、触发器或存储函数的主体内执行的查询,缓存失效

  • 如果表更改,则使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除,包括使用 MERGE 映射到已更改表的表的查询,比如:INSERT、UPDATE、DELETE、ALTER TABLE、DROP TABLE、DROP DATABASE


分析器

没有命中查询缓存,就开始了 SQL 的真正执行,分析器会对 SQL 语句做解析

SELECT * FROM t WHERE id = 1;

解析器:处理语法和解析查询,生成一课对应的解析树

  • 先做词法分析,输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么代表什么。从输入的 select 这个关键字识别出来这是一个查询语句;把字符串 t 识别成 表名 t,把字符串 id 识别成列 id
  • 然后做语法分析,根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果语句不对,就会收到 You have an error in your SQL syntax 的错误提醒

预处理器:进一步检查解析树的合法性,比如数据表和数据列是否存在、别名是否有歧义等


优化器
成本分析

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序

  • 根据搜索条件找出所有可能的使用的索引
  • 成本分析,执行成本由 I/O 成本和 CPU 成本组成,计算全表扫描和使用不同索引执行 SQL 的代价
  • 找到一个最优的执行方案,用最小的代价去执行语句

在数据库里面,扫描行数是影响执行代价的因素之一,扫描的行数越少意味着访问磁盘的次数越少,消耗的 CPU 资源越少,优化器还会结合是否使用临时表、是否排序等因素进行综合判断


统计数据

MySQL 中保存着两种统计数据:

  • innodb_table_stats 存储了表的统计数据,每一条记录对应着一个表的统计数据
  • innodb_index_stats 存储了索引的统计数据,每一条记录对应着一个索引的一个统计项的数据

MySQL 在真正执行语句之前,并不能精确地知道满足条件的记录有多少条,只能根据统计信息来估算记录,统计信息就是索引的区分度,一个索引上不同的值的个数(比如性别只能是男女,就是 2 ),称之为基数(cardinality),基数越大说明区分度越好

通过采样统计来获取基数,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数

在 MySQL 中,有两种存储统计数据的方式,可以通过设置参数 innodb_stats_persistent 的值来选择:

  • ON:表示统计信息会持久化存储(默认),采样页数 N 默认为 20,可以通过 innodb_stats_persistent_sample_pages 指定,页数越多统计的数据越准确,但消耗的资源更大
  • OFF:表示统计信息只存储在内存,采样页数 N 默认为 8,也可以通过系统变量设置(不推荐,每次重新计算浪费资源)

数据表是会持续更新的,两种统计信息的更新方式:

  • 设置 innodb_stats_auto_recalc 为 1,当发生变动的记录数量超过表大小的 10% 时,自动触发重新计算,不过是异步进行
  • 调用 ANALYZE TABLE t 手动更新统计信息,只对信息做重新统计(不是重建表),没有修改数据,这个过程中加了 MDL 读锁并且是同步进行,所以会暂时阻塞系统

EXPLAIN 执行计划在优化器阶段生成,如果 explain 的结果预估的 rows 值跟实际情况差距比较大,可以执行 analyze 命令重新修正信息


错选索引

采样统计本身是估算数据,或者 SQL 语句中的字段选择有问题时,可能导致 MySQL 没有选择正确的执行索引

解决方法:

  • 采用 force index 强行选择一个索引

    SELECT * FROM user FORCE INDEX(name) WHERE NAME='seazean';
    
  • 可以考虑修改 SQL 语句,引导 MySQL 使用期望的索引

  • 新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引


执行器

开始执行的时候,要先判断一下当前连接对表有没有执行查询的权限,如果没有就会返回没有权限的错误,在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。如果有权限,就打开表继续执行,执行器就会根据表的引擎定义,去使用这个引擎提供的接口


引擎层

Server 层和存储引擎层的交互是以记录为单位的,存储引擎会将单条记录返回给 Server 层做进一步处理,并不是直接返回所有的记录

工作流程:

  • 首先根据二级索引选择扫描范围,获取第一条符合二级索引条件的记录,进行回表查询,将聚簇索引的记录返回 Server 层,由 Server 判断记录是否符合要求
  • 然后在二级索引上继续扫描下一个符合条件的记录

推荐阅读:https://mp.weixin.qq.com/s/YZ-LckObephrP1f15mzHpA


终止流程

终止语句

终止线程中正在执行的语句:

KILL QUERY thread_id

KILL 不是马上终止的意思,而是告诉执行线程这条语句已经不需要继续执行,可以开始执行停止的逻辑(类似于打断)。因为对表做增删改查操作,会在表上加 MDL 读锁,如果线程被 KILL 时就直接终止,那这个 MDL 读锁就没机会被释放了

命令 KILL QUERYthread_id_A 的执行流程:

  • 把 session A 的运行状态改成 THD::KILL_QUERY(将变量 killed 赋值为 THD::KILL_QUERY)
  • 给 session A 的执行线程发一个信号,让 session A 来处理这个 THD::KILL_QUERY 状态

会话处于等待状态(锁阻塞),必须满足是一个可以被唤醒的等待,必须有机会去判断线程的状态,如果不满足就会造成 KILL 失败

典型场景:innodb_thread_concurrency 为 2,代表并发线程上限数设置为 2

  • session A 执行事务,session B 执行事务,达到线程上限;此时 session C 执行事务会阻塞等待,session D 执行 kill query C 无效
  • C 的逻辑是每 10 毫秒判断是否可以进入 InnoDB 执行,如果不行就调用 nanosleep 函数进入 sleep 状态,没有去判断线程状态

补充:执行 Ctrl+C 的时候,是 MySQL 客户端另外启动一个连接,然后发送一个 KILL QUERY 命令


终止连接

断开线程的连接:

KILL CONNECTION id

断开连接后执行 SHOW PROCESSLIST 命令,如果这条语句的 Command 列显示 Killed,代表线程的状态是 KILL_CONNECTION,说明这个线程有语句正在执行,当前状态是停止语句执行中,终止逻辑耗时较长

  • 超大事务执行期间被 KILL,这时回滚操作需要对事务执行期间生成的所有新数据版本做回收操作,耗时很长
  • 大查询回滚,如果查询过程中生成了比较大的临时文件,删除临时文件可能需要等待 IO 资源,导致耗时较长
  • DDL 命令执行到最后阶段被 KILL,需要删除中间过程的临时文件,也可能受 IO 资源影响耗时较久

总结:KILL CONNECTION 本质上只是把客户端的 SQL 连接断开,后面的终止流程还是要走 KILL QUERY

一个事务被 KILL 之后,持续处于回滚状态,不应该强行重启整个 MySQL 进程,应该等待事务自己执行完成,因为重启后依然继续做回滚操作的逻辑


常用工具

mysql

mysql 不是指 mysql 服务,而是指 mysql 的客户端工具

mysql [options] [database]
  • -u --user=name:指定用户名
  • -p --password[=name]:指定密码
  • -h --host=name:指定服务器IP或域名
  • -P --port=#:指定连接端口
  • -e --execute=name:执行SQL语句并退出,在控制台执行SQL语句,而不用连接到数据库执行

示例:

mysql -h 127.0.0.1 -P 3306 -u root -p
mysql -uroot -p2143 db01 -e "select * from tb_book";

admin

mysqladmin 是一个执行管理操作的客户端程序,用来检查服务器的配置和当前状态、创建并删除数据库等

通过 mysqladmin --help 指令查看帮助文档

mysqladmin -uroot -p2143 create 'test01';

binlog

服务器生成的日志文件以二进制格式保存,如果需要检查这些文本,就要使用 mysqlbinlog 日志管理工具

mysqlbinlog [options]  log-files1 log-files2 ...
  • -d --database=name:指定数据库名称,只列出指定的数据库相关操作

  • -o --offset=#:忽略掉日志中的前 n 行命令。

  • -r --result-file=name:将输出的文本格式日志输出到指定文件。

  • -s --short-form:显示简单格式,省略掉一些信息。

  • –start-datatime=date1 --stop-datetime=date2:指定日期间隔内的所有日志

  • –start-position=pos1 --stop-position=pos2:指定位置间隔内的所有日志


dump
命令介绍

mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移,备份内容包含创建表,及插入表的 SQL 语句

mysqldump [options] db_name [tables]
mysqldump [options] --database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A

连接选项:

  • -u --user=name:指定用户名
  • -p --password[=name]:指定密码
  • -h --host=name:指定服务器 IP 或域名
  • -P --port=#:指定连接端口

输出内容选项:

  • –add-drop-database:在每个数据库创建语句前加上 Drop database 语句
  • –add-drop-table:在每个表创建语句前加上 Drop table 语句 , 默认开启,不开启 (–skip-add-drop-table)
  • -n --no-create-db:不包含数据库的创建语句
  • -t --no-create-info:不包含数据表的创建语句
  • -d --no-data:不包含数据
  • -T, --tab=name:自动生成两个文件:一个 .sql 文件,创建表结构的语句;一个 .txt 文件,数据文件,相当于 select into outfile

示例:

mysqldump -uroot -p2143 db01 tb_book --add-drop-database --add-drop-table > a
mysqldump -uroot -p2143 -T /tmp test city

数据备份

命令行方式:

  • 备份命令:mysqldump -u root -p 数据库名称 > 文件保存路径
  • 恢复
    1. 登录MySQL数据库:mysql -u root p
    2. 删除已经备份的数据库
    3. 重新创建与备份数据库名称相同的数据库
    4. 使用该数据库
    5. 导入文件执行:source 备份文件全路径

更多方式参考:https://time.geekbang.org/column/article/81925

图形化界面:

  • 备份

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rh5dfWIi-1686862213560)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/图形化界面备份.png)]

  • 恢复

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQUK9jtg-1686862213560)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/图形化界面恢复.png)]


import

mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件

mysqlimport [options]  db_name  textfile1  [textfile2...]

示例:

mysqlimport -uroot -p2143 test /tmp/city.txt

导入 sql 文件,可以使用 MySQL 中的 source 指令 :

source 文件全路径

show

mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引

mysqlshow [options] [db_name [table_name [col_name]]]
  • –count:显示数据库及表的统计信息(数据库,表 均可以不指定)

  • -i:显示指定数据库或者指定表的状态信息

示例:

#查询每个数据库的表的数量及表中记录的数量
mysqlshow -uroot -p1234 --count
#查询test库中每个表中的字段书,及行数
mysqlshow -uroot -p1234 test --count
#查询test库中book表的详细情况
mysqlshow -uroot -p1234 test book --count

单表操作

SQL

  • SQL

    • Structured Query Language:结构化查询语言
    • 定义了操作所有关系型数据库的规则,每种数据库操作的方式可能会存在不一样的地方,称为“方言”
  • SQL 通用语法

    • SQL 语句可以单行或多行书写,以分号结尾
    • 可使用空格和缩进来增强语句的可读性。
    • MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写
    • 数据库的注释:
      • 单行注释:-- 注释内容 #注释内容(MySQL 特有)
      • 多行注释:/* 注释内容 */
  • SQL 分类

    • DDL(Data Definition Language)数据定义语言

      • 用来定义数据库对象:数据库,表,列等。关键字:create、drop,、alter 等
    • DML(Data Manipulation Language)数据操作语言

      • 用来对数据库中表的数据进行增删改。关键字:insert、delete、update 等
    • DQL(Data Query Language)数据查询语言

      • 用来查询数据库中表的记录(数据)。关键字:select、where 等
    • DCL(Data Control Language)数据控制语言

      • 用来定义数据库的访问权限和安全级别,及创建用户。关键字:grant, revoke等

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1bG3xD5-1686862213561)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-SQL分类.png)]


DDL

数据库
  • R(Retrieve):查询

    • 查询所有数据库:

      SHOW DATABASES;
      
    • 查询某个数据库的创建语句

      SHOW CREATE DATABASE 数据库名称;  -- 标准语法
      
      SHOW CREATE DATABASE mysql;     -- 查看mysql数据库的创建格式
      
  • C(Create):创建

    • 创建数据库

      CREATE DATABASE 数据库名称;-- 标准语法
      
      CREATE DATABASE db1;     -- 创建db1数据库
      
    • 创建数据库(判断,如果不存在则创建)

      CREATE DATABASE IF NOT EXISTS 数据库名称;
      
    • 创建数据库,并指定字符集

      CREATE DATABASE 数据库名称 CHARACTER SET 字符集名称;
      
    • 例如:创建db4数据库、如果不存在则创建,指定字符集为gbk

      -- 创建db4数据库、如果不存在则创建,指定字符集为gbk
      CREATE DATABASE IF NOT EXISTS db4 CHARACTER SET gbk;
      
      -- 查看db4数据库的字符集
      SHOW CREATE DATABASE db4;
      
  • U(Update):修改

    • 修改数据库的字符集

      ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称;
      
    • 常用字符集:

      --查询所有支持的字符集
      SHOW CHARSET;
      --查看所有支持的校对规则
      SHOW COLLATION;
      
      -- 字符集: utf8,latinI,GBK,,GBK是utf8的子集
      -- 校对规则: ci 大小定不敏感,cs或bin大小写敏感
      
  • D(Delete):删除

    • 删除数据库:

      DROP DATABASE 数据库名称;
      
    • 删除数据库(判断,如果存在则删除):

      DROP DATABASE IF EXISTS 数据库名称;
      
  • 使用数据库:

    • 查询当前正在使用的数据库名称

      SELECT DATABASE();
      
    • 使用数据库

      USE 数据库名称; -- 标准语法
      USE db4;	   -- 使用db4数据库
      

数据表
  • R(Retrieve):查询

    • 查询数据库中所有的数据表

      USE mysql;-- 使用mysql数据库
      
      SHOW TABLES;-- 查询库中所有的表
      
    • 查询表结构

    DESC 表名;

    
    - 查询表字符集
    
    ```mysql
    SHOW TABLE STATUS FROM 库名 LIKE '表名';
    
  • C(Create):创建

    • 创建数据表

      CREATE TABLE 表名(
          列名1 数据类型1,
          列名2 数据类型2,
          ....
          列名n 数据类型n
      );
      -- 注意:最后一列,不需要加逗号
      
    • 复制表

      CREATE TABLE 表名 LIKE 被复制的表名;  -- 标准语法
      
      CREATE TABLE product2 LIKE product; -- 复制product表到product2表
      
    • 数据类型

      数据类型 说明
      INT 整数类型
      DOUBLE 小数类型
      DATE 日期,只包含年月日:yyyy-MM-dd
      DATETIME 日期,包含年月日时分秒:yyyy-MM-dd HH:mm:ss
      TIMESTAMP 时间戳类型,包含年月日时分秒:yyyy-MM-dd HH:mm:ss
      如果不给这个字段赋值或赋值为 NULL,则默认使用当前的系统时间
      CHAR 字符串,定长类型
      VARCHAR 字符串,变长类型
      name varchar(20) 代表姓名最大 20 个字符:zhangsan 8 个字符,张三 2 个字符

      INT(n):n 代表位数

      • 3:int(9)显示结果为 000000010
      • 3:int(3)显示结果为 010

      varchar(n):n 表示的是字符数

    • 例如:

      -- 使用db3数据库
      USE db3;
      
      -- 创建一个product商品表
      CREATE TABLE product(
      	id INT,				-- 商品编号
      	NAME VARCHAR(30),	-- 商品名称
      	price DOUBLE,		-- 商品价格
      	stock INT,			-- 商品库存
      	insert_time DATE    -- 上架时间
      );
      

  • U(Update):修改

    • 修改表名

      ALTER TABLE 表名 RENAME TO 新的表名;
      
    • 修改表的字符集

    ALTER TABLE 表名 CHARACTER SET 字符集名称;

    
    - 添加一列
    
    ```mysql
    ALTER TABLE 表名 ADD 列名 数据类型;
    
    • 修改列数据类型

      ALTER TABLE 表名 MODIFY 列名 新数据类型;
      
    • 修改列名称和数据类型

      ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
      
    • 删除列

      ALTER TABLE 表名 DROP 列名;
      
  • D(Delete):删除

    • 删除数据表

      DROP TABLE 表名;
      
    • 删除数据表(判断,如果存在则删除)

      DROP TABLE IF EXISTS 表名;
      

DML

INSERT
  • 新增表数据

    • 新增格式 1:给指定列添加数据

      INSERT INTO 表名(列名1,列名2...) VALUES (值1,值2...);
      
    • 新增格式 2:默认给全部列添加数据

      INSERT INTO 表名 VALUES (值1,值2,值3,...);
      
    • 新增格式 3:批量添加数据

      -- 给指定列批量添加数据
      INSERT INTO 表名(列名1,列名2,...) VALUES (值1,值2,...),(值1,值2,...)...;
      
      -- 默认给所有列批量添加数据 
      INSERT INTO 表名 VALUES (值1,值2,值3,...),(值1,值2,值3,...)...;
      
  • 字符串拼接

    CONCAT(string1,string2,'',...)
    
  • 注意事项

    • 列名和值的数量以及数据类型要对应
    • 除了数字类型,其他数据类型的数据都需要加引号(单引双引都可以,推荐单引)

UPDATE
  • 修改表数据语法

    • 标准语法

      UPDATE 表名 SET 列名1 = 值1,列名2 = 值2,... [where 条件];
      
    • 修改电视的价格为1800、库存为36

      UPDATE product SET price=1800,stock=36 WHERE NAME='电视';
      SELECT * FROM product;-- 查看所有商品信息
      
  • 注意事项

    • 修改语句中必须加条件
    • 如果不加条件,则将所有数据都修改

DELETE
  • 删除表数据语法

    DELETE FROM 表名 [WHERE 条件];
    
  • 注意事项

    • 删除语句中必须加条件
    • 如果不加条件,则将所有数据删除


DQL

查询语法

数据库查询遵循条件在前的原则

SELECT DISTINCT
	<select list>
FROM
	<left_table> <join_type>
JOIN
	<right_table> ON <join_condition>	-- 连接查询在多表查询部分详解
WHERE
	<where_condition>
GROUP BY
	<group_by_list>
HAVING
	<having_condition>
ORDER BY
	<order_by_condition>
LIMIT
	<limit_params>

执行顺序:

FROM	<left_table>

ON 		<join_condition>

<join_type>		JOIN	<right_table>

WHERE		<where_condition>

GROUP BY 	<group_by_list>

HAVING		<having_condition>

SELECT DISTINCT		<select list>

ORDER BY	<order_by_condition>

LIMIT		<limit_params>

查询全部
  • 查询全部的表数据

    -- 标准语法
    SELECT * FROM 表名;
    
    -- 查询product表所有数据(常用)
    SELECT * FROM product;
    
  • 查询指定字段的表数据

    SELECT 列名1,列名2,... FROM 表名;
    
  • 去除重复查询:只有值全部重复的才可以去除,需要创建临时表辅助查询

    SELECT DISTINCT 列名1,列名2,... FROM 表名;
    
  • 计算列的值(四则运算)

    SELECT 列名1 运算符(+ - * /) 列名2 FROM 表名;
    
    /*如果某一列值为null,可以进行替换
    	ifnull(表达式1,表达式2)
    	表达式1:想替换的列
    	表达式2:想替换的值*/
    

    例如:

    -- 查询商品名称和库存,库存数量在原有基础上加10
    SELECT NAME,stock+10 FROM product;
    
    -- 查询商品名称和库存,库存数量在原有基础上加10。进行null值判断
    SELECT NAME,IFNULL(stock,0)+10 FROM product;
    
  • 起别名

    SELECT 列名1,列名2,... AS 别名 FROM 表名;
    

    例如:

    -- 查询商品名称和库存,库存数量在原有基础上加10。进行null值判断,起别名为getSum,AS可以省略。
    SELECT NAME,IFNULL(stock,0)+10 AS getsum FROM product;
    SELECT NAME,IFNULL(stock,0)+10 getsum FROM product;
    

条件查询
  • 条件查询语法

    SELECT 列名 FROM 表名 WHERE 条件;
    
  • 条件分类

    符号 功能
    > 大于
    < 小于
    >= 大于等于
    <= 小于等于
    = 等于
    <> 或 != 不等于
    BETWEEN … AND … 在某个范围之内(都包含)
    IN(…) 多选一
    LIKE 模糊查询:_单个任意字符、%任意个字符、[] 匹配集合内的字符
    LIKE '[^AB]%' :不以 A 和 B 开头的任意文本
    IS NULL 是NULL
    IS NOT NULL 不是NULL
    AND 或 && 并且
    OR 或 || 或者
    NOT 或 ! 非,不是
    UNION 对两个结果集进行并集操作并进行去重,同时进行默认规则的排序
    UNION ALL 对两个结果集进行并集操作不进行去重,不进行排序
  • 例如:

    -- 查询库存大于20的商品信息
    SELECT * FROM product WHERE stock > 20;
    
    -- 查询品牌为华为的商品信息
    SELECT * FROM product WHERE brand='华为';
    
    -- 查询金额在4000 ~ 6000之间的商品信息
    SELECT * FROM product WHERE price >= 4000 AND price <= 6000;
    SELECT * FROM product WHERE price BETWEEN 4000 AND 6000;
    
    -- 查询库存为14、30、23的商品信息
    SELECT * FROM product WHERE stock=14 OR stock=30 OR stock=23;
    SELECT * FROM product WHERE stock IN(14,30,23);
    
    -- 查询库存为null的商品信息
    SELECT * FROM product WHERE stock IS NULL;
    -- 查询库存不为null的商品信息
    SELECT * FROM product WHERE stock IS NOT NULL;
    
    -- 查询名称以'小米'为开头的商品信息
    SELECT * FROM product WHERE NAME LIKE '小米%';
    
    -- 查询名称第二个字是'为'的商品信息
    SELECT * FROM product WHERE NAME LIKE '_为%';
    
    -- 查询名称为四个字符的商品信息 4个下划线
    SELECT * FROM product WHERE NAME LIKE '____';
    
    -- 查询名称中包含电脑的商品信息
    SELECT * FROM product WHERE NAME LIKE '%电脑%';
    

函数查询
聚合函数

聚合函数:将一列数据作为一个整体,进行纵向的计算

  • 聚合函数语法

    SELECT 函数名(列名) FROM 表名 [WHERE 条件]
    
  • 聚合函数分类

    函数名 功能
    COUNT(列名) 统计数量(一般选用不为 null 的列)
    MAX(列名) 最大值
    MIN(列名) 最小值
    SUM(列名) 求和
    AVG(列名) 平均值(会忽略 null 行)
  • 例如

    -- 计算product表中总记录条数 7
    SELECT COUNT(*) FROM product;
    
    -- 获取最高价格
    SELECT MAX(price) FROM product;
    -- 获取最高价格的商品名称
    SELECT NAME,price FROM product WHERE price = (SELECT MAX(price) FROM product);
    
    -- 获取最低库存
    SELECT MIN(stock) FROM product;
    -- 获取最低库存的商品名称
    SELECT NAME,stock FROM product WHERE stock = (SELECT MIN(stock) FROM product);
    
    -- 获取总库存数量
    SELECT SUM(stock) FROM product;
    -- 获取品牌为小米的平均商品价格
    SELECT AVG(price) FROM product WHERE brand='小米';
    

文本函数

CONCAT():用于连接两个字段

SELECT CONCAT(TRIM(col1), '(', TRIM(col2), ')') AS concat_col FROM mytable
-- 许多数据库会使用空格把一个值填充为列宽,连接的结果出现一些不必要的空格,使用TRIM()可以去除首尾空格
函数名称 作 用
LENGTH 计算字符串长度函数,返回字符串的字节长度
CONCAT 合并字符串函数,返回结果为连接参数产生的字符串,参数可以使一个或多个
INSERT 替换字符串函数
LOWER 将字符串中的字母转换为小写
UPPER 将字符串中的字母转换为大写
LEFT 从左侧字截取符串,返回字符串左边的若干个字符
RIGHT 从右侧字截取符串,返回字符串右边的若干个字符
TRIM 删除字符串左右两侧的空格
REPLACE 字符串替换函数,返回替换后的新字符串
SUBSTRING 截取字符串,返回从指定位置开始的指定长度的字符换
REVERSE 字符串反转(逆序)函数,返回与原始字符串顺序相反的字符串

数字函数
函数名称 作 用
ABS 求绝对值
SQRT 求二次方根
MOD 求余数
CEIL 和 CEILING 两个函数功能相同,都是返回不小于参数的最小整数,即向上取整
FLOOR 向下取整,返回值转化为一个BIGINT
RAND 生成一个0~1之间的随机数,传入整数参数是,用来产生重复序列
ROUND 对所传参数进行四舍五入
SIGN 返回参数的符号
POW 和 POWER 两个函数的功能相同,都是所传参数的次方的结果值
SIN 求正弦值
ASIN 求反正弦值,与函数 SIN 互为反函数
COS 求余弦值
ACOS 求反余弦值,与函数 COS 互为反函数
TAN 求正切值
ATAN 求反正切值,与函数 TAN 互为反函数
COT 求余切值

日期函数
函数名称 作 用
CURDATE 和 CURRENT_DATE 两个函数作用相同,返回当前系统的日期值
CURTIME 和 CURRENT_TIME 两个函数作用相同,返回当前系统的时间值
NOW 和 SYSDATE 两个函数作用相同,返回当前系统的日期和时间值
MONTH 获取指定日期中的月份
MONTHNAME 获取指定日期中的月份英文名称
DAYNAME 获取指定曰期对应的星期几的英文名称
DAYOFWEEK 获取指定日期对应的一周的索引位置值
WEEK 获取指定日期是一年中的第几周,返回值的范围是否为 0〜52 或 1〜53
DAYOFYEAR 获取指定曰期是一年中的第几天,返回值范围是1~366
DAYOFMONTH 获取指定日期是一个月中是第几天,返回值范围是1~31
YEAR 获取年份,返回值范围是 1970〜2069
TIME_TO_SEC 将时间参数转换为秒数
SEC_TO_TIME 将秒数转换为时间,与TIME_TO_SEC 互为反函数
DATE_ADD 和 ADDDATE 两个函数功能相同,都是向日期添加指定的时间间隔
DATE_SUB 和 SUBDATE 两个函数功能相同,都是向日期减去指定的时间间隔
ADDTIME 时间加法运算,在原始时间上添加指定的时间
SUBTIME 时间减法运算,在原始时间上减去指定的时间
DATEDIFF 获取两个日期之间间隔,返回参数 1 减去参数 2 的值
DATE_FORMAT 格式化指定的日期,根据参数返回指定格式的值
WEEKDAY 获取指定日期在一周内的对应的工作日索引

正则查询

正则表达式(Regular Expression)是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串

SELECT * FROM emp WHERE name REGEXP '^T';	-- 匹配以T开头的name值
SELECT * FROM emp WHERE name REGEXP '2$';	-- 匹配以2结尾的name值
SELECT * FROM emp WHERE name REGEXP '[uvw]';-- 匹配包含 uvw 的name值
符号 含义
^ 在字符串开始处进行匹配
$ 在字符串末尾处进行匹配
. 匹配任意单个字符, 包括换行符
[…] 匹配出括号内的任意字符
[^…] 匹配不出括号内的任意字符
a* 匹配零个或者多个a(包括空串)
a+ 匹配一个或者多个a(不包括空串)
a? 匹配零个或者一个a
a1|a2 匹配a1或a2
a(m) 匹配m个a
a(m,) 至少匹配m个a
a(m,n) 匹配m个a 到 n个a
a(,n) 匹配0到n个a
(…) 将模式元素组成单一元素

排序查询
  • 排序查询语法

    SELECT 列名 FROM 表名 [WHERE 条件] ORDER BY 列名1 排序方式1,列名2 排序方式2;
    
  • 排序方式

    ASC:升序
    DESC:降序
    

    注意:多个排序条件,当前边的条件值一样时,才会判断第二条件

  • 例如

    -- 按照库存升序排序
    SELECT * FROM product ORDER BY stock ASC;
    
    -- 查询名称中包含手机的商品信息。按照金额降序排序
    SELECT * FROM product WHERE NAME LIKE '%手机%' ORDER BY price DESC;
    
    -- 按照金额升序排序,如果金额相同,按照库存降序排列
    SELECT * FROM product ORDER BY price ASC,stock DESC;
    

分组查询

分组查询会进行去重

  • 分组查询语法

    SELECT 列名 FROM 表名 [WHERE 条件] GROUP BY 分组列名 [HAVING 分组后条件过滤] [ORDER BY 排序列名 排序方式];
    

    WHERE 过滤行,HAVING 过滤分组,行过滤应当先于分组过滤

    分组规定:

    • GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前
    • NULL 的行会单独分为一组
    • 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型
  • 例如

    -- 按照品牌分组,获取每组商品的总金额
    SELECT brand,SUM(price) FROM product GROUP BY brand;
    
    -- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额
    SELECT brand,SUM(price) FROM product WHERE price > 4000 GROUP BY brand;
    
    -- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的
    SELECT brand,SUM(price) AS getSum FROM product WHERE price > 4000 GROUP BY brand HAVING getSum > 7000;
    
    -- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的、并按照总金额的降序排列
    SELECT brand,SUM(price) AS getSum FROM product WHERE price > 4000 GROUP BY brand HAVING getSum > 7000 ORDER BY getSum DESC;
    

分页查询
  • 分页查询语法

    SELECT 列名 FROM 表名 [WHERE 条件] GROUP BY 分组列名 [HAVING 分组后条件过滤] [ORDER BY 排序列名 排序方式] LIMIT 开始索引,查询条数;
    
  • 公式:开始索引 = (当前页码-1) * 每页显示的条数

  • 例如

    SELECT * FROM product LIMIT 0,2;  -- 第一页 开始索引=(1-1) * 2
    SELECT * FROM product LIMIT 2,2;  -- 第二页 开始索引=(2-1) * 2
    SELECT * FROM product LIMIT 4,2;  -- 第三页 开始索引=(3-1) * 2
    SELECT * FROM product LIMIT 6,2;  -- 第四页 开始索引=(4-1) * 2
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-piVOy5sJ-1686862213561)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-DQL分页查询图解.png)]


多表操作

约束分类

约束介绍

约束:对表中的数据进行限定,保证数据的正确性、有效性、完整性

约束的分类:

约束 说明
PRIMARY KEY 主键约束
PRIMARY KEY AUTO_INCREMENT 主键、自动增长
UNIQUE 唯一约束
NOT NULL 非空约束
FOREIGN KEY 外键约束
FOREIGN KEY ON UPDATE CASCADE 外键级联更新
FOREIGN KEY ON DELETE CASCADE 外键级联删除

主键约束
  • 主键约束特点:

    • 主键约束默认包含非空和唯一两个功能
    • 一张表只能有一个主键
    • 主键一般用于表中数据的唯一标识
  • 建表时添加主键约束

    CREATE TABLE 表名(
    	列名 数据类型 PRIMARY KEY,
        列名 数据类型,
        ...
    );
    
  • 删除主键约束

    ALTER TABLE 表名 DROP PRIMARY KEY;
    
  • 建表后单独添加主键约束

    ALTER TABLE 表名 MODIFY 列名 数据类型 PRIMARY KEY;
    
  • 例如

    -- 创建student表
    CREATE TABLE student(
    	id INT PRIMARY KEY  -- 给id添加主键约束
    );
    
    -- 添加数据
    INSERT INTO student VALUES (1),(2);
    -- 主键默认唯一,添加重复数据,会报错
    INSERT INTO student VALUES (2);
    -- 主键默认非空,不能添加null的数据
    INSERT INTO student VALUES (NULL);
    

主键自增

主键自增约束可以为空,并自动增长。删除某条数据不影响自增的下一个数值,依然按照前一个值自增

  • 建表时添加主键自增约束

    CREATE TABLE 表名(
    	列名 数据类型 PRIMARY KEY AUTO_INCREMENT,
        列名 数据类型,
        ...
    );
    
  • 删除主键自增约束

    ALTER TABLE 表名 MODIFY 列名 数据类型;
    
  • 建表后单独添加主键自增约束

    ALTER TABLE 表名 MODIFY 列名 数据类型 AUTO_INCREMENT;
    
  • 例如

    -- 创建student2表
    CREATE TABLE student2(
    	id INT PRIMARY KEY AUTO_INCREMENT    -- 给id添加主键自增约束
    );
    
    -- 添加数据
    INSERT INTO student2 VALUES (1),(2);
    -- 添加null值,会自动增长
    INSERT INTO student2 VALUES (NULL),(NULL);-- 3,4
    

唯一约束

唯一约束:约束不能有重复的数据

  • 建表时添加唯一约束

    CREATE TABLE 表名(
    	列名 数据类型 UNIQUE,
        列名 数据类型,
        ...
    );
    
  • 删除唯一约束

    ALTER TABLE 表名 DROP INDEX 列名;
    
  • 建表后单独添加唯一约束

    ALTER TABLE 表名 MODIFY 列名 数据类型 UNIQUE;
    

非空约束
  • 建表时添加非空约束

    CREATE TABLE 表名(
    	列名 数据类型 NOT NULL,
        列名 数据类型,
        ...
    );
    
  • 删除非空约束

    ALTER TABLE 表名 MODIFY 列名 数据类型;
    
  • 建表后单独添加非空约束

    ALTER TABLE 表名 MODIFY 列名 数据类型 NOT NULL;
    

外键约束

外键约束:让表和表之间产生关系,从而保证数据的准确性

  • 建表时添加外键约束

    CREATE TABLE 表名(
    	列名 数据类型 约束,
        ...
        CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名)
    );
    
  • 删除外键约束

    ALTER TABLE 表名 DROP FOREIGN KEY 外键名;
    
  • 建表后单独添加外键约束

    ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名);
    
  • 例如

    -- 创建user用户表
    CREATE TABLE USER(
    	id INT PRIMARY KEY AUTO_INCREMENT,    -- id
    	name VARCHAR(20) NOT NULL             -- 姓名
    );
    -- 添加用户数据
    INSERT INTO USER VALUES (NULL,'张三'),(NULL,'李四'),(NULL,'王五');
    
    -- 创建orderlist订单表
    CREATE TABLE orderlist(
    	id INT PRIMARY KEY AUTO_INCREMENT,    -- id
    	number VARCHAR(20) NOT NULL,          -- 订单编号
    	uid INT,                              -- 订单所属用户
    	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)   -- 添加外键约束
    );
    -- 添加订单数据
    INSERT INTO orderlist VALUES (NULL,'hm001',1),(NULL,'hm002',1),
    (NULL,'hm003',2),(NULL,'hm004',2),
    (NULL,'hm005',3),(NULL,'hm006',3);
    
    -- 添加一个订单,但是没有所属用户。无法添加
    INSERT INTO orderlist VALUES (NULL,'hm007',8);
    -- 删除王五这个用户,但是订单表中王五还有很多个订单呢。无法删除
    DELETE FROM USER WHERE NAME='王五';
    

外键级联

级联操作:当把主表中的数据进行删除或更新时,从表中有关联的数据的相应操作,包括 RESTRICT、CASCADE、SET NULL 和 NO ACTION

  • RESTRICT 和 NO ACTION相同, 是指限制在子表有关联记录的情况下, 父表不能更新

  • CASCADE 表示父表在更新或者删除时,更新或者删除子表对应的记录

  • SET NULL 则表示父表在更新或者删除的时候,子表的对应字段被SET NULL

级联操作:

  • 添加级联更新

    ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名) ON UPDATE [CASCADE | RESTRICT | SET NULL];
    
  • 添加级联删除

    ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名) ON DELETE CASCADE;
    
  • 同时添加级联更新和级联删除

    ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名) ON UPDATE CASCADE ON DELETE CASCADE;
    

多表设计

一对一

多表:有多张数据表,而表与表之间有一定的关联关系,通过外键约束实现,分为一对一、一对多、多对多三类

举例:人和身份证

实现原则:在任意一个表建立外键,去关联另外一个表的主键

-- 创建person表
CREATE TABLE person(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)                        -- 姓名
);
-- 添加数据
INSERT INTO person VALUES (NULL,'张三'),(NULL,'李四');

-- 创建card表
CREATE TABLE card(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	number VARCHAR(20) UNIQUE NOT NULL,	-- 身份证号
	pid INT UNIQUE,                         -- 外键列
	CONSTRAINT cp_fk1 FOREIGN KEY (pid) REFERENCES person(id)
);
-- 添加数据
INSERT INTO card VALUES (NULL,'12345',1),(NULL,'56789',2);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wBlATlgY-1686862213562)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/多表设计一对一.png)]


一对多

举例:用户和订单、商品分类和商品

实现原则:在多的一方,建立外键约束,来关联一的一方主键

-- 创建user表
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)                        -- 姓名
);
-- 添加数据
INSERT INTO USER VALUES (NULL,'张三'),(NULL,'李四');

-- 创建orderlist表
CREATE TABLE orderlist(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	number VARCHAR(20),                     -- 订单编号
	uid INT,				-- 外键列
	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);
-- 添加数据
INSERT INTO orderlist VALUES (NULL,'hm001',1),(NULL,'hm002',1),(NULL,'hm003',2),(NULL,'hm004',2);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKxDKHR5-1686862213563)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/多表设计一对多.png)]


多对多

举例:学生和课程。一个学生可以选择多个课程,一个课程也可以被多个学生选择

实现原则:借助第三张表中间表,中间表至少包含两个列,这两个列作为中间表的外键,分别关联两张表的主键

-- 创建student表
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)			-- 学生姓名
);
-- 添加数据
INSERT INTO student VALUES (NULL,'张三'),(NULL,'李四');

-- 创建course表
CREATE TABLE course(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(10)			-- 课程名称
);
-- 添加数据
INSERT INTO course VALUES (NULL,'语文'),(NULL,'数学');

-- 创建中间表
CREATE TABLE stu_course(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	sid INT,  -- 用于和student表中的id进行外键关联
	cid INT,  -- 用于和course表中的id进行外键关联
	CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id), -- 添加外键约束
	CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id)   -- 添加外键约束
);
-- 添加数据
INSERT INTO stu_course VALUES (NULL,1,1),(NULL,1,2),(NULL,2,1),(NULL,2,2);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4awrgwGs-1686862213563)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/多表设计多对多.png)]


连接查询

内外连接
内连接

连接查询的是两张表有交集的部分数据,两张表分为驱动表和被驱动表,如果结果集中的每条记录都是两个表相互匹配的组合,则称这样的结果集为笛卡尔积

内连接查询,若驱动表中的记录在被驱动表中找不到匹配的记录时,则该记录不会加到最后的结果集

  • 显式内连接:

    SELECT 列名 FROM 表名1 [INNER] JOIN 表名2 ON 条件;
    
  • 隐式内连接:内连接中 WHERE 子句和 ON 子句是等价的

    SELECT 列名 FROM 表名1,表名2 WHERE 条件;
    

STRAIGHT_JOIN与 JOIN 类似,只不过左表始终在右表之前读取,只适用于内连接


外连接

外连接查询,若驱动表中的记录在被驱动表中找不到匹配的记录时,则该记录也会加到最后的结果集,只是对于被驱动表中不匹配过滤条件的记录,各个字段使用 NULL 填充

应用实例:查学生成绩,也想展示出缺考的人的成绩

  • 左外连接:选择左侧的表为驱动表,查询左表的全部数据,和左右两张表有交集部分的数据

    SELECT 列名 FROM 表名1 LEFT [OUTER] JOIN 表名2 ON 条件;
    
  • 右外连接:选择右侧的表为驱动表,查询右表的全部数据,和左右两张表有交集部分的数据

    SELECT 列名 FROM 表名1 RIGHT [OUTER] JOIN 表名2 ON 条件;
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esfDYBH2-1686862213563)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/MySQL-JOIN查询图.png)]


关联查询

自关联查询:同一张表中有数据关联,可以多次查询这同一个表

  • 数据准备

    -- 创建员工表
    CREATE TABLE employee(
    	id INT PRIMARY KEY AUTO_INCREMENT,	-- 员工编号
    	NAME VARCHAR(20),					-- 员工姓名
    	mgr INT,							-- 上级编号
    	salary DOUBLE						-- 员工工资
    );
    -- 添加数据
    INSERT INTO employee VALUES (1001,'孙悟空',1005,9000.00),..,(1009,'宋江',NULL,16000.00);
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3lRk2qa0-1686862213564)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/自关联查询数据准备.png)]

  • 数据查询

    -- 查询所有员工的姓名及其直接上级的姓名,没有上级的员工也需要查询
    /*
    分析
    	员工信息 employee表
    	条件:employee.mgr = employee.id
    	查询左表的全部数据,和左右两张表有交集部分数据,左外连接
    */
    SELECT
    	e1.id,
    	e1.name,
    	e1.mgr,
    	e2.id,
    	e2.name
    FROM
    	employee e1
    LEFT OUTER JOIN
    	employee e2
    ON
    	e1.mgr = e2.id;	
    
  • 查询结果

    id		name	mgr	   id	  name
    1001	孙悟空	  1005	1005	唐僧
    1002	猪八戒	  1005	1005	唐僧
    1003	沙和尚	  1005	1005	唐僧
    1004	小白龙	  1005	1005	唐僧
    1005	唐僧	   NULL	 NULL	 NULL
    1006	武松	   1009	 1009	 宋江
    1007	李逵	   1009	 1009	 宋江
    1008	林冲	   1009	 1009	 宋江
    1009	宋江	   NULL	 NULL	 NULL
    

连接原理

Index Nested-Loop Join 算法:查询驱动表得到数据集,然后根据数据集中的每一条记录的关联字段再分别到被驱动表中查找匹配(走索引),所以驱动表只需要访问一次,被驱动表要访问多次

MySQL 将查询驱动表后得到的记录成为驱动表的扇出,连接查询的成本:单次访问驱动表的成本 + 扇出值 * 单次访问被驱动表的成本,优化器会选择成本最小的表连接顺序(确定谁是驱动表,谁是被驱动表)生成执行计划,进行连接查询,优化方式:

  • 减少驱动表的扇出(让数据量小的表来做驱动表)
  • 降低访问被驱动表的成本

说明:STRAIGHT_JOIN 是查一条驱动表,然后根据关联字段去查被驱动表,要访问多次驱动表,所以需要优化为 INL 算法

Block Nested-Loop Join 算法:一种空间换时间的优化方式,基于块的循环连接,执行连接查询前申请一块固定大小的内存作为连接缓冲区 Join Buffer,先把若干条驱动表中的扇出暂存在缓冲区,每一条被驱动表中的记录一次性的与 Buffer 中多条记录进行匹配(扫描全部数据,一条一条的匹配),因为是在内存中完成,所以速度快,并且降低了 I/O 成本

Join Buffer 可以通过参数 join_buffer_size 进行配置,默认大小是 256 KB

在成本分析时,对于很多张表的连接查询,连接顺序有非常多,MySQL 如果挨着进行遍历计算成本,会消耗很多资源

  • 提前结束某种连接顺序的成本评估:维护一个全局变量记录当前成本最小的连接方式,如果一种顺序只计算了一部分就已经超过了最小成本,可以提前结束计算

  • 系统变量 optimizer_search_depth:如果连接表的个数小于该变量,就继续穷举分析每一种连接数量,反之只对数量与 depth 值相同的表进行分析,该值越大成本分析的越精确

  • 系统变量 optimizer_prune_level:控制启发式规则的启用,这些规则就是根据以往经验指定的,不满足规则的连接顺序不分析成本


连接优化
BKA

Batched Key Access 算法是对 NLJ 算法的优化,在读取被驱动表的记录时使用顺序 IO,Extra 信息中会有 Batched Key Access 信息

使用 BKA 的表的 JOIN 过程如下:

  • 连接驱动表将满足条件的记录放入 Join Buffer,并将两表连接的字段放入一个 DYNAMIC_ARRAY ranges 中
  • 在进行表的过接过程中,会将 ranges 相关的信息传入 Buffer 中,进行被驱动表主建的查找及排序操作
  • 调用步骤 2 中产生的有序主建,顺序读取被驱动表的数据
  • 当缓冲区的数据被读完后,会重复进行步骤 2、3,直到记录被读取完

使用 BKA 优化需要设进行设置:

SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

说明:前两个参数的作用是启用 MRR,因为 BKA 算法的优化要依赖于 MRR(系统优化 → 内存优化 → Read 详解)


BNL
问题

BNL 即 Block Nested-Loop Join 算法,由于要访问多次被驱动表,会产生两个问题:

  • Join 语句多次扫描一个冷表,并且语句执行时间小于 1 秒,就会在再次扫描冷表时,把冷表的数据页移到 LRU 链表头部,导致热数据被淘汰,影响业务的正常运行

    这种情况冷表的数据量要小于整个 Buffer Pool 的 old 区域,能够完全放入 old 区,才会再次被读时加到 young,否则读取下一段时就已经把上一段淘汰

  • Join 语句在循环读磁盘和淘汰内存页,进入 old 区域的数据页很可能在 1 秒之内就被淘汰,就会导致 MySQL 实例的 Buffer Pool 在这段时间内 young 区域的数据页没有被合理地淘汰

大表 Join 操作虽然对 IO 有影响,但是在语句执行结束后对 IO 的影响随之结束。但是对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率

优化

将 BNL 算法转成 BKA 算法,优化方向:

  • 在被驱动表上建索引,这样就可以根据索引进行顺序 IO
  • 使用临时表,在临时表上建立索引,将被驱动表和临时表进行连接查询

驱动表 t1,被驱动表 t2,使用临时表的工作流程:

  • 把表 t1 中满足条件的数据放在临时表 tmp_t 中
  • 给临时表 tmp_t 的关联字段加上索引,使用 BKA 算法
  • 让表 t2 和 tmp_t 做 Join 操作(临时表是被驱动表)

补充:MySQL 8.0 支持 hash join,join_buffer 维护的不再是一个无序数组,而是一个哈希表,查询效率更高,执行效率比临时表更高


嵌套查询

查询分类

查询语句中嵌套了查询语句,将嵌套查询称为子查询,FROM 子句后面的子查询的结果集称为派生表

根据结果分类:

  • 结果是单行单列:可以将查询的结果作为另一条语句的查询条件,使用运算符判断

    SELECT 列名 FROM 表名 WHERE 列名=(SELECT 列名/聚合函数(列名) FROM 表名 [WHERE 条件]);
    
  • 结果是多行单列:可以作为条件,使用运算符 IN 或 NOT IN 进行判断

    SELECT 列名 FROM 表名 WHERE 列名 [NOT] IN (SELECT 列名 FROM 表名 [WHERE 条件]); 
    
  • 结果是多行多列:查询的结果可以作为一张虚拟表参与查询

    SELECT 列名 FROM 表名 [别名],(SELECT 列名 FROM 表名 [WHERE 条件]) [别名] [WHERE 条件];
    
    -- 查询订单表orderlist中id大于4的订单信息和所属用户USER信息
    SELECT 
    	* 
    FROM 
    	USER u,
    	(SELECT * FROM orderlist WHERE id>4) o 
    WHERE 
    	u.id=o.uid;
    

相关性分类:

  • 不相关子查询:子查询不依赖外层查询的值,可以单独运行出结果
  • 相关子查询:子查询的执行需要依赖外层查询的值

查询优化

不相关子查询的结果集会被写入一个临时表,并且在写入时去重,该过程称为物化,存储结果集的临时表称为物化表

系统变量 tmp_table_size 或者 max_heap_table_size 为表的最值

  • 小于系统变量时,内存中可以保存,会为建立基于内存的 MEMORY 存储引擎的临时表,并建立哈希索引
  • 大于任意一个系统变量时,物化表会使用基于磁盘的 InnoDB 存储引擎来保存结果集中的记录,索引类型为 B+ 树

物化后,嵌套查询就相当于外层查询的表和物化表进行内连接查询,然后经过优化器选择成本最小的表连接顺序执行查询

子查询物化会产生建立临时表的成本,但是将子查询转化为连接查询可以充分发挥优化器的作用,所以引入:半连接

  • t1 和 t2 表进行半连接,对于 t1 表中的某条记录,只需要关心在 t2 表中是否存在,而不需要关心有多少条记录与之匹配,最终结果集只保留 t1 的记录
  • 半连接只是执行子查询的一种方式,MySQL 并没有提供面向用户的半连接语法

参考书籍:https://book.douban.com/subject/35231266/


联合查询

UNION 是取这两个子查询结果的并集,并进行去重,同时进行默认规则的排序(union 是行加起来,join 是列加起来)

UNION ALL 是对两个结果集进行并集操作不进行去重,不进行排序

(select 1000 as f) union (select id from t1 order by id desc limit 2); #t1表中包含id 为 1-1000 的数据

语句的执行流程:

  • 创建一个内存临时表,这个临时表只有一个整型字段 f,并且 f 是主键字段
  • 执行第一个子查询,得到 1000 这个值,并存入临时表中
  • 执行第二个子查询,拿到第一行 id=1000,试图插入临时表中,但由于 1000 这个值已经存在于临时表了,违反了唯一性约束,所以插入失败,然后继续执行
  • 取到第二行 id=999,插入临时表成功
  • 从临时表中按行取出数据,返回结果并删除临时表,结果中包含两行数据分别是 1000 和 999

查询练习

数据准备:

-- 创建db4数据库
CREATE DATABASE db4;
-- 使用db4数据库
USE db4;

-- 创建user表
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 用户id
	NAME VARCHAR(20),					-- 用户姓名
	age INT                             -- 用户年龄
);

-- 订单表
CREATE TABLE orderlist(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 订单id
	number VARCHAR(30),					-- 订单编号
	uid INT,   							-- 外键字段
	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);

-- 商品分类表
CREATE TABLE category(
	id INT PRIMARY KEY AUTO_INCREMENT,  -- 商品分类id
	NAME VARCHAR(10)                    -- 商品分类名称
);

-- 商品表
CREATE TABLE product(
	id INT PRIMARY KEY AUTO_INCREMENT,   -- 商品id
	NAME VARCHAR(30),                    -- 商品名称
	cid INT, -- 外键字段
	CONSTRAINT cp_fk1 FOREIGN KEY (cid) REFERENCES category(id)
);

-- 中间表
CREATE TABLE us_pro(
	upid INT PRIMARY KEY AUTO_INCREMENT,  -- 中间表id
	uid INT, 							  -- 外键字段。需要和用户表的主键产生关联
	pid INT,							  -- 外键字段。需要和商品表的主键产生关联
	CONSTRAINT up_fk1 FOREIGN KEY (uid) REFERENCES USER(id),
	CONSTRAINT up_fk2 FOREIGN KEY (pid) REFERENCES product(id)
);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xaGbiuaG-1686862213564)(https://seazean.oss-cn-beijing.aliyuncs.com/img/DB/多表练习架构设计.png)]

数据查询:

  1. 查询用户的编号、姓名、年龄、订单编号

    数据:用户的编号、姓名、年龄在 user 表,订单编号在 orderlist 表

    条件:user.id = orderlist.uid

    SELECT
    	u.*,
    	o.number
    FROM
    	USER u,
    	orderlist o
    WHERE
    	u.id = o.uid;
    
  2. 查询所有的用户,显示用户的编号、姓名、年龄、订单编号。

    SELECT
    	u.*,
    	o.number
    FROM
    	USER u
    LEFT OUTER JOIN
    	orderlist o
    ON
    	u.id = o.uid;
    
  3. 查询用户年龄大于 23 岁的信息,显示用户的编号、姓名、年龄、订单编号

    SELECT
    	u.*,
    	o.number
    FROM
    	USER u,
    	orderlist o
    WHERE
    	u.id = o.uid
    	AND
    	u.age > 23;
    
    SELECT
    	u.*,
    	o.number
    FROM
    	(SELECT * FROM USER WHERE age > 23) u,-- 嵌套查询
    	orderlist o
    WHERE
    	u.id = o.uid;
    
  4. 查询张三和李四用户的信息,显示用户的编号、姓名、年龄、订单编号。

    SELECT
    	u.*,
    	o.number
    FROM
    	USER u,
    	orderlist o
    WHERE
    	u.id=o.uid
    	AND
    	u.name IN ('张三','李四');
    
  5. 查询所有的用户和该用户能查看的所有的商品,显示用户的编号、姓名、年龄、商品名称

    数据:用户的编号、姓名、年龄在 user 表,商品名称在 product 表,中间表 us_pro

    条件:us_pro.uid = user.id AND us_pro.pid = product.id

    SELECT
    	u.id,
    	u.name,
    	u.age,
    	p.name
    FROM
    	USER u,
    	product p,
    	us_pro up
    WHERE
    	up.uid = u.id
    	AND
    	up.pid=p.id;
    
  6. 查询张三和李四这两个用户可以看到的商品,显示用户的编号、姓名、年龄、商品名称。

    SELECT
    	u.id,
    	u.name,
    	u.age,
    	p.name
    FROM
    	USER u,
    	product p,
    	us_pro up
    WHERE
    	up.uid=u.id
    	AND
    	up.pid=p.id
    	AND
    	u.name IN ('张三','李四');
    

高级结构

视图

基本介绍

视图概念:视图是一种虚拟存在的数据表,这个虚拟的表并不在数据库中实际存在

本质:将一条 SELECT 查询语句的结果封装到了一个虚拟表中,所以在创建视图的时候,工作重心要放在这条 SELECT 查询语句上

作用:将一些比较复杂的查询语句的结果,封装到一个虚拟表中,再有相同查询需求时,直接查询该虚拟表

优点:

  • 简单:使用视图的用户不需要关心表的结构、关联条件和筛选条件,因为虚拟表中已经是过滤好的结果集

  • 安全:使用视图的用户只能访问查询的结果集,对表的权限管理并不能限制到某个行某个列

  • 数据独立,一旦视图的结构确定,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响


视图创建
  • 创建视图

    CREATE [OR REPLACE] 
    VIEW 视图名称 [(列名列表)] 
    AS 查询语句
    [WITH [CASCADED | LOCAL] CHECK OPTION];
    

    WITH [CASCADED | LOCAL] CHECK OPTION 决定了是否允许更新数据使记录不再满足视图的条件:

    • LOCAL:只要满足本视图的条件就可以更新
    • CASCADED:必须满足所有针对该视图的所有视图的条件才可以更新, 默认值
  • 例如

    -- 数据准备 city
    id	NAME	cid
    1	深圳	 	1
    2	上海		1
    3	纽约		2
    4	莫斯科	    3
    
    -- 数据准备 country
    id	NAME
    1	中国
    2	美国
    3	俄罗斯
    
    -- 创建city_country视图,保存城市和国家的信息(使用指定列名)
    CREATE 
    VIEW 
    	city_country (city_id,city_name,country_name)
    AS
        SELECT
            c1.id,
            c1.name,
            c2.name
        FROM
            city c1,
            country c2
        WHERE
            c1.cid=c2.id;
    

视图查询
  • 查询所有数据表,视图也会查询出来

    SHOW TABLES;
    SHOW TABLE STATUS [\G];
    
  • 查询视图

    SELECT * FROM 视图名称;
    
  • 查询某个视图创建

    SHOW CREATE VIEW 视图名称;
    

视图修改

视图表数据修改,会自动修改源表中的数据,因为更新的是视图中的基表中的数据

  • 修改视图表中的数据

    UPDATE 视图名称 SET 列名 = 值 WHERE 条件;
    
  • 修改视图的结构

    ALTER [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
    VIEW 视图名称 [(列名列表)] 
    AS 查询语句
    [WITH [CASCADED | LOCAL] CHECK OPTION]
    
    -- 将视图中的country_name修改为name
    ALTER 
    VIEW 
    	city_country (city_id,city_name,name) 
    AS
        SELECT
            c1.id,
            c1.name,
            c2.name
        FROM
            city c1,
            country c2
        WHERE
            c1.cid=c2.id;
    

视图删除
  • 删除视图

    DROP VIEW 视图名称;
    
  • 如果存在则删除

    DROP VIEW IF EXISTS 视图名称;
    

存储过程

基本介绍

存储过程和函数:存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合

存储过程和函数的好处:

  • 提高代码的复用性
  • 减少数据在数据库和应用服务器之间的传输,提高传输效率
  • 减少代码层面的业务处理
  • 一次编译永久有效

存储过程和函数的区别:

  • 存储函数必须有返回值
  • 存储过程可以没有返回值

基本操作

DELIMITER:

  • DELIMITER 关键字用来声明 sql 语句的分隔符,告诉 MySQL 该段命令已经结束

  • MySQL 语句默认的分隔符是分号,但是有时需要一条功能 sql 语句中包含分号,但是并不作为结束标识,这时使用 DELIMITER 来指定分隔符:

    DELIMITER 分隔符
    

存储过程的创建调用查看和删除:

  • 创建存储过程

    -- 修改分隔符为$
    DELIMITER $
    
    -- 标准语法
    CREATE PROCEDURE 存储过程名称(参数...)
    BEGIN
    	sql语句;
    END$
    
    -- 修改分隔符为分号
    DELIMITER ;
    
  • 调用存储过程

    CALL 存储过程名称(实际参数);
    
  • 查看存储过程

    SELECT * FROM mysql.proc WHERE db='数据库名称';
    
  • 删除存储过程

    DROP PROCEDURE [IF EXISTS] 存储过程名称;
    

练习:

  • 数据准备

    id	NAME	age		gender	score
    1	张三		23		男		95
    2	李四		24		男		98
    3	王五		25		女		100
    4	赵六		26		女		90
    
  • 创建 stu_group() 存储过程,封装分组查询总成绩,并按照总成绩升序排序的功能

    DELIMITER $
    
    CREATE PROCEDURE stu_group()
    BEGIN
    	SELECT gender,SUM(score) getSum FROM student GROUP BY gender ORDER BY getSum ASC; 
    END$
    
    DELIMITER ;
    
    -- 调用存储过程
    CALL stu_group();
    -- 删除存储过程
    DROP PROCEDURE IF EXISTS stu_group;
    

存储语法
变量使用

存储过程是可以进行编程的,意味着可以使用变量、表达式、条件控制语句等,来完成比较复杂的功能

  • 定义变量:DECLARE 定义的是局部变量,只能用在 BEGIN END 范围之内

    DECLARE 变量名 数据类型 [DEFAULT 默认值];
    
  • 变量的赋值

    SET 变量名 = 变量值;
    SELECT 列名 INTO 变量名 FROM 表名 [WHERE 条件];
    
  • 数据准备:表 student

    id	NAME	age		gender	score
    1	张三		23		男		95
    2	李四		24		男		98
    3	王五		25		女		100
    4	赵六		26		女		90
    
  • 定义两个 int 变量,用于存储男女同学的总分数

    DELIMITER $
    CREATE PROCEDURE pro_test3()
    BEGIN
    	-- 定义两个变量
    	DECLARE men,women INT;
    	-- 查询男同学的总分数,为men赋值
    	SELECT SUM(score) INTO men FROM student WHERE gender='男';
    	-- 查询女同学的总分数,为women赋值
    	SELECT SUM(score) INTO women FROM student WHERE gender='女';
    	-- 使用变量
    	SELECT men,women;
    END$
    DELIMITER ;
    -- 调用存储过程
    CALL pro_test3();
    

IF语句
  • if 语句标准语法

    IF 判断条件1 THEN 执行的sql语句1;
    [ELSEIF 判断条件2 THEN 执行的sql语句2;]
    ...
    [ELSE 执行的sql语句n;]
    END IF;
    
  • 数据准备:表 student

    id	NAME	age		gender	score
    1	张三		23		男		95
    2	李四		24		男		98
    3	王五		25		女		100
    4	赵六		26		女		90
    
  • 根据总成绩判断:全班 380 分及以上学习优秀、320 ~ 380 学习良好、320 以下学习一般

    DELIMITER $
    CREATE PROCEDURE pro_test4()
    BEGIN
    	DECLARE total INT;							-- 定义总分数变量
    	DECLARE description VARCHAR(10);			-- 定义分数描述变量
    	SELECT SUM(score) INTO total FROM student; 	-- 为总分数变量赋值
    	-- 判断总分数
    	IF total >= 380 THEN
    		SET description = '学习优秀';
    	ELSEIF total >=320 AND total < 380 THEN
    		SET description = '学习良好';
    	ELSE
    		SET description &#
;