最后一篇笔记了;MySQL部分暂且完结!前面的传送门:
MySQL笔记(三) 联结、组合查询、全文本搜索、视图、索引、触发器、事务
MySQL笔记(二) 数据库操纵语言DML 、数据库查询语言DQL、数据库控制语言DCL、计算字段、子查询、函数、聚集函数
MySQL笔记(一):设计范式、基础概念、数据库定义语言DDL
函数的可移植性没有SQL强,所以要确保做注释
大多数SQL支持以下类型的函数
实例
#注意如果使用的是UTF-8编码格式,那么一个汉字占3字节,数字和字母占一个字节)
SELECT LENGTH(`name`) FROM student
#获取第二个字
SELECT SUBSTRING(name, 2, 2) FROM student
关于SOUNDEX
是一个将任何文本串转换为描述其语音表示的字母数字模式的算法,它考虑了类似的发音字符和音节,使得能对字符串进行发音比较而不是字母比较
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
日期和时间处理函数采用相应的数据类型和特殊的格式存储,以便能快速有效地排序或者过滤,并且节省物理存储空间
它总是被用来读取、统计和处理这些值,在MySQL当中有着重要的意义
数据经常需要用日期进行过滤,首先需要注意的时MySQL的日期格式均为yyyy-mm-dd
,排除了多义性
SELECT cust_id, order_num
FROM orders
WHERE order_date = '2023-05-07';
但是这种会出问题,因为date的类型为datetime
,它同时存储日期与时间值,因此我们就需要用到Date()
函数,仅仅提取日期部分
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) = '2023-05-07';
如果想要提取一个自然月的订单:
第一种方法就是使用 BETWEEN
WHERE Date(order_date) BETWEEN '2023-04-01' AND '2023-04-30';
第二种方法:分别提取比较
WHERE Year(order_date) = 2023 AND Month(order_date) = 9;
其他实例
单位有:year(年)、month(月)、day(日)、hour(小时)、minute(分钟)、second(秒)
#延后5天
SELECT DATE_ADD('2022-1-1',INTERVAL 5 day)
#向前一年
SELECT DATE_ADD('2022-1-1',INTERVAL -1 year)
在主要DBMS的函数中,数值函数是最统一最一致的函数
补充:ceiling(x) x向上取整;floor(x) x向下取整;round(x, 精度) x取四舍五入,遵循小数点精度 ;log(x) x的对数;power(x, n) x的n次方
cast(数据 as 数据类型)
SELECT CAST(pi() AS SIGNED)
数据类型有以下几种:
MySQL还为我们提供了很多的逻辑判断函数,比如:
除了IF条件判断,我们还可以使用类似Switch一样的语句完成多分支结构:
SELECT
CASE 2
WHEN 1 THEN
10
ELSE
5
END;
我们也可以将自定义的判断条件放入When之后,它类似于else-if:
SELECT
CASE
WHEN 3>5 THEN
10
WHEN 0<1 THEN
11
ELSE
5
END;
还有一个类似于Java中的Thread.sleep的函数,以秒为单位:
SELECT sleep(10);
函数定义后不能修改
可以添加参数和返回值,可以通过CREATE FUNCTION
创建函数:
CREATE FUNCTION test() RETURNS INT
BEGIN
RETURN 666;
END
定义函数的格式
{ ... }
添加参数注意类型需要写在参数名称后面:
CREATE FUNCTION test(i INT) RETURNS INT
BEGIN
RETURN i * i;
END
我们可以在BEGIN和RETURN之间编写一些其他的逻辑,比如我们想要定义一个局部变量,并为其赋值:
BEGIN
DECLARE a INT;
SET a = 10;
RETURN i * i * a;
END
定义局部变量的格式为:
为变量赋值的格式为:
我们还可以在函数内部使用select
语句,它可以直接从表中读取数据,并可以结合into关键字将查询结果赋值给变量:
BEGIN
DECLARE a INT;
-- select into from 语句
SELECT COUNT(*) INTO a FROM student;
RETURN a;
END
某些情况下,可以直接在一次会话中直接定义变量并使用,这时它并不是位于函数内的,这就是全局变量,它无需预先定义
set @x = 10;
可以将全局变量作为参数传递给函数:
select test(@x);
除了我们自己定义的全部变量以外,系统默认也有很多的变量
自己定义的变量称为用户变量,系统默认变量称为系统变量。查看系统变量的命令为:
show GLOBAL VARIABLES
聚集函数用来汇总数据,这些函数是高效设计的,返回结果一般比在自己的客户机应用程序中计算的要快得多
包括:
SELECT count(distinct 列名) FROM 表名 AS 别名 WHERE 条件 ;
SELECT COUNT(DISTINCT name) FROM student; //注意中间没有空格
DISTINCT
关键字应用于所有列,而不仅是他的前置列,除非指定的两个列相同,否则所有行都会被检测出来
如果指定列名,DISTINCT只能用于Count()而不是Count(*),也就是不允许使用Count(DISTINCT);
DISTINCT不能用于计算或表达式,必须使用列名
存储过程简单来说就是为以后使用而保存的一条或多条MySQL语句的集合。可将其视为批文件。虽然作用不仅限于批处理
换句话说,使用存储过程有3个主要的好处,即简单、安全、高性能。显然,它们都很重要。不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷
MySQL称存储过程的执行为调用
,因此MySQL执行存储过程的语句为CALL。CALL接受存储过程的名字以及需要传递给它的任意参数
存储过程可以显示结果,也可以不显示结果
CALL productpricing(@pricelow, #执行名为productpricing的存储过程
@pricehigh,
@priceaverage);
#此存储过程名为productpricing,用CREATE PROCEDURE productpricing()语句定义。如果存储过程接受参数,它们将在()中列举出来
CREATE PROCEDURE productpricing()
BEGIN #BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的SELECT语句
SELECT Avg(prod_price) AS priceaverage
FROM products;
END;
MySQL处理这段代码时,它创建一个新的存储过程product-pricing。没有返回数据,因为这段代码并未调用存储过程;这只是为了以后使用创造他
执行刚创建的存储过程并显示返回的结果。因为存储过程实际上是一种函数,所以存储过程名后需要有()符号(即使不传递参数也需要)
CALL productpricing();
这条语句删除刚创建的存储过程。请注意没有使用后面的(),只给出存储过程名
DROP PROCEDURE productpri
如果指定的过程不存在,则DROP PROCEDURE将产生一个错误。当过程存在想删除它时(如果过程不存在也不产生错误)可使用DROP PROCEDURE IF EXISTS
CREATE PROCEDURE productprcing(
OUT pl DECIMAL(8,2), #a指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38;
#b指定小数点右边可以存储的十进制数字的最大个数,小数位数必须是从0到a之间的值,默认小数位数是0
OUT ph DECIMAL(8,2),
OUT pa DECIMAL(8,2) #OUT(从存储过程传出)
)
#存储过程的代码位于BEGIN和END语句内
BEGIN
SELECT Min(prod_price)
INTO pl #保存到相应的变量(通过指定INTO关键字
FROM products;
SELECT Max(prod_price)
INTO ph
FROM products;
SELECT Avg(prod_price)
INTO p2
FROM products;
END;
每个参数必须具有指定的类型,这里使用十进制值
MySQL支持IN(传递给存储过程)、OUT(从存储过程传出)和INOUT(对存储过程传入和传出)类型的参数
记录集不是允许的类型,因此,不能通过一个参数返回多个行和列。这就是前面的例子要使用3个参数(和3条SELECT语句)的原因
CALL productpricing(@pricelow, #所有MySQL变量都必须以@开始
@pricehigh,
@priceaverage);
只有在存储过程内包含业务规则和智能处理时,它们的威力才真正显现出来
CREATE PROCEDURE ordertotal (
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL (8,2)
--它不是必需的,但如果给出,将在SHOW PROCEDURE STATUS的结果中显示
) COMMENT 'Obtain order total, optionally adding tax'
BEGIN
--定义局部变量,DECLARE要求指定变量名和数据类型
DECLARE total DECIMAL (8,2);
DECLARE taxrate INT DEFAULT 6;
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order num = onumber
INTO total;
#IF语句还支持ELSEIF和ELSE子句(前者还使用THEN子句,后者不使用)
IF taxable THEN
SELECT total+(total/100*taxrate) INTO total;
END IF;
SELECT total INTO ototal;
END;
使用
SELECT ordertotal(2005,1,@total);
SELECT @total; #只显示这一行
为了获得包括何时、由谁创建等详细信息的存储过程列表,使用SHOW PROCEDURE STATUS。
限制过程状态结果 SHOW PROCEDURE STATUS列出所有存储过程。为限制其输出,可使用LIKE指定一个过滤模式,例如:
显示用来创建一个存储过程的CREATE语句
SHOW CREATE PROCEDURE ordertotal;
获得包括何时、由谁创建等详细信息的存储过程列表
SHOW PROCEDURE STATUS ordertotal;
限制过程状态结果 SHOW PROCEDURE STATUS列出所有存储过程。为限制其输出,可使用LIKE指定一个过滤模式,例如:
SHOW PROCEDURE STATUS LIKE 'ordertotal';
是一个存储在MySQL服务器上的数据库查询,不是一条SELECT语句,而是被该语句检索出来的结果集
游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改
MySQL中的游标只能用于存储过程和函数
CREATE PROCEDURE processorders()
BEGINS #存储过程处理完,游标就消失了
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
END;
OPEN ordernumbers;
CLOSE ordernumbers; #释放游标使用的所有内存和资源
隐式关闭:当END语句之后,会自动关闭
在一个游标被打开之后,可以使用FETCH
分别访问每一行
FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)
OPEN ordernumbers;
FETCH ordernumbers INTO o;
CLOSE ordernumbers;
循环检索数据
CREATE PROCEDURE processorders()
BEGIN
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE oredernumbers CURSOR
FOR
SELECT order_num FROM orders;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN ordernumbers;
REPEAT
FETCH ordernumbers INTO o;
UNTIL done END REPEAT;
CLOSE ordernumbers;
关于MySQL 8使用的MySQL错误代码列表:MySQL :: MySQL 8.0 Reference Manual :: B Error Messages and Common Problems
DECLARE语句的次序 :用DECLARE语句定义的局部变量必须在定义任意游标或句柄之前定义,而句柄必须在游标之后定义
重复或循环:除这里使用的REPEAT语句外,MySQL还支持循环语句,它可用来重复执行代码,直到使用LEAVE语句手动退出为止。通常REPEAT语句的语法使它更适合于对游标进行循环。
为了把这些内容组织起来,下面给出我们的游标存储过程样例的更进一步修改的版本,这次对取出的数据进行某种实际的处理:
CREATE PROCEDURE processorders()
BEGIN
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE t DECIMAL(8, 2);
DECLARE oredernumbers CURSOR
FOR
SELECT order_num FROM orders;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
CREATE TABLE IF NOT EXISTS ordertotals (
order_num INT,
total DECIMAL(8, 2));
OPEN ordernumbers;
REPEAT
FETCH ordernumbers INTO o;
CALL ordertotal(o, 1, t); #执行另一个过程
INSERT INTO ordertotals(order_num, total) VALUES(o, t)
UNTIL done END REPEAT;
CLOSE ordernumbers;
当数据量变得非常庞大的时候,一个索引能很好的帮助;能够快速定位元素的位置
不能过度使用,会占用资源
只针对某一列数据创建索引
类型
like %
更高,并且它还支持多种匹配方式,灵活性也更加强大。只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。-- 创建索引
CREATE INDEX 索引名称 ON 表名 (列名)
-- 查看表中的索引
show INDEX FROM student
mysql> CREATE INDEX i ON student(name);
mysql> SHOW INDEX FROM student;
DROP INDEX 索引名称 FROM 表名;
组合索引实际上就是将多行捆绑在一起,作为一个索引,它同样支持以上几种索引类型
注意组合索引在进行匹配时,遵循最左原则
可以使用explain
语句(它可以用于分析select语句的执行计划,也就是MySQL到底是如何在执行某条select语句的)来分析查询语句到底有没有通过索引进行匹配
explain select * from student where name = '小王';
索引是存储在硬盘上的,跟我们之前使用的HashMap之类的不同,它们都是在内存中的,但是硬盘的读取速度远小于内存的速度
每一次IO操作都会耗费大量的时间,我们也不可能把整个磁盘上的索引全部导入内存,因此我们需要考虑尽可能多的减少IO次数
索引的实现可以依靠两种数据结构,一种是我们在JavaSE阶段已经学习过的Hash表,还有一种就是B-Tree
通过对Key进行散列值计算,我们可以直接得到对应数据的存放位置,它的查询效率能够达到O(1),但是它也存在一定的缺陷:
那么,既然要解决这些问题,我们还有一种方案就是使用类似于二叉树那样的数据结构来存储索引,但是这样相比使用Hash索引,会牺牲一定的读取速度。
但是这里并没有使用二叉树,而是使用了BTree,它是专门为磁盘数据读取设计的一种度为n的查找树:
树中每个结点最多含有m个孩子(m >= 2)
除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子。
若根结点不是叶子结点,则至少有2个孩子。
所有叶子结点都出现在同一层。
每个非终端结点中包含有n个键值信息: (P1,K1,P2,K2,P3,…,Kn,Pn+1)。其中:
比如现在要对键值为10的记录进行查找,过程如下:
接着来看,虽然BTree能够很好地利用二叉查找树的思想大幅度减少查找次数,但是它的查找效率还是很低,因此它的优化版本B+Tree诞生了,它拥有更稳定的查询效率和更低的IO读取次数:
可以发现,它和BTree有一定的区别:
这样,读取IO的时间相比BTree就减少了很多,并且查询任何键值信息都需要完整地走到叶子节点,保证了查询的IO读取次数一致。因此MySQL默认选择B+Tree作为索引的存储数据结构。
这是MyISAM存储引擎下的B+Tree实现:
这是InnoDB存储引擎下的B+Tree实现:
InnoDB与MyISAM实现的不同之处:
不同的语言和字符集需要以不同的方式存储和检索,因此MySQL需要适应不同的字符集、排序和检索的方式
字符集:字母和符号的集合
编码:某个字符集成员的内部表示
校对:规定字符如何比较的指令
在MySQL的正常数据库活动中不需要太担心,使用何种字符集和校对的决定在服务器、数据库和表级进行
查找所支持的字符集完整列表;显示可用字符集、描述和默认校对
SHOW CHARACTER SET;
查看所支持的校对完整列表;通常出现两次:区分大小写(_cs)、不区分 ( _ci )
SHOW COLLATION
指定:不指定就默认
CREATE TABLE mytable (
columnn1 INT,
columnn2 VARCHAR(10)
)DEFAULT CHARACTER SET hebrew
COLLATE hebrew_general_ci;
还允许给每一个列设置
CREATE TABLE mytable (
columnn1 INT,
columnn2 VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_general_ci;
)DEFAULT CHARACTER SET hebrew
COLLATE hebrew_general_ci;
如果绝对需要,串可以在字符集之间转换,用Cast()
和Convert()
函数
由于MySQL数据库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据。但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不一定总是有效
解决方案
mysqldump
在进行常规备份前这个实用程序应该正常运行,以便能正确地备份转储文件mysqlhotcopy
(并非所有数据库引擎都支持这个实用程序)BACKUP TABLE
或SELECT INTO OUTFILE
。这两条语句都接受将要创建的系统文件名,此系统文件必须不存在,否则会出错。RESTORE TABLE
FLUSH TABLES
语句 ANALYZE TABLE
用来检查表键是否正确
mysql> ANALYZE TABLE student;
+--------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------+---------+----------+----------+
| need.student | analyze | status | OK |
+--------------+---------+----------+----------+
1 row in set (0.06 sec)
CHECK TABLE
用来针对许多问题对表进行检查。在MyISAM表还对索引进行检查
CHANGED:检查自最后一次检查以来改动过的表
EXTENDED:执行最彻底的检查
FAST:只检查未正常关闭的表
MEDIUM:检查所有被删除的链接并进行键检验
QUICK:只进行快速扫描
如果MyISAM表访问产生不正确和不一致的结果,使用REPAIR TABLE
从表中删除大量数据,则需要使用OPTIMIZE TABLE
来收回所用的空间,从而优化性能
服务器启动问题通常在对MySQL配置或服务器本身进行更改时出现
MySQL在这个问题发生时报告错误,但由于多数MySQL服务器是作为系统进程或服务自动启动的,这些消息可能看不到
在排除系统启动问题时,首先应该尽量用手动启动服务器;MySQL服务器自身通过在命令行上执行mysqld启动
下面是几个重要的mysqld命令行选项:
MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下几种:
hostname.err
,位于data目录。日志名可用--log-error
命令行选项更改hostname.log
,位于data目录。此名字用--log
命令行选项更改hostname-bin
,位于data目录内。此名字可以用--log-bin
命令行选项更改。(这个日志文件是MySQL 5中添加的,以前的MySQL版本中使用的是更新日志)hostname-slow.log
,位于data目录中。此名字可以用--log-slow-queries
命令行选项更改在使用日志时,可用FLUSH LOGS语句来刷新和重新开始所有日志文件
存储引擎就像我们电脑中的CPU,它是整个MySQL最核心的部分,数据库中的数据如何存储,数据库能够支持哪些功能,我们的增删改查请求如何执行,都是由存储引擎来决定的。
大致了解一下以下三种存储引擎:
我们可以使用下面的命令来查看MySQL支持的存储引擎:
show engines;
在创建表时,我们也可以为表指定其存储引擎。
我们还可以在配置文件中修改默认的存储引擎,在Windows 11系统下,MySQL的配置文件默认放在C:\ProgramData\MySQL\MySQL Server 8.0
中,ProgramData是个隐藏文件夹
当对某个方法或是某个代码块加锁后,除非锁的持有者释放当前的锁,否则其他线程无法进入此方法或是代码块,我们可以利用锁机制来保证多线程之间的安全性
在MySQL中很容易出现多线程同时操作表中数据的情况;
为了避免潜在的并发问题,可以使用之前讲解的事务隔离级别来处理,而事务隔离中利用了锁机制
我们可以切换隔离级别分别演示一下:
set session transaction isolation level read uncommitted;
在RR级别下,MySQL在一定程度上解决了幻读问题:
MVCC
,全称Multi-Version Concurrency Control
:即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存
从对数据的操作类型上来说,锁分为读锁和写锁:
从锁的作用范围上划分,分为全局锁、表锁和行锁:
我们首先来看全局锁,它作用于整个数据库,我们可以使用以下命令来开启读全局锁:
flush tables with read lock;
开启后,整个数据库被上读锁,我们只能去读取数据,但是不允许进行写操作(包括更新、插入、删除等)一旦执行写操作,会被阻塞,直到锁被释放,我们可以使用以下命令来解锁:
unlock tables;
除了手动释放锁之外,当我们的会话结束后,锁也会被自动释放。
表锁作用于某一张表,也是MyISAM和InnoDB存储引擎支持的方式,我们可以使用以下命令来为表添加锁:
lock table 表名称 read/write;
其他地方是无法访问此表的,一律都被阻塞
表锁的作用范围太广了,如果我们仅仅只是对某一行进行操作,那么大可不必对整个表进行加锁,因此InnoDB
支持了行锁,我们可以使用以下命令来对某一行进行加锁:
-- 添加读锁(共享锁)
select * from ... lock in share mode;
-- 添加写锁(排他锁)
select * from ... for update;
InnoDB:在执行更新、删除、插入操作时,数据库也会自动为所涉及的行添加写锁(排他锁),直到事务提交时,才会释放锁,执行普通的查询操作时,不会添加任何锁
MyISAM:在执行更新、删除、插入操作时,数据库会对涉及的表添加写锁,在执行查询操作时,数据库会对涉及的表添加读锁
我们知道InnoDB支持使用行锁,但是行锁比较复杂,它可以继续分为多个类型
记录锁, 仅仅锁住索引记录的一行,在单条索引记录上加锁
Record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚合索引后面加写锁,这个类似于表锁,但原理上和表锁应该是完全不同的
仅仅锁住一个索引区间(开区间,不包括双端端点)。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。比如在 1、2中,间隙锁的可能值有 (-∞, 1),(1, 2),(2, +∞)
间隙锁可用于防止幻读,保证索引间的不会被插入数据
Record lock + Gap lock,左开右闭区间。默认情况下,InnoDB
正是使用Next-key Locks来锁定记录(如select … for update语句)它还会根据场景进行灵活变换:
场景 | 转换 |
---|---|
使用唯一索引进行精确匹配,但表中不存在记录 | 自动转换为 Gap Locks |
使用唯一索引进行精确匹配,且表中存在记录 | 自动转换为 Record Locks |
使用非唯一索引进行精确匹配 | 不转换 |
使用唯一索引进行范围匹配 | 不转换,但是只锁上界,不锁下界 |