MySQL笔记(四)
文章目录
最后一篇笔记了;MySQL部分暂且完结!前面的传送门:
MySQL笔记(三) 联结、组合查询、全文本搜索、视图、索引、触发器、事务
MySQL笔记(二) 数据库操纵语言DML 、数据库查询语言DQL、数据库控制语言DCL、计算字段、子查询、函数、聚集函数
MySQL笔记(一):设计范式、基础概念、数据库定义语言DDL
函数
函数的可移植性没有SQL强,所以要确保做注释
大多数SQL支持以下类型的函数
- 用于处理文本串的文本函数
- 用于在数值数据上进行算术操作的数值函数
- 用于处理日期和时间值并从这些值中提取特定成分的日期和时间函数
- 返回DBMS正在使用的特殊信息的系统函数
文本处理函数
实例
#注意如果使用的是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)
数据类型有以下几种:
- BINARY[(N)] :二进制字符串,转换后长度小于N个字节
- CHAR[(N)] :字符串,转换后长度小于N个字符
- DATE :日期
- DATETIME :日期时间
- DECIMAL[(M[,N])] :浮点数,M为数字总位数(包括整数部分和小数部分),N为小数点后的位数
- SIGNED [INTEGER] :有符号整数
- TIME :时间
- UNSIGNED [INTEGER] :无符号整数
流程控制函数
MySQL还为我们提供了很多的逻辑判断函数,比如:
- if(条件表达式, 结果1, 结果2) 与Java中的三目运算符一致 a > b ? “AAA” : “BBB”
- ifnull(值1, 值2) 如果值1为NULL则返回值2,否则返回值1
- nullif(值1, 值2) 如果值1与值2相等,那么返回NULL
- isnull(值) 判断值是否为NULL
除了IF条件判断,我们还可以使用类似Switch一样的语句完成多分支结构:
SELECT
CASE 2WHEN 1 THEN10ELSE5
END;
我们也可以将自定义的判断条件放入When之后,它类似于else-if:
SELECT
CASEWHEN 3>5 THEN10WHEN 0<1 THEN11ELSE5
END;
还有一个类似于Java中的Thread.sleep的函数,以秒为单位:
SELECT sleep(10);
自定义函数
函数定义后不能修改
基本语法
可以添加参数和返回值,可以通过CREATE FUNCTION
创建函数:
CREATE FUNCTION test() RETURNS INT
BEGIN
RETURN 666;
END
定义函数的格式
- create function 函数名称([参数列表]) returns 返回值类型
- begin 和 end 之间写函数的其他逻辑,begin和end就相当于Java中的花括号
{ ... }
- return后紧跟返回的结果
添加参数注意类型需要写在参数名称后面:
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
定义局部变量的格式为:
- declare 变量名称 变量类型 [, …]
- declare 变量名称 变量类型 default 默认值
为变量赋值的格式为:
- set 变量名称 = 值
我们还可以在函数内部使用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
聚集函数 aggregate function
聚集函数用来汇总数据,这些函数是高效设计的,返回结果一般比在自己的客户机应用程序中计算的要快得多
包括:
- count([distinct]*)统计所有的行数(distinct表示去重再统计,下同)
- count([distinct]列名)统计某列的值总和
- sum([distinct]列名)求一列的和(注意必须是数字类型的)
- avg([distinct]列名)求一列的平均值(注意必须是数字类型)
- max([distinct]列名)求一列的最大值
- min([distinct]列名)求一列的最小值
SELECT count(distinct 列名) FROM 表名 AS 别名 WHERE 条件 ;SELECT COUNT(DISTINCT name) FROM student; //注意中间没有空格
DISTINCT
DISTINCT
关键字应用于所有列,而不仅是他的前置列,除非指定的两个列相同,否则所有行都会被检测出来
如果指定列名,DISTINCT只能用于Count()而不是Count(*),也就是不允许使用Count(DISTINCT);
DISTINCT不能用于计算或表达式,必须使用列名
存储过程
存储过程简单来说就是为以后使用而保存的一条或多条MySQL语句的集合。可将其视为批文件。虽然作用不仅限于批处理
为什么要使用
- 通过处理封装在容易使用的单元中,简化复杂的操作
- 由于不要求反复建立一系列处理步骤,这保证了数据的完整性。这一点的延申就是防止错误,需要执行的步骤越多,出错的可能性就越大
- 简化对变动的管理。如果有所变动,只需要更改存储过程的代码,使用它的人员甚至都不需要知道;这一点的延申就是安全性,通过存储过程限制对基础数据的访问减少了数据讹传
- 提高性能。因为使用存储过程比使用单独的SQL语句要快。
- 存在一些只能用在单个请求中的MySQL元素和特性,存储过程可以使用它们来编写功能更强更灵活的代码(在下一章的例子中可以看到。)
换句话说,使用存储过程有3个主要的好处,即简单、安全、高性能。显然,它们都很重要。不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷
- 一般来说,存储过程的编写比基本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之间的值,默认小数位数是0OUT ph DECIMAL(8,2),OUT pa DECIMAL(8,2) #OUT(从存储过程传出)
)
#存储过程的代码位于BEGIN和END语句内
BEGINSELECT Min(prod_price)INTO pl #保存到相应的变量(通过指定INTO关键字FROM products;SELECT Max(prod_price)INTO phFROM products;SELECT Avg(prod_price)INTO p2FROM 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 THENSELECT 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中的游标只能用于存储过程和函数
使用规则
- 必须声明,这个过程实际上没有检索数据,只是单纯的定义
- 一旦声明,必须打开游标以供使用,这个过程用前面定义的SELECT语句把数据实际检索出来
- 对于填有数据的游标,根据需要检索
- 结束使用时,必须关闭
创建
CREATE PROCEDURE processorders()
BEGINS #存储过程处理完,游标就消失了DECLARE ordernumbers CURSORFORSELECT order_num FROM orders;
END;
打开关闭
OPEN ordernumbers;
CLOSE ordernumbers; #释放游标使用的所有内存和资源
隐式关闭:当END语句之后,会自动关闭
使用
在一个游标被打开之后,可以使用FETCH
分别访问每一行
FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)
OPEN ordernumbers;
FETCH ordernumbers INTO o;
CLOSE ordernumbers;
循环检索数据
CREATE PROCEDURE processorders()
BEGINDECLARE done BOOLEAN DEFAULT 0;DECLARE o INT;DECLARE oredernumbers CURSORFOR SELECT order_num FROM orders;DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;OPEN ordernumbers;REPEATFETCH 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()
BEGINDECLARE done BOOLEAN DEFAULT 0;DECLARE o INT;DECLARE t DECIMAL(8, 2);DECLARE oredernumbers CURSORFOR 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;REPEATFETCH ordernumbers INTO o;CALL ordertotal(o, 1, t); #执行另一个过程INSERT INTO ordertotals(order_num, total) VALUES(o, t)UNTIL done END REPEAT;
CLOSE ordernumbers;
索引
当数据量变得非常庞大的时候,一个索引能很好的帮助;能够快速定位元素的位置
不能过度使用,会占用资源
单列索引
只针对某一列数据创建索引
类型
- **NORMAL:**普通的索引类型,完完全全相当于一本书的目录
- **UNIQUE:**唯一索引,一旦建立唯一索引,那么整个列中将不允许出现重复数据。每个表的主键列,都有一个特殊的唯一索引,叫做Primary Key,它不仅仅要求不允许出现重复,还要求不能为NULL,它还可以自动递增。每张表可以有多个唯一索引,但是只能有一个Primary索引
- **SPATIAL:**空间索引,空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON
- **FULLTEXT:**全文索引(MySQL 5.6 之后InnoDB才支持),它是模糊匹配的一种更好的解决方案,它的效率要比使用
like %
更高,并且它还支持多种匹配方式,灵活性也更加强大。只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。
-- 创建索引
CREATE INDEX 索引名称 ON 表名 (列名)
-- 查看表中的索引
show INDEX FROM studentmysql> 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索引仅仅能满足“=”,“in”查询条件,不能使用范围查询。
- Hash碰撞问题。
- 不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。
那么,既然要解决这些问题,我们还有一种方案就是使用类似于二叉树那样的数据结构来存储索引,但是这样相比使用Hash索引,会牺牲一定的读取速度。
但是这里并没有使用二叉树,而是使用了BTree,它是专门为磁盘数据读取设计的一种度为n的查找树:
-
树中每个结点最多含有m个孩子(m >= 2)
-
除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子。
-
若根结点不是叶子结点,则至少有2个孩子。
-
所有叶子结点都出现在同一层。
-
每个非终端结点中包含有n个键值信息: (P1,K1,P2,K2,P3,…,Kn,Pn+1)。其中:
- Ki (i=1…n)为键值,且键值按顺序升序排序K(i-1)< Ki。
- Pi为指向子树根的结点,且指针P(i)指向的子树中所有结点的键值均小于Ki,但都大于K(i-1)。
- 键值的个数n必须满足: [ceil(m / 2)-1] <= n <= m-1。
比如现在要对键值为10的记录进行查找,过程如下:
- 读取根节点数据(目前进行了一次IO操作)
- 根据根节点数据进行判断得到10<17,因为P1指向的子树中所有值都是小于17的,所以这时我们将P1指向的节点读取(目前进行了两次IO操作)
- 再次进行判断,得到8<10<12,因为P2指向的子树中所有的值都是小于12大于8的,所以这时读取P2指向的节点(目前进行了三次IO操作)
- 成功找到。
接着来看,虽然BTree能够很好地利用二叉查找树的思想大幅度减少查找次数,但是它的查找效率还是很低,因此它的优化版本B+Tree诞生了,它拥有更稳定的查询效率和更低的IO读取次数:
可以发现,它和BTree有一定的区别:
- 有n棵子树的结点中含有n个键值,BTree只有n-1个。
- 所有的键值信息只在叶子节点中包含,非叶子节点仅仅保存子节点的最小(或最大)值,和指向叶子节点的指针,这样相比BTree每一个节点在硬盘中存放了更少的内容(没有键值信息了)
- 所有叶子节点都有一个根据大小顺序指向下一个叶子节点的指针Q,本质上数据就是一个链表。
这样,读取IO的时间相比BTree就减少了很多,并且查询任何键值信息都需要完整地走到叶子节点,保证了查询的IO读取次数一致。因此MySQL默认选择B+Tree作为索引的存储数据结构。
这是MyISAM存储引擎下的B+Tree实现:
这是InnoDB存储引擎下的B+Tree实现:
InnoDB与MyISAM实现的不同之处:
- 数据本身就是索引的一部分(所以这里建议主键使用自增)
- 非主键索引的数据实际上存储的是对应记录的主键值(因此InnoDB必须有主键,若没有也会自动查找替代)
全球化和本地化
基础
不同的语言和字符集需要以不同的方式存储和检索,因此MySQL需要适应不同的字符集、排序和检索的方式
字符集:字母和符号的集合
编码:某个字符集成员的内部表示
校对:规定字符如何比较的指令
在MySQL的正常数据库活动中不需要太担心,使用何种字符集和校对的决定在服务器、数据库和表级进行
使用
查找所支持的字符集完整列表;显示可用字符集、描述和默认校对
SHOW CHARACTER SET;
查看所支持的校对完整列表;通常出现两次:区分大小写(_cs)、不区分 ( _ci )
SHOW COLLATION
指定:不指定就默认
CREATE TABLE mytable (columnn1 INT,columnn2 VARCHAR(10)
)DEFAULT CHARACTER SET hebrewCOLLATE hebrew_general_ci;
还允许给每一个列设置
CREATE TABLE mytable (columnn1 INT,columnn2 VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_general_ci;
)DEFAULT CHARACTER SET hebrewCOLLATE 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命令行选项:
- –help显示帮助——一个选项列表;
- –safe-mode装载减去某些最佳配置的服务器;
- –verbose显示全文本消息(为获得更详细的帮助消息与–help联合使用);
- –version显示版本信息然后退出
查看日志文件
MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下几种:
- 错误日志:它包含启动和关闭问题以及任意关键错误的细节。通常名为
hostname.err
,位于data目录。日志名可用--log-error
命令行选项更改 - 查询日志:它记录所有MySQL活动,在诊断问题时非常有用。此日志文件可能会很快地变得非常大,因此不应该长期使用它。通常名为
hostname.log
,位于data目录。此名字用--log
命令行选项更改 - 二进制日志:它记录更新过数据(或者可能更新过数据)的所有语句。此日志通常名为
hostname-bin
,位于data目录内。此名字可以用--log-bin
命令行选项更改。(这个日志文件是MySQL 5中添加的,以前的MySQL版本中使用的是更新日志) - 缓慢查询日志 :顾名思义,此日志记录执行缓慢的任何查询。这个日志在确定数据库何处需要优化很有用。此日志通常名为
hostname-slow.log
,位于data目录中。此名字可以用--log-slow-queries
命令行选项更改
在使用日志时,可用FLUSH LOGS语句来刷新和重新开始所有日志文件
存储引擎
存储引擎就像我们电脑中的CPU,它是整个MySQL最核心的部分,数据库中的数据如何存储,数据库能够支持哪些功能,我们的增删改查请求如何执行,都是由存储引擎来决定的。
大致了解一下以下三种存储引擎:
- **MyISAM:**MySQL5.5之前的默认存储引擎,在插入和查询的情况下性能很高,但是它不支持事务,只能添加表级锁。
- **InnoDB:**MySQL5.5之后的默认存储引擎,它支持ACID事务、行级锁、外键,但是性能比不过MyISAM,更加消耗资源。
- **Memory:**数据都存放在内存中,数据库重启或发生崩溃,表中的数据都将消失。
我们可以使用下面的命令来查看MySQL支持的存储引擎:
show engines;
在创建表时,我们也可以为表指定其存储引擎。
我们还可以在配置文件中修改默认的存储引擎,在Windows 11系统下,MySQL的配置文件默认放在C:\ProgramData\MySQL\MySQL Server 8.0
中,ProgramData是个隐藏文件夹
锁机制
当对某个方法或是某个代码块加锁后,除非锁的持有者释放当前的锁,否则其他线程无法进入此方法或是代码块,我们可以利用锁机制来保证多线程之间的安全性
在MySQL中很容易出现多线程同时操作表中数据的情况;
为了避免潜在的并发问题,可以使用之前讲解的事务隔离级别来处理,而事务隔离中利用了锁机制
- 读未提交(Read Uncommitted):能够读取到其他事务中未提交的内容,存在脏读问题
- 读已提交(Read Committed RC):只能读取其他事务已经提交的内容,存在不可重复读问题
- 可重复读(Repeated Read RR):在读取某行后不允许其他事务操作此行,直到事务结束,但是依然存在幻读问题
- 串行读(Serializable):一个事务的开始必须等待另一个事务的完成
我们可以切换隔离级别分别演示一下:
set session transaction isolation level read uncommitted;
在RR级别下,MySQL在一定程度上解决了幻读问题:
- 在快照读(不加锁)读情况下,mysql通过mvcc来避免幻读
- 在当前读(加锁)读情况下,mysql通过next-key来避免幻读
MVCC
,全称Multi-Version Concurrency Control
:即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存
读锁和写锁
从对数据的操作类型上来说,锁分为读锁和写锁:
- **读锁:**也叫共享锁,当一个事务添加了读锁后,其他的事务也可以添加读锁或是读取数据,但是不能进行写操作,只能等到所有的读锁全部释放
- **写锁:**也叫排他锁,当一个事务添加了写锁后,其他事务不能读不能写也不能添加任何锁,只能等待当前事务释放锁
全局锁、表锁和行锁
从锁的作用范围上划分,分为全局锁、表锁和行锁:
- **全局锁:**锁作用于全局,整个数据库的所有操作全部受到锁限制。
- **表锁:**锁作用于整个表,所有对表的操作都会收到锁限制。
- **行锁:**锁作用于表中的某一行,只会通过锁限制对某一行的操作(仅InnoDB支持)
全局锁
我们首先来看全局锁,它作用于整个数据库,我们可以使用以下命令来开启读全局锁:
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 Locks)
记录锁, 仅仅锁住索引记录的一行,在单条索引记录上加锁
Record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚合索引后面加写锁,这个类似于表锁,但原理上和表锁应该是完全不同的
间隙锁(Gap Locks)
仅仅锁住一个索引区间(开区间,不包括双端端点)。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。比如在 1、2中,间隙锁的可能值有 (-∞, 1),(1, 2),(2, +∞)
间隙锁可用于防止幻读,保证索引间的不会被插入数据
临键锁(Next-Key Locks)
Record lock + Gap lock,左开右闭区间。默认情况下,InnoDB
正是使用Next-key Locks来锁定记录(如select … for update语句)它还会根据场景进行灵活变换:
场景 | 转换 |
---|---|
使用唯一索引进行精确匹配,但表中不存在记录 | 自动转换为 Gap Locks |
使用唯一索引进行精确匹配,且表中存在记录 | 自动转换为 Record Locks |
使用非唯一索引进行精确匹配 | 不转换 |
使用唯一索引进行范围匹配 | 不转换,但是只锁上界,不锁下界 |