理解MySQL
# 一、引言
MySQL
是一种开源的关系型数据库管理系统(RDBMS
),最初由瑞典MySQL AB
公司开发,后被甲骨文(Oracle
)公司收购。
作为一种基于SQL
(结构化查询语言)的数据库管理系统,MySQL
在许多应用中广泛使用,如网站后端、电子商务、数据仓库和企业信息系统等。它以高性能、稳定性和易用性著称,是当今最受欢迎的关系型数据库之一。
随着互联网和大数据时代的到来,数据管理变得越来越重要。MySQL
作为一种高效且稳定的数据库管理系统,具有以下优点:
- 开源:
MySQL
遵循GPL
协议,可以免费使用,这使得许多开发者能够轻松地学习和使用它,推动了开源社区的发展。 - 高性能:
MySQL
采用多层架构设计,可以支持大量并发连接和高速数据处理,满足现代应用的性能要求。 - 易用性:
MySQL
提供了丰富的工具和资源,使得数据库管理变得简单高效。同时,它的语法简洁易懂,便于学习和使用。 - 稳定性:经过多年的发展,
MySQL
已经具备了足够的稳定性,可以应对各种复杂的应用场景和生产环境。 - 社区支持:
MySQL
有一个庞大的开发者社区,为用户提供了丰富的资源、插件和第三方工具,方便用户解决问题和拓展功能。
基于以上优点,MySQL
在许多领域都有广泛的应用,如:
- 网站后端:作为网站数据存储和管理的核心组件,
MySQL
为各种规模的网站提供了强大的支持。 - 电子商务:电子商务平台需要处理大量的交易、库存和客户数据,
MySQL
可以高效地支持这些操作。 - 数据仓库:
MySQL
可以作为数据仓库,存储和分析企业的大量数据,帮助企业做出数据驱动的决策。 - 企业信息系统:作为企业信息系统的基础设施,
MySQL
可以处理各种业务场景,如财务、人力资源和供应链管理等。
本文旨在帮助读者全面了解MySQL
(8.0.33
版本)的基本概念、功能和应用场景,以便更好地使用这一强大的数据库管理系统。
# 二、MySQL基础知识
# 1、关系型数据库与非关系型数据库
**关系型数据库(Relational Database)**基于关系模型,使用二维表结构(表)来存储数据。关系型数据库的主要特点是数据间的关系通过表之间的关联来表示,支持复杂的SQL
查询。典型的关系型数据库有MySQL
、Oracle
、SQL Server
等。
**非关系型数据库(NoSQL,Not Only SQL)**不完全依赖关系模型,主要用于解决关系型数据库在大数据和高并发场景下的性能瓶颈。非关系型数据库的类型有:
- 键值存储(如
Redis
) - 文档存储(如
MongoDB
) - 列式存储(如
Cassandra
) - 图形数据库(如
Neo4j
)等。
# 2、MySQL与其他关系型数据库的比较
数据库 | 类型 | 优势 | 劣势 |
---|---|---|---|
Oracle | 商业 | 强大功能、高性能、稳定性 | 许可费高、复杂度高 |
SQL Server | 商业 | 与微软产品兼容、企业级功能 | Windows平台绑定、许可费用 |
PostgreSQL | 开源 | 丰富功能、SQL 标准兼容、扩展性强 | 学习曲线陡峭、性能调优复杂 |
MySQL | 开源 | 开源免费、易用、社区资源丰富 | 某些高级功能相对较少 |
详细对比:
Oracle
:Oracle
数据库是一种商业关系型数据库,具有强大的功能、高性能和稳定性。但是,Oracle
的许可费较高,对于中小型企业来说可能成本较大。SQL Server
:SQL Server
是微软推出的关系型数据库,与其它微软产品有很好的兼容性。SQL Server
在性能和功能上与Oracle
类似,但许可费较低。PostgreSQL
:PostgreSQL
是一种开源关系型数据库,具有丰富的功能和高度可扩展性。相较于MySQL
,PostgreSQL
支持更多的SQL
标准和高级功能,如物化视图和存储过程等。MySQL
:MySQL
是一种开源关系型数据库,具有较好的性能和稳定性。与其他关系型数据库相比,MySQL
的优势在于其开源、免费、易用和有丰富的社区资源。
# 3、MySQL的数据类型及常用场景
MySQL
支持多种数据类型,这些数据类型可以分为以下几类:
# 3.1、数值类型(Numeric Types)
类型 | 字节 | 有符号范围 | 无符号范围 | 常用场景 |
---|---|---|---|---|
TINYINT | 1 | -128 ~ 127 | 0 ~ 255 | 开关状态、状态码 |
SMALLINT | 2 | -32,768 ~ 32,767 | 0 ~ 65,535 | 年龄、身高 |
MEDIUMINT | 3 | -8,388,608 ~ 8,388,607 | 0 ~ 16,777,215 | IP地址转换 |
INT | 4 | -2,147,483,648 ~ 2,147,483,647 | 0 ~ 4,294,967,295 | 用户ID、订单号 |
BIGINT | 8 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0 ~ 18,446,744,073,709,551,615 | 全球唯一ID |
浮点数类型:
FLOAT
:4字节,单精度浮点数,范围约为 ±3.4 × 10^38,精度约7位小数。用于存储价格、温度等对精度要求不高的数据。DOUBLE
:8字节,双精度浮点数,范围约为 ±1.8 × 10^308,精度约15位小数。用于存储经纬度、科学计算结果等需要更高精度的数据。DECIMAL
:变长(5-17字节),任意精度十进制数。用于财务、金融等需要精确计算的场景。
示例:
CREATE TABLE example_numeric (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
status TINYINT DEFAULT 0,
age SMALLINT UNSIGNED,
price DECIMAL(10,2),
latitude DOUBLE,
longitude DOUBLE
);
# 3.2、字符串类型(String Types)
文本类型:
类型 | 最大长度 | 存储空间 | 常用场景 |
---|---|---|---|
CHAR(M) | 255字符 | M字节(定长) | 国家代码、状态码 |
VARCHAR(M) | 65,535字符 | 实际长度 + 1-2字节 | 用户名、地址、标题 |
TINYTEXT | 255字符 | 实际长度 + 1字节 | 短消息、评论 |
TEXT | 65,535字符 | 实际长度 + 2字节 | 文章内容、邮件 |
MEDIUMTEXT | 16,777,215字符 | 实际长度 + 3字节 | 长文档、报告 |
LONGTEXT | 4GB | 实际长度 + 4字节 | 长篇小说、电子书 |
二进制类型:
类型 | 最大长度 | 存储空间 | 常用场景 |
---|---|---|---|
BINARY(M) | 255字节 | M字节(定长) | 定长二进制数据 |
VARBINARY(M) | 65,535字节 | 实际长度 + 1-2字节 | 变长二进制数据 |
TINYBLOB | 255字节 | 实际长度 + 1字节 | 小型文件 |
BLOB | 65,535字节 | 实际长度 + 2字节 | 图片、音频 |
MEDIUMBLOB | 16MB | 实际长度 + 3字节 | 视频文件 |
LONGBLOB | 4GB | 实际长度 + 4字节 | 大型媒体文件 |
特殊类型:
ENUM
:枚举类型,用于存储预定义值集合中的一个值SET
:集合类型,用于存储预定义值集合中的多个值
示例:
CREATE TABLE example_string (
id INT PRIMARY KEY,
country_code CHAR(2),
username VARCHAR(50),
description TEXT,
avatar BLOB,
gender ENUM('M', 'F', 'Other'),
hobbies SET('reading', 'sports', 'music', 'travel')
);
重要提示:
CHAR
vsVARCHAR
:CHAR
适用于定长数据,VARCHAR
适用于变长数据TEXT
vsVARCHAR
:超过255字符建议使用TEXT
类型BLOB
类型不建议存储大文件,建议存储文件路径
# 3.3、日期时间类型(Date and Time Types)
类型 | 格式 | 范围 | 零值 | 常用场景 |
---|---|---|---|---|
DATE | YYYY-MM-DD | 1000-01-01 ~ 9999-12-31 | 0000-00-00 | 出生日期、创建日期 |
TIME | HH:MM:SS | -838:59:59 ~ 838:59:59 | 00:00:00 | 持续时间、营业时间 |
YEAR | YYYY | 1901 ~ 2155 | 0000 | 毕业年份 |
DATETIME | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | 0000-00-00 00:00:00 | 日程安排、日志时间 |
TIMESTAMP | YYYY-MM-DD HH:MM:SS | 1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 | 0000-00-00 00:00:00 | 记录修改时间 |
重要区别:
DATETIME
vsTIMESTAMP
:DATETIME
:不受时区影响,存储什么值就是什么值TIMESTAMP
:受时区影响,会根据服务器时区自动转换TIMESTAMP
:支持自动更新(ON UPDATE CURRENT_TIMESTAMP
)
示例:
CREATE TABLE example_datetime (
id INT PRIMARY KEY,
birthday DATE,
work_time TIME,
graduation_year YEAR,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 插入示例数据
INSERT INTO example_datetime (birthday, work_time, graduation_year)
VALUES ('1990-05-15', '08:30:00', 2012);
# 3.4、JSON类型(MySQL 5.7+)
JSON
:用于存储JSON
格式的数据,如JSON
对象和JSON
数组。适用于半结构化数据,如用户配置、设备信息、商品属性等。
JSON路径表达式:
$
:根元素$.field
:访问对象字段$[index]
:访问数组元素$[*]
:访问所有数组元素$**.field
:递归搜索字段
JSON操作符:
->
:提取JSON值(保持JSON格式)->>
:提取JSON值并转为字符串JSON_EXTRACT()
:提取JSON值JSON_SET()
:设置JSON值JSON_INSERT()
:插入JSON值JSON_REPLACE()
:替换JSON值
示例:
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
attributes JSON
);
-- 插入JSON数据
INSERT INTO products VALUES
(1, 'iPhone 14', '{"color": "blue", "storage": "128GB", "features": ["5G", "Face ID"]}'),
(2, 'MacBook Pro', '{"color": "silver", "memory": "16GB", "screen": 14}');
-- 查询JSON数据
SELECT id, name,
attributes->'$.color' AS color,
attributes->>'$.storage' AS storage,
JSON_EXTRACT(attributes, '$.features[0]') AS first_feature
FROM products;
-- 更新JSON数据
UPDATE products
SET attributes = JSON_SET(attributes, '$.price', 999)
WHERE id = 1;
JSON数据类型:
NULL
、BOOLEAN
、INTEGER
、DOUBLE
、STRING
ARRAY
、OBJECT
、DATE
、TIME
、DATETIME
等
# 3.5、其他重要类型
UUID类型(MySQL 8.0+):
UUID
:128位全局唯一标识符,用于分布式系统中的唯一标识
位类型:
BIT(M)
:存储1-64位的位数据,用于标志位、权限控制等
空间数据类型:
类型 | 描述 | 用途 |
---|---|---|
GEOMETRY | 通用几何类型 | 存储各种几何形状 |
POINT | 点 | 存储坐标位置 |
LINESTRING | 线 | 存储路径、边界 |
POLYGON | 多边形 | 存储区域、范围 |
MULTIPOINT | 多点集合 | 存储多个位置点 |
MULTILINESTRING | 多线集合 | 存储复杂路径 |
MULTIPOLYGON | 多多边形集合 | 存储复杂区域 |
示例:
-- UUID使用示例
CREATE TABLE users (
id BINARY(16) PRIMARY KEY, -- 存储UUID
username VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入UUID
INSERT INTO users (id, username)
VALUES (UUID_TO_BIN(UUID()), 'john_doe');
-- 查询UUID
SELECT BIN_TO_UUID(id) as user_id, username
FROM users;
-- 空间数据示例
CREATE TABLE locations (
id INT PRIMARY KEY,
name VARCHAR(100),
position POINT
);
-- 插入地理位置
INSERT INTO locations (id, name, position)
VALUES (1, '北京', POINT(116.4074, 39.9042));
-- 查询地理位置
SELECT name, ST_X(position) as longitude, ST_Y(position) as latitude
FROM locations;
# 4、MySQL字符集
字符集定义了可以在数据库中使用的字符和编码规则。正确选择字符集对于数据的存储和显示至关重要。
# 4.1、常见字符集对比
字符集 | 字节长度 | 支持字符 | 优势 | 劣势 |
---|---|---|---|---|
latin1 | 1 | 西欧字符 | 存储空间小、速度快 | 不支持中文等多语言 |
gbk | 1-2 | 中文简繁体 | 支持中文 | 不支持其他语言字符 |
utf8 | 1-3 | 大部分Unicode | 兼容性好 | 不支持4字节字符(如emoji) |
utf8mb4 | 1-4 | 完整Unicode | 支持所有字符 | 占用空间稍大 |
# 4.2、utf8 vs utf8mb4 重要区别
⚠️ 重要:
MySQL
中真正的Unicode
字符集是utf8mb4
,而不是utf8
!
区别对比:
特性 | utf8 | utf8mb4 |
---|---|---|
最大字节数 | 3字节 | 4字节 |
emoji支持 | ❌ | ✅ |
标准兼容 | 部分兼容 | 完全兼容 |
存储空间 | 较小 | 稍大 |
推荐使用 | ❌ | ✅ |
示例:
-- 创建不同字符集的表
CREATE TABLE utf8_test (
content TEXT CHARACTER SET utf8
);
CREATE TABLE utf8mb4_test (
content TEXT CHARACTER SET utf8mb4
);
-- 测试emoji存储
INSERT INTO utf8_test VALUES ('Hello 😀'); -- 报错!
INSERT INTO utf8mb4_test VALUES ('Hello 😀'); -- 成功!
# 4.3、字符集设置层级
MySQL
支持在多个层级设置字符集,优先级从高到低:
- 列级别 > 2. 表级别 > 3. 数据库级别 > 4. 服务器级别
-- 服务器级别(my.cnf配置文件)
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
-- 数据库级别
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 表级别
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 列级别
CREATE TABLE mixed_charset (
id INT PRIMARY KEY,
name_cn VARCHAR(50) CHARACTER SET utf8mb4,
name_en VARCHAR(50) CHARACTER SET latin1
);
# 4.4、字符集检查和修改
-- 查看系统字符集设置
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
-- 查看数据库字符集
SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA;
-- 查看表字符集
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database';
-- 修改数据库字符集
ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改表字符集
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 4.5、最佳实践
- 新项目推荐:统一使用
utf8mb4
字符集 - 排序规则推荐:
utf8mb4_unicode_ci
(更准确)或utf8mb4_general_ci
(更快) - 连接字符集:确保客户端连接使用正确字符集
- 数据迁移:从其他字符集迁移时注意数据兼容性
-- 设置连接字符集
SET NAMES utf8mb4;
-- 或在连接URL中指定
jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8mb4&useUnicode=true
# 5、MySQL的存储引擎
MySQL支持多种存储引擎,每种存储引擎具有不同的特点和应用场景。常见的存储引擎有:
InnoDB:InnoDB是MySQL中最常用的存储引擎,它支持事务、行级锁定和外键约束等特性,是一个完全ACID兼容的存储引擎。InnoDB引擎在处理大规模数据时性能表现较好,适用于高并发的OLTP应用场景。
MyISAM:MyISAM是MySQL中最早的存储引擎之一,它不支持事务和行级锁定,但具有较高的读取性能和较低的存储空间开销。MyISAM引擎适用于只读或读写比例较小的应用场景。
MEMORY:MEMORY存储引擎也称为HEAP存储引擎,它将数据存储在内存中,提供了非常快速的数据访问速度,但是数据的持久性较差。MEMORY存储引擎适用于对速度有严格要求的临时表或缓存表等场景。
CSV:CSV存储引擎将数据存储在CSV(逗号分隔值)格式的文件中,适用于需要导出和导入数据的场景。
ARCHIVE:ARCHIVE存储引擎将数据存储在高度压缩的格式中,提供了较高的存储空间效率和较快的数据压缩和解压缩速度,但是读写性能较低。ARCHIVE存储引擎适用于大数据存档和备份等场景。
除了以上常用的存储引擎外,MySQL还支持其他存储引擎,如NDB Cluster存储引擎、Blackhole存储引擎等。选择合适的存储引擎是优化MySQL性能和满足业务需求的关键一步。
# 6、MySQL基本架构
在存储引擎之上,MySQL有Server层,它包括以下组件:
# 6.1、连接器
连接器(Connection Manager)是MySQL的入口,负责建立和维护与客户端之间的连接。连接器支持多种协议,包括TCP/IP、Unix域套接字(Unix Domain Socket)等,它还支持SSL/TLS加密连接和其他高级特性。连接器是MySQL中非常重要的一个组件,直接影响MySQL的并发处理能力和稳定性。
连接器的工作原理如下:
客户端发送连接请求。客户端通过连接器向MySQL服务器发送连接请求。连接请求包括用户名、密码和目标数据库等信息。
连接器进行身份验证。MySQL服务器接收到连接请求后,连接器会对客户端的身份进行验证,包括用户名和密码等信息。如果身份验证失败,则连接器会返回错误信息,并关闭连接。
连接器进行授权。如果身份验证通过,则连接器会对客户端进行授权,以确定客户端可以访问的数据库和数据表等资源。
建立通信管道。连接器会建立一个通信管道,负责客户端和MySQL服务器之间的数据传输。通信管道可以使用多种协议,包括TCP/IP、Unix域套接字等。
处理SQL语句。客户端通过连接器发送SQL语句到MySQL服务器,连接器会对SQL语句进行解析和优化,并将执行计划发送给存储引擎。
获取执行结果。存储引擎执行SQL语句,并将结果返回给连接器。连接器将结果传递给客户端,并关闭连接。
除了以上基本流程,连接器还具有以下特性:
连接池。连接器维护一个连接池,用于重复使用已经建立的连接,从而避免频繁建立和关闭连接的开销。连接池还可以限制同时连接的数量,从而控制MySQL的并发处理能力。
线程安全。连接器是多线程安全的,可以同时处理多个客户端的连接请求,提高MySQL的并发处理能力。
安全性。连接器支持多种身份验证机制,包括基于密码的身份验证和基于证书的身份验证等,从而确保只有合法的客户端才能连接MySQL服务器。
连接器是MySQL中非常重要的一个组件,它决定了MySQL的并发处理能力和稳定性。优化连接器的性能和可靠性是提高MySQL整体性能和可靠性的关键一步。
# 6.2、查询缓存
查询缓存(Query Cache)用于缓存已经执行过的SELECT语句和结果集。如果一个SELECT语句和结果集已经被缓存,那么MySQL会直接返回结果,而不需要执行查询操作,从而提高查询性能。查询缓存可以减少数据库的负载,提高响应速度,并降低了查询的CPU和I/O开销。
查询缓存的工作原理如下:
客户端发送查询请求。客户端通过连接器向MySQL服务器发送SELECT查询请求。
查询缓存查找。MySQL服务器接收到查询请求后,会先查找查询缓存中是否有相应的结果集。如果查询缓存中存在相应的结果集,则MySQL直接返回结果集给客户端,不需要执行查询操作。
执行查询。如果查询缓存中不存在相应的结果集,则MySQL服务器会执行查询操作,并将结果集返回给客户端。同时,MySQL还将查询结果写入查询缓存,以便下次查询时可以直接返回结果。
查询缓存的使用需要注意以下几点:
查询缓存对于只读型的应用性能提升比较明显,对于有写入操作的应用,由于写入操作会使查询缓存失效,所以查询缓存的效果并不显著。
查询缓存需要占用大量的内存,如果内存不足,查询缓存的效果会逐渐降低,甚至会对MySQL的性能造成负面影响。
在MySQL 8.0版本中,查询缓存被废弃,并在以后的版本中删除。这是因为查询缓存可能导致数据库的性能下降,特别是在高并发和动态数据的情况下,而且查询缓存还会占用大量的内存。
查询缓存是MySQL中一个重要的性能优化组件,它可以提高查询性能和降低数据库的负载。但是,在使用查询缓存时需要注意其限制和适用场景,以充分发挥其优势。
# 6.3、分析器
查询分析器(Query Analyzer)负责对SQL语句进行解析和优化,将SQL语句转换为可执行的计划。MySQL的查询优化器会根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划,以提高查询的性能和效率。
查询分析器的工作原理如下:
解析SQL语句。查询分析器首先对SQL语句进行解析,将其分解成多个语句块,包括SELECT语句、FROM子句、WHERE子句等。
生成执行计划。MySQL的查询优化器根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。执行计划是一组操作序列,用于执行SQL查询并生成结果集。执行计划包括访问方法、表连接顺序、过滤条件、排序方式等。
执行查询。MySQL根据执行计划执行SQL查询,并生成结果集。
查询分析器还具有以下特点:
语法分析。查询分析器可以识别和分析SQL语句的语法结构,从而检测和报告错误的语法。
语义分析。查询分析器可以分析SQL语句的语义,检测和报告不合理的查询和错误的查询操作。
优化查询。查询分析器可以优化SQL查询,提高查询性能和效率。MySQL的查询优化器会尽可能利用索引和其他优化技术,生成一个最优的执行计划。
常量表达式计算。查询分析器可以计算SQL查询中的常量表达式,并将其转换为常量,从而减少计算量,提高查询性能。
查询分析器是MySQL中非常重要的一个组件,它可以优化SQL查询,提高查询性能和效率。了解查询分析器的工作原理和特点,对于优化MySQL的性能和解决查询问题非常重要。
# 6.4、优化器
查询优化器(Query Optimizer)优化SQL查询,以提高查询性能和效率。MySQL的查询优化器可以根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。最优的执行计划可以使查询更快、更高效地执行,从而提高MySQL的整体性能。
查询优化器的工作原理如下:
解析SQL语句。查询优化器首先对SQL语句进行解析,将其分解成多个语句块,包括SELECT语句、FROM子句、WHERE子句等。
分析查询。查询优化器分析查询,包括表的选择、连接类型、过滤条件、排序方式等,以生成一个可能的执行计划。
生成执行计划。MySQL的查询优化器会根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。执行计划是一组操作序列,用于执行SQL查询并生成结果集。执行计划包括访问方法、表连接顺序、过滤条件、排序方式等。
执行查询。MySQL根据执行计划执行SQL查询,并生成结果集。
MySQL的查询优化器还具有以下特点:
选择最优索引。查询优化器会根据查询条件和索引的选择性,选择最优的索引,以提高查询性能和效率。
预测行数。查询优化器可以预测查询语句返回的行数,从而帮助MySQL优化查询执行计划,提高查询性能。
确定表连接顺序。查询优化器可以确定表连接顺序,以减少查询的I/O操作,提高查询性能。
优化常量表达式。查询优化器可以优化SQL查询中的常量表达式,从而减少计算量,提高查询性能。
查询优化器是MySQL中非常重要的一个组件,它可以优化SQL查询,提高查询性能和效率。
# 6.5、执行器
执行器(Executor)它负责执行SQL语句并返回结果集。执行器的主要工作是管理查询缓存、读取数据、排序、分组、聚合、计算表达式等,以生成结果集并将其返回给客户端。
执行器的工作流程如下:
客户端发送SQL查询请求。客户端通过连接器向MySQL服务器发送SQL查询请求。
查询缓存查找。MySQL服务器接收到查询请求后,会先查找查询缓存中是否有相应的结果集。如果查询缓存中存在相应的结果集,则MySQL直接返回结果集给客户端,不需要执行查询操作。
执行查询。如果查询缓存中不存在相应的结果集,则MySQL服务器会根据查询优化器生成的执行计划,执行查询操作,并将结果集返回给执行器。
处理结果集。执行器对结果集进行排序、分组、聚合、计算表达式等操作,并将最终结果集返回给客户端。
执行器还具有以下特点:
管理事务。执行器负责管理MySQL的事务,包括事务的开始、提交和回滚等操作。
锁管理。执行器负责管理MySQL的锁机制,包括行级锁和表级锁等,以保证并发性和一致性。
资源管理。执行器负责管理MySQL的资源,包括缓存、连接池和线程池等,以提高MySQL的性能和效率。
执行器是MySQL
中非常重要的一个组件,它直接影响MySQL
的查询性能和稳定性。优化执行器的性能和可靠性是提高MySQL
整体性能和可靠性的关键一步。
# 三、MySQL 8.0 重要新特性
MySQL 8.0
是一个里程碑版本,引入了许多重要的新特性和改进,显著提升了性能、安全性和易用性。
# 1、窗口函数(Window Functions)
窗口函数允许在不使用GROUP BY
的情况下进行聚合计算,极大简化了复杂查询。
语法:
function_name() OVER (
[PARTITION BY column1, column2, ...]
[ORDER BY column3, column4, ...]
[ROWS/RANGE BETWEEN ... AND ...]
)
常用窗口函数:
函数类型 | 函数名 | 说明 |
---|---|---|
排名函数 | ROW_NUMBER() | 连续排名 |
RANK() | 跳跃排名 | |
DENSE_RANK() | 密集排名 | |
聚合函数 | SUM() OVER() | 累计求和 |
AVG() OVER() | 移动平均 | |
COUNT() OVER() | 累计计数 | |
偏移函数 | LAG() | 获取前面的值 |
LEAD() | 获取后面的值 | |
FIRST_VALUE() | 第一个值 | |
LAST_VALUE() | 最后一个值 |
实际应用示例:
-- 销售排名分析
SELECT
employee_id,
sales_amount,
-- 整体排名
ROW_NUMBER() OVER (ORDER BY sales_amount DESC) as overall_rank,
-- 部门内排名
RANK() OVER (PARTITION BY department_id ORDER BY sales_amount DESC) as dept_rank,
-- 累计销售额
SUM(sales_amount) OVER (ORDER BY sales_date ROWS UNBOUNDED PRECEDING) as cumulative_sales,
-- 移动平均(近3个月)
AVG(sales_amount) OVER (ORDER BY sales_date ROWS 2 PRECEDING) as moving_avg,
-- 同比增长
LAG(sales_amount, 12) OVER (PARTITION BY employee_id ORDER BY sales_date) as last_year_sales
FROM sales_data;
# 2、CTE(公共表表达式)
CTE
提供了更清晰的查询结构,支持递归查询,替代复杂的子查询。
基础语法:
WITH cte_name AS (
SELECT ...
)
SELECT ... FROM cte_name;
递归CTE示例:
-- 组织架构层级查询
WITH RECURSIVE org_hierarchy AS (
-- 锚点:顶级管理者
SELECT employee_id, name, manager_id, 1 as level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归:下属员工
SELECT e.employee_id, e.name, e.manager_id, oh.level + 1
FROM employees e
INNER JOIN org_hierarchy oh ON e.manager_id = oh.employee_id
)
SELECT * FROM org_hierarchy ORDER BY level, employee_id;
-- 复杂数据分析CTE
WITH
monthly_sales AS (
SELECT
DATE_FORMAT(order_date, '%Y-%m') as month,
SUM(amount) as total_sales
FROM orders
WHERE order_date >= '2023-01-01'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
),
sales_growth AS (
SELECT
month,
total_sales,
LAG(total_sales) OVER (ORDER BY month) as prev_month_sales,
(total_sales - LAG(total_sales) OVER (ORDER BY month)) /
LAG(total_sales) OVER (ORDER BY month) * 100 as growth_rate
FROM monthly_sales
)
SELECT * FROM sales_growth WHERE growth_rate > 10;
# 3、隐藏索引(Invisible Indexes)
隐藏索引允许在不删除索引的情况下测试索引的影响,便于索引优化。
-- 创建隐藏索引
CREATE INDEX idx_customer_email ON customers(email) INVISIBLE;
-- 现有索引设为隐藏
ALTER TABLE customers ALTER INDEX idx_customer_name INVISIBLE;
-- 显示索引
ALTER TABLE customers ALTER INDEX idx_customer_name VISIBLE;
-- 查看索引状态
SELECT
INDEX_NAME,
IS_VISIBLE
FROM information_schema.statistics
WHERE TABLE_NAME = 'customers';
-- 优化器忽略隐藏索引,除非强制使用
SELECT * FROM customers USE INDEX (idx_customer_email) WHERE email = 'john@example.com';
# 4、降序索引
真正的降序索引支持,提升ORDER BY DESC
查询性能。
-- 创建降序索引
CREATE INDEX idx_order_date_desc ON orders(order_date DESC);
-- 混合升降序索引
CREATE INDEX idx_complex ON orders(customer_id ASC, order_date DESC, amount ASC);
-- 性能对比
EXPLAIN SELECT * FROM orders ORDER BY order_date DESC LIMIT 10;
# 5、原子DDL操作
DDL
操作现在是原子的,要么全部成功,要么全部失败,提高了数据一致性。
-- 原子性重命名操作
RENAME TABLE
old_table1 TO new_table1,
old_table2 TO new_table2,
old_table3 TO new_table3;
-- 失败时所有操作都会回滚
DROP TABLE table1, table2, table3; -- 如果任一表不存在,所有删除都会回滚
# 6、JSON增强功能
更强大的JSON
处理能力,包括聚合函数和表函数。
-- JSON聚合函数
SELECT
department,
JSON_ARRAYAGG(employee_name) as employees,
JSON_OBJECTAGG(employee_id, salary) as salary_map
FROM employees
GROUP BY department;
-- JSON表函数
SELECT *
FROM JSON_TABLE(
'[{"name": "John", "age": 30}, {"name": "Jane", "age": 25}]',
'$[*]' COLUMNS (
name VARCHAR(50) PATH '$.name',
age INT PATH '$.age'
)
) as jt;
-- JSON合并和更新
UPDATE products
SET attributes = JSON_MERGE_PATCH(
attributes,
'{"updated_at": "2023-01-01", "version": 2}'
)
WHERE id = 1;
# 7、性能提升
多值索引:
-- 为JSON数组创建多值索引
CREATE INDEX idx_tags ON articles((CAST(tags AS CHAR(255) ARRAY)));
-- 查询数组元素
SELECT * FROM articles WHERE 'mysql' MEMBER OF(tags);
直方图统计:
-- 创建直方图提升查询优化
ANALYZE TABLE orders UPDATE HISTOGRAM ON order_date, amount WITH 100 BUCKETS;
-- 查看直方图信息
SELECT * FROM information_schema.column_statistics;
# 8、安全增强
默认身份验证:
-- 新的身份验证插件(默认)
CREATE USER 'newuser'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'password';
-- 角色管理
CREATE ROLE 'app_read', 'app_write';
GRANT SELECT ON mydb.* TO 'app_read';
GRANT INSERT, UPDATE ON mydb.* TO 'app_write';
GRANT 'app_read', 'app_write' TO 'appuser'@'localhost';
重要注意事项:
MySQL 8.0
移除了查询缓存- 默认字符集改为
utf8mb4
- 性能模式默认启用
- 二进制日志默认启用
- 最低支持版本要求较高
# 9、安装与配置MySQL
# 9.1、安装要求
根据您的操作系统和硬件配置选择合适的MySQL版本。确保您的系统满足安装MySQL的最低要求,如内存、磁盘空间和操作系统版本等。
# 9.2、环境变量配置
安装完成后,需要将MySQL的可执行文件路径添加到系统的环境变量中,以便在命令行中直接使用mysql
命令。
# 9.3、验证安装
在命令行中输入mysql --version
,如果显示出MySQL的版本信息,则表示安装成功。接下来,可以使用mysql -u root -p
命令登录到MySQL数据库,并进行进一步的配置和使用。
# 四、SQL基础
# 1、SQL语言的概念与分类
SQL
(结构化查询语言,Structured Query Language)是用于管理关系型数据库的一种标准编程语言。它提供了一套完整的数据库操作指令,让用户能够创建、查询、更新和删除数据库中的数据。
根据功能和用途,SQL
可以分为以下四种类型:
# 1.1、DDL(数据定义语言)
**DDL
(Data Definition Language)**主要用于定义和管理数据库对象(如数据库、表、视图、索引等)的结构。
语句 | 功能 | 语法示例 |
---|---|---|
CREATE | 创建数据库对象 | CREATE TABLE users (...) |
ALTER | 修改对象结构 | ALTER TABLE users ADD COLUMN email VARCHAR(100) |
DROP | 删除数据库对象 | DROP TABLE users |
TRUNCATE | 清空表数据但保留结构 | TRUNCATE TABLE users |
实际应用示例:
-- 创建数据库
CREATE DATABASE ecommerce CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建表
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_created_at (created_at)
);
-- 修改表结构
ALTER TABLE customers
ADD COLUMN address TEXT,
ADD COLUMN status ENUM('active', 'inactive') DEFAULT 'active';
-- 添加外键约束
ALTER TABLE orders
ADD CONSTRAINT fk_customer_id
FOREIGN KEY (customer_id) REFERENCES customers(id);
# 1.2、DML(数据操作语言)
**DML
(Data Manipulation Language)**主要用于对数据库中的数据进行增删改查操作。
语句 | 功能 | 特点 |
---|---|---|
SELECT | 查询数据 | 最常用的SQL语句 |
INSERT | 插入数据 | 支持单行和批量插入 |
UPDATE | 更新数据 | 可配合WHERE条件 |
DELETE | 删除数据 | 可配合WHERE条件 |
# 1.3、DCL(数据控制语言)
**DCL
(Data Control Language)**主要用于控制数据库的访问权限和数据安全性。
语句 | 功能 | 示例 |
---|---|---|
GRANT | 授予权限 | GRANT SELECT ON mydb.* TO 'user'@'localhost' |
REVOKE | 撤销权限 | REVOKE INSERT ON mydb.users FROM 'user'@'localhost' |
# 1.4、TCL(事务控制语言)
**TCL
(Transaction Control Language)**用于管理数据库事务。
语句 | 功能 | 说明 |
---|---|---|
BEGIN / START TRANSACTION | 开始事务 | 标记事务开始 |
COMMIT | 提交事务 | 永久保存更改 |
ROLLBACK | 回滚事务 | 撤销未提交的更改 |
SAVEPOINT | 设置保存点 | 部分回滚的标记点 |
# 2、SQL语句的基本结构与书写规范
SQL
语句通常由关键字、表达式和子句组成。关键字是SQL
语言的保留字,如SELECT
、FROM
、WHERE
等。
# 2.1、SQL基本结构
查询语句结构:
SELECT column_list -- 选择列
FROM table_name -- 指定表
[WHERE condition] -- 过滤条件
[GROUP BY column] -- 分组
[HAVING condition] -- 分组后筛选
[ORDER BY column] -- 排序
[LIMIT offset, count] -- 限制结果数量
执行顺序:
FROM
- 确定数据来源WHERE
- 行级过滤GROUP BY
- 分组HAVING
- 分组级过滤SELECT
- 选择列ORDER BY
- 排序LIMIT
- 限制结果
# 2.2、书写规范最佳实践
规范 | 推荐做法 | 示例 |
---|---|---|
关键字 | 使用大写 | SELECT , FROM , WHERE |
标识符 | 使用小写+下划线 | user_name , order_date |
缩进 | 使用2-4个空格 | 见下方示例 |
换行 | 每个主要子句一行 | 见下方示例 |
注释 | 使用-- 或/* */ | -- 查询活跃用户 |
标准格式示例:
-- 查询最近30天活跃用户的订单统计
SELECT
u.user_id,
u.username,
COUNT(o.order_id) AS total_orders,
SUM(o.amount) AS total_amount,
AVG(o.amount) AS avg_order_amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
WHERE o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND u.status = 'active'
GROUP BY u.user_id, u.username
HAVING COUNT(o.order_id) >= 5
ORDER BY total_amount DESC, total_orders DESC
LIMIT 20;
# 3、数据库、表的创建与删除
在MySQL
中,使用DDL
语句来创建和管理数据库结构。以下是常见操作的详细示例:
# 3.1、数据库操作
创建数据库:
-- 基本创建
CREATE DATABASE ecommerce;
-- 指定字符集和排序规则
CREATE DATABASE ecommerce
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 条件创建(如果不存在)
CREATE DATABASE IF NOT EXISTS ecommerce
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
查看和选择数据库:
-- 显示所有数据库
SHOW DATABASES;
-- 选择使用数据库
USE ecommerce;
-- 显示当前数据库
SELECT DATABASE();
删除数据库:
-- 删除数据库(谨慎操作!)
DROP DATABASE ecommerce;
-- 条件删除
DROP DATABASE IF EXISTS ecommerce;
# 3.2、表操作详解
创建表的完整语法:
CREATE TABLE [IF NOT EXISTS] table_name (
column1 data_type [column_constraints],
column2 data_type [column_constraints],
...
[table_constraints]
) [table_options];
实际业务表示例:
-- 用户表
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
phone VARCHAR(20) COMMENT '手机号',
birth_date DATE COMMENT '出生日期',
gender ENUM('M', 'F', 'Other') COMMENT '性别',
status ENUM('active', 'inactive', 'banned') DEFAULT 'active' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
INDEX idx_email (email),
INDEX idx_phone (phone),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
COMMENT='用户表';
-- 订单表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
order_number VARCHAR(50) NOT NULL UNIQUE,
total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
order_status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
payment_method ENUM('credit_card', 'debit_card', 'paypal', 'bank_transfer') NOT NULL,
shipping_address TEXT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 外键约束
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE ON UPDATE CASCADE,
-- 索引
INDEX idx_user_id (user_id),
INDEX idx_order_status (order_status),
INDEX idx_order_date (order_date),
INDEX idx_order_number (order_number)
) ENGINE=InnoDB COMMENT='订单表';
表修改操作:
-- 添加列
ALTER TABLE users ADD COLUMN last_login TIMESTAMP NULL COMMENT '最后登录时间';
-- 修改列
ALTER TABLE users MODIFY COLUMN phone VARCHAR(30) COMMENT '手机号码';
-- 重命名列
ALTER TABLE users CHANGE COLUMN birth_date birthday DATE COMMENT '生日';
-- 删除列
ALTER TABLE users DROP COLUMN last_login;
-- 添加索引
ALTER TABLE users ADD INDEX idx_birthday (birthday);
-- 删除索引
ALTER TABLE users DROP INDEX idx_birthday;
-- 添加外键
ALTER TABLE orders ADD CONSTRAINT fk_user_orders
FOREIGN KEY (user_id) REFERENCES users(user_id);
删除表:
-- 删除表(谨慎操作!)
DROP TABLE orders;
-- 条件删除
DROP TABLE IF EXISTS orders;
-- 清空表数据但保留结构
TRUNCATE TABLE orders; -- 重置AUTO_INCREMENT
DELETE FROM orders; -- 不重置AUTO_INCREMENT
查看表信息:
-- 显示所有表
SHOW TABLES;
-- 查看表结构
DESC users;
DESCRIBE users;
SHOW COLUMNS FROM users;
-- 查看创建表的SQL
SHOW CREATE TABLE users;
-- 查看表状态
SHOW TABLE STATUS LIKE 'users';
# 4、数据的增删改查操作(CRUD)
使用DML
语句进行数据操作是数据库使用的核心。以下是详细的CRUD
操作示例:
# 4.1、查询数据(READ)
基础查询语法:
SELECT column_list
FROM table_name
[WHERE condition]
[GROUP BY column]
[HAVING condition]
[ORDER BY column [ASC|DESC]]
[LIMIT offset, count];
实际查询示例:
-- 基础查询
SELECT user_id, username, email FROM users;
-- 条件查询
SELECT * FROM users
WHERE status = 'active'
AND created_at >= '2023-01-01';
-- 模糊查询
SELECT * FROM users
WHERE username LIKE 'john%'
OR email LIKE '%@gmail.com';
-- 范围查询
SELECT * FROM orders
WHERE total_amount BETWEEN 100 AND 1000
AND order_date IN ('2023-01-01', '2023-01-02');
-- 空值查询
SELECT * FROM users WHERE phone IS NOT NULL;
-- 排序查询
SELECT * FROM orders
ORDER BY order_date DESC, total_amount ASC;
-- 限制结果
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10; -- 最新的10个用户
-- 分页查询
SELECT * FROM users
ORDER BY user_id
LIMIT 20, 10; -- 跳过20条,取10条(第3页)
聚合查询:
-- 统计查询
SELECT
COUNT(*) AS total_users,
COUNT(DISTINCT email) AS unique_emails,
AVG(TIMESTAMPDIFF(YEAR, birth_date, CURDATE())) AS avg_age
FROM users
WHERE status = 'active';
-- 分组统计
SELECT
DATE(created_at) AS register_date,
COUNT(*) AS daily_registrations
FROM users
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(created_at)
ORDER BY register_date;
-- 分组筛选
SELECT
user_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5 AND SUM(total_amount) > 1000;
多表查询:
-- 内连接
SELECT
u.username,
o.order_number,
o.total_amount,
o.order_date
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
WHERE u.status = 'active';
-- 左连接(包含没有订单的用户)
SELECT
u.username,
COUNT(o.order_id) AS order_count,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.username;
-- 子查询
SELECT * FROM users
WHERE user_id IN (
SELECT DISTINCT user_id
FROM orders
WHERE total_amount > 500
);
# 4.2、插入数据(CREATE)
插入语法:
-- 单行插入
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
-- 多行插入
INSERT INTO table_name (column1, column2, ...)
VALUES
(value1a, value2a, ...),
(value1b, value2b, ...),
(value1c, value2c, ...);
实际插入示例:
-- 插入新用户
INSERT INTO users (username, email, password_hash, phone, gender)
VALUES ('john_doe', 'john@example.com', SHA2('password123', 256), '13800138000', 'M');
-- 批量插入用户
INSERT INTO users (username, email, password_hash, status)
VALUES
('alice', 'alice@example.com', SHA2('pass1', 256), 'active'),
('bob', 'bob@example.com', SHA2('pass2', 256), 'active'),
('charlie', 'charlie@example.com', SHA2('pass3', 256), 'inactive');
-- 插入订单
INSERT INTO orders (user_id, order_number, total_amount, payment_method, shipping_address)
VALUES (
1,
CONCAT('ORD', DATE_FORMAT(NOW(), '%Y%m%d'), LPAD(LAST_INSERT_ID(), 6, '0')),
299.99,
'credit_card',
'北京市朝阳区某某街道123号'
);
-- 忽略重复数据
INSERT IGNORE INTO users (username, email, password_hash)
VALUES ('existing_user', 'existing@example.com', 'hash');
-- 更新重复数据
INSERT INTO users (username, email, password_hash)
VALUES ('john_doe', 'newemail@example.com', 'newhash')
ON DUPLICATE KEY UPDATE
email = VALUES(email),
updated_at = CURRENT_TIMESTAMP;
# 4.3、更新数据(UPDATE)
更新语法:
UPDATE table_name
SET column1 = value1, column2 = value2, ...
[WHERE condition]
[ORDER BY column]
[LIMIT count];
实际更新示例:
-- 更新单个用户信息
UPDATE users
SET
email = 'newemail@example.com',
phone = '13900139000',
updated_at = CURRENT_TIMESTAMP
WHERE user_id = 1;
-- 批量更新用户状态
UPDATE users
SET status = 'inactive'
WHERE last_login < DATE_SUB(NOW(), INTERVAL 6 MONTH);
-- 基于计算的更新
UPDATE orders
SET total_amount = total_amount * 1.1 -- 涨价10%
WHERE order_status = 'pending'
AND order_date >= '2023-01-01';
-- 多表更新
UPDATE users u
INNER JOIN (
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
) o ON u.user_id = o.user_id
SET u.user_level = CASE
WHEN o.order_count >= 10 THEN 'VIP'
WHEN o.order_count >= 5 THEN 'Gold'
ELSE 'Regular'
END;
-- 安全更新(限制更新数量)
UPDATE users
SET status = 'verified'
WHERE email_verified = 1
ORDER BY created_at
LIMIT 100;
# 4.4、删除数据(DELETE)
删除语法:
DELETE FROM table_name
[WHERE condition]
[ORDER BY column]
[LIMIT count];
实际删除示例:
-- 删除指定用户
DELETE FROM users WHERE user_id = 123;
-- 条件删除
DELETE FROM users
WHERE status = 'banned'
AND created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- 批量删除(限制数量,避免长时间锁表)
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY created_at
LIMIT 1000;
-- 多表删除
DELETE u, p
FROM users u
LEFT JOIN profiles p ON u.user_id = p.user_id
WHERE u.status = 'deleted';
-- 安全删除确认
-- 先查看要删除的数据
SELECT COUNT(*) FROM orders WHERE order_status = 'cancelled' AND order_date < '2022-01-01';
-- 确认后再删除
DELETE FROM orders WHERE order_status = 'cancelled' AND order_date < '2022-01-01';
# 4.5、事务操作示例
-- 转账操作示例
START TRANSACTION;
-- 检查余额
SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE;
-- 如果余额充足,执行转账
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 记录转账日志
INSERT INTO transfer_logs (from_account, to_account, amount, transfer_time)
VALUES (1, 2, 100, NOW());
-- 提交事务
COMMIT;
-- 如果出错则回滚
-- ROLLBACK;
CRUD操作最佳实践:
- 查询优化:合理使用索引,避免全表扫描
- 批量操作:大量数据操作时使用批量处理
- 事务控制:重要操作使用事务保证数据一致性
- 安全防护:始终使用
WHERE
条件,避免误操作 - 性能监控:使用
EXPLAIN
分析查询性能
# 五、函数
# 1、字符串函数
在处理文本数据时,字符串函数具有显著的作用。MySQL 提供了众多的内置字符串函数,下面是一些常用的函数及其简述:
# 1.1、数据转换函数
ASCII(string)
: 返回字符串的第一个字符的 ASCII 码值。- 示例:
SELECT ASCII('A');
返回结果为65
.
- 示例:
BIN(number)
: 将整数转换为二进制字符串。- 示例:
SELECT BIN(10);
返回结果为1010
.
- 示例:
CAST(string AS type)
: 将字符串转换为特定的数据类型。- 示例:
SELECT CAST('42.42' AS DECIMAL);
返回结果为42.42
.
- 示例:
CHAR(number, ...)
: 将多个 ASCII 码转换为字符串。- 示例:
SELECT CHAR(65, 66, 67);
返回结果为ABC
.
- 示例:
CONVERT(string, type)
: 将字符串转换为指定的字符集。- 示例:
SELECT CONVERT('hello', BINARY);
返回结果为二进制格式的字符串。
- 示例:
FROM_BASE64(string)
: 将 Base64 编码的字符串解码为原始字符串。- 示例:
SELECT FROM_BASE64('SGVsbG8gV29ybGQh');
返回结果为Hello World!
.
- 示例:
HEX(number)
: 将数值转换为十六进制字符串。- 示例:
SELECT HEX(3055);
返回结果为BEF
.
- 示例:
OCT(number)
: 将数值转换为八进制字符串。- 示例:
SELECT OCT(42);
返回结果为52
.
- 示例:
UNHEX(string)
: 将十六进制字符串转换为原始字符串。- 示例:
SELECT UNHEX('48656C6C6F');
返回结果为Hello
.
- 示例:
# 1.2、字符串操作函数
BIT_LENGTH(string)
: 返回字符串的位数。- 示例:
SELECT BIT_LENGTH('hello');
返回结果为40
.
- 示例:
CHAR_LENGTH(string) / CHARACTER_LENGTH(string)
: 返回字符串的字符数。- 示例:
SELECT CHAR_LENGTH('apple');
返回结果为5
.
- 示例:
CONCAT(string1, string2, ...)
: 连接多个字符串。- 示例:
SELECT CONCAT('Hello ', 'World');
返回结果为Hello World
.
- 示例:
CONCAT_WS(separator, string1, string2, ...)
: 使用指定的分隔符连接多个字符串。- 示例:
SELECT CONCAT_WS(', ', 'apple', 'banana', 'cherry');
返回结果为apple, banana, cherry
.
- 示例:
INSERT(string, start, length, new_string)
: 在字符串的特定位置插入新字符串。- 示例:
SELECT INSERT('MySQL', 2, 0, 'SQL ');
返回结果为MSQLySQL SQL
.
- 示例:
LCASE(string) / LOWER(string)
: 将字符串中的所有字符转换为小写。- 示例:
SELECT LCASE('Hello');
返回结果为hello
.
- 示例:
LEFT(string, length)
: 返回字符串左侧的特定长度的子字符串。- 示例:
SELECT LEFT('MySQL', 3);
返回结果为MyS
.
- 示例:
LENGTH(string)
: 返回字符串的长度。- 示例:
SELECT LENGTH('MySQL');
返回结果为5
.
- 示例:
LPAD(string, length, pad_string)
: 在字符串左侧填充指定字符以达到特定长度。- 示例:
SELECT LPAD('hello', 10, 'X');
返回结果为XXXXXhello
.
- 示例:
LTRIM(string)
: 去除字符串左侧的空格。- 示例:
SELECT LTRIM(' MySQL ');
返回结果为MySQL
.
- 示例:
MID(string, start, length)
: 返回字符串的特定部分。- 示例:
SELECT MID('MySQL', 2, 3);
返回结果为ySQ
.
- 示例:
REPEAT(string, count)
: 重复字符串指定的次数。- 示例:
SELECT REPEAT('MySQL', 2);
返回结果为MySQLMySQL
.
- 示例:
REPLACE(string, search_string, replacement_string)
: 替换字符串中的指定字符。- 示例:
SELECT REPLACE('Hello World', 'World', 'Universe');
返回结果为Hello Universe
.
- 示例:
REVERSE(string)
: 反转字符串。- 示例:
SELECT REVERSE('MySQL');
返回结果为LQSyM
.
- 示例:
RIGHT(string, length)
: 返回字符串右侧的特定长度的子字符串。- 示例:
SELECT RIGHT('MySQL', 3);
返回结果为QL
.
- 示例:
RPAD(string, length, pad_string)
: 在字符串右侧填充指定字符以达到特定长度。- 示例:
SELECT RPAD('hello', 10, 'X');
返回结果为helloXXXXX
.
- 示例:
RTRIM(string)
: 去除字符串右侧的空格。- 示例:
SELECT RTRIM(' MySQL ');
返回结果为MySQL
.
- 示例:
SPACE(length)
: 返回指定长度的空格字符串。- 示例:
SELECT SPACE(5);
返回结果为.
- 示例:
SUBSTRING(string, start, length)
: 从字符串中提取子字符串。- 示例:
SELECT SUBSTRING('MySQL', 2, 3);
返回结果为ySQ
.
- 示例:
SUBSTRING_INDEX(string, delimiter, count)
: 返回分隔符分隔的字符串的某个部分。- 示例:
SELECT SUBSTRING_INDEX('a,b,c,d', ',', 2);
返回结果为a,b
.
- 示例:
TRIM(string)
: 去除字符串开头和末尾的空格。- 示例:
SELECT TRIM(' MySQL ');
返回结果为MySQL
.
- 示例:
UCASE(string)
: 将字符串中的所有字符转换为大写。- 示例:
SELECT UCASE('hello');
返回结果为HELLO
.
- 示例:
# 1.3、其他字符串函数
ELT(index, string1, string2, ...)
: 返回字符串列表中指定索引的字符串。- 示例:
SELECT ELT(2, 'apple', 'banana', 'cherry');
返回结果为banana
.
- 示例:
EXPORT_SET(bits, on, off, separator, width)
: 将二进制数转换为由特定字符串表示的值。- 示例:
SELECT EXPORT_SET(5, 'Y', 'N', ',', 8);
返回结果为N,N,Y,N,N,N,N,N
.
- 示例:
FIELD(string, string1, string2, ...)
: 返回字符串在指定字符串列表中的位置。- 示例:
SELECT FIELD('apple', 'orange', 'banana', 'apple');
返回结果为3
.
- 示例:
FIND_IN_SET(string, string_list)
: 返回字符串在逗号分隔的字符串列表中的位置。- 示例:
SELECT FIND_IN_SET('apple', 'orange,banana,apple,grape');
返回结果为3
.
- 示例:
FORMAT(number, decimals)
: 将数值格式化为特定格式的字符串。- 示例:
SELECT FORMAT(12345.6789, 2);
返回结果为12,345.68
.
- 示例:
INSTR(string, substring) / LOCATE(substring, string, start) / POSITION(substring IN string)
: 返回子字符串在字符串中的位置。- 示例:
SELECT INSTR('hello world', 'world');
返回结果为7
.
- 示例:
LOAD_FILE(filename)
: 将文件内容读入字符串。- 示例:
SELECT LOAD_FILE('/path/to/file.txt');
返回文件内容的字符串形式。
- 示例:
MAKE_SET(bits, string1, string2, ...)
: 将二进制数转换为由特定字符串表示的集合。- 示例:
SELECT MAKE_SET(1 | 4, 'apple', 'banana', 'cherry', 'date');
返回结果为apple,cherry
.
- 示例:
ORD(char)
: 返回字符的 ASCII 码值。- 示例:
SELECT ORD('A');
返回结果为65
.
- 示例:
QUOTE(string)
: 为字符串添加引号。- 示例:
SELECT QUOTE('It\'s a sunny day');
返回结果为'It\'s a sunny day'
.
- 示例:
SOUNDEX(string)
: 返回字符串的 SOUNDEX 编码。- 示例:
SELECT SOUNDEX('MySQL');
返回结果为M240
.
- 示例:
STRCMP(string1, string2)
: 比较两个字符串的大小关系。- 示例:
SELECT STRCMP('apple', 'banana');
返回结果为-1
.
- 示例:
这些字符串函数可以用于字符串的操作、转换、匹配等操作,能够满足 MySQL 中大部分的字符串处理需求。
# 2、数值函数
数值函数主要用于处理各种数值数据,例如整数和浮点数。MySQL提供了许多内置的数值函数:
# 2.1、基本数学函数
ABS(number)
: 返回一个数值的绝对值。- 示例:
SELECT ABS(-10);
返回结果为10
.
- 示例:
CEIL(number) / CEILING(number)
: 返回一个数值的向上取整值。- 示例:
SELECT CEIL(3.14);
返回结果为4
.
- 示例:
FLOOR(number)
: 返回一个数值的向下取整值。- 示例:
SELECT FLOOR(3.9);
返回结果为3
.
- 示例:
MOD(number, divisor)
: 返回两个数值相除的余数.- 示例:
SELECT MOD(10, 3);
返回结果为1
.
- 示例:
PI()
: 返回圆周率π的值.- 示例:
SELECT PI();
返回结果为3.141592653589793
.
- 示例:
POW(number, exponent) / POWER(number, exponent)
: 返回一个数值的指定幂次方.- 示例:
SELECT POW(2, 3);
返回结果为8
.
- 示例:
ROUND(number, decimals)
: 将一个数值四舍五入到指定的小数位数.- 示例:
SELECT ROUND(3.14159, 2);
返回结果为3.14
.
- 示例:
SIGN(number)
: 返回一个数值的符号(1、0或-1).- 示例:
SELECT SIGN(-10);
返回结果为-1
.
- 示例:
SQRT(number)
: 返回一个数值的平方根.- 示例:
SELECT SQRT(25);
返回结果为5
.
- 示例:
TRUNCATE(number, decimals)
: 将一个数值截断到指定的小数位数.- 示例:
SELECT TRUNCATE(3.14159, 2);
返回结果为3.14
.
- 示例:
# 2.2、三角函数
ACOS(number)
: 返回一个数值的反余弦值.- 示例:
SELECT ACOS(0.5);
返回结果为1.0471975511965979
.
- 示例:
ASIN(number)
: 返回一个数值的反正弦值.- 示例:
SELECT ASIN(0.5);
返回结果为0.5235987755982989
.
- 示例:
ATAN(number)
: 返回一个数值的反正切值.- 示例:
SELECT ATAN(1);
返回结果为0.7853981633974483
.
- 示例:
ATAN2(y, x)
: 返回y/x的反正切值.- 示例:
SELECT ATAN2(1, 1);
返回结果为0.7853981633974483
.
- 示例:
COS(number)
: 返回一个数值的余弦值.- 示例:
SELECT COS(0);
返回结果为1
.
- 示例:
COT(number)
: 返回一个数值的余切值.- 示例:
SELECT COT(1);
返回结果为0.6420926159343306
.
- 示例:
SIN(number)
: 返回一个数值的正弦值.- 示例:
SELECT SIN(0);
返回结果为0
.
- 示例:
TAN(number)
: 返回一个数值的正切值.- 示例:
SELECT TAN(0);
返回结果为0
.
- 示例:
# 2.3、对数和指数函数
EXP(number)
: 返回一个数值的指数值.- 示例:
SELECT EXP(1);
返回结果为2.718281828459045
.
- 示例:
LN(number)
: 返回一个数值的自然对数.- 示例:
SELECT LN(10);
返回结果为2.302585092994046
.
- 示例:
LOG(number, base)
: 返回一个数值的指定底数的对数.- 示例:
SELECT LOG(1000, 10);
返回结果为3
.
- 示例:
LOG10(number)
: 返回一个数值的以10为底数的对数.- 示例:
SELECT LOG10(100);
返回结果为2
.
- 示例:
# 2.4、角度和弧度转换
DEGREES(number)
: 将弧度转换为角度.- 示例:
SELECT DEGREES(1.57);
返回结果为89.95437383553924
.
- 示例:
RADIANS(number)
: 将角度转换为弧度.- 示例:
SELECT RADIANS(90);
返回结果为1.5707963267948966
.
- 示例:
# 2.5、随机数生成
RAND()
: 返回一个0~1之间的随机数.- 示例:
SELECT RAND();
返回一个介于 0 和 1 之间的随机数。
- 示例:
这些数值函数可以用于数值的计算、转换等操作,能够满足MySQL中大部分的数值处理需求。
# 3、日期和时间函数
处理日期和时间数据时,日期和时间函数非常有用。以下是一些常用的日期和时间函数:
# 3.1、日期/时间增减函数
ADDDATE(date, INTERVAL value unit) / DATE_ADD(date, INTERVAL value unit)
: 将日期加上指定的时间间隔.- 示例:
SELECT ADDDATE('2024-11-01', INTERVAL 3 DAY);
返回结果为2024-11-04
.
- 示例:
ADDTIME(time1, time2)
: 将两个时间相加.- 示例:
SELECT ADDTIME('10:00:00', '02:30:00');
返回结果为12:30:00
.
- 示例:
DATE_SUB(date, INTERVAL value unit) / SUBDATE(date, INTERVAL value unit)
: 将日期减去指定的时间间隔.- 示例:
SELECT DATE_SUB('2024-11-10', INTERVAL 1 WEEK);
返回结果为2024-11-03
.
- 示例:
SUBTIME(time1, time2)
: 将一个时间值减去另一个时间值.- 示例:
SELECT SUBTIME('14:30:00', '02:15:00');
返回结果为12:15:00
.
- 示例:
# 3.2、日期/时间获取函数
CURDATE() / CURRENT_DATE()
: 返回当前日期.- 示例:
SELECT CURDATE();
返回当前日期2024-11-06
.
- 示例:
CURRENT_TIME() / CURTIME()
: 返回当前时间.- 示例:
SELECT CURTIME();
返回当前时间15:30:00
.
- 示例:
CURRENT_TIMESTAMP() / NOW()
: 返回当前日期和时间.- 示例:
SELECT NOW();
返回当前日期和时间2024-11-06 15:30:45
.
- 示例:
LOCALTIME()
: 返回当前本地时间.- 示例:
SELECT LOCALTIME();
返回当前本地时间15:30:00
.
- 示例:
LOCALTIMESTAMP()
: 返回当前本地日期和时间.- 示例:
SELECT LOCALTIMESTAMP();
返回当前本地日期和时间2024-11-06 15:30:45
.
- 示例:
SYSDATE()
: 返回当前日期和时间.- 示例:
SELECT SYSDATE();
返回当前日期和时间2024-11-06 15:30:45
.
- 示例:
UNIX_TIMESTAMP()
: 返回当前的Unix时间戳.- 示例:
SELECT UNIX_TIMESTAMP();
返回当前的Unix时间戳1636200645
.
- 示例:
UTC_DATE()
: 返回当前UTC时间的日期部分.- 示例:
SELECT UTC_DATE();
返回UTC日期2024-11-06
.
- 示例:
UTC_TIME()
: 返回当前UTC时间的时间部分.- 示例:
SELECT UTC_TIME();
返回UTC时间07:30:45
.
- 示例:
UTC_TIMESTAMP()
: 返回当前UTC时间的日期和时间.- 示例:
SELECT UTC_TIMESTAMP();
返回UTC日期和时间2024-11-06 07:30:45
.
- 示例:
# 3.3、日期/时间格式转换函数
CONVERT_TZ(dt, from_tz, to_tz)
: 将日期从一个时区转换到另一个时区.- 示例:
SELECT CONVERT_TZ('2024-11-06 12:00:00', 'UTC', 'America/New_York');
.
- 示例:
DATE(date)
: 返回日期或日期时间值的日期部分.- 示例:
SELECT DATE('2024-11-06 12:00:00');
返回日期部分2024-11-06
.
- 示例:
DATE_FORMAT(date, format)
: 将日期格式化成指定的字符串.- 示例:
SELECT DATE_FORMAT('2024-11-06', '%W, %M %e, %Y');
返回格式化后的日期字符串。
- 示例:
FROM_DAYS(days)
: 将天数值转换成日期值.- 示例:
SELECT FROM_DAYS(737999);
返回日期2024-11-06
.
- 示例:
FROM_UNIXTIME(unix_timestamp, format)
: 将Unix时间戳转换成日期格式的字符串.- 示例:
SELECT FROM_UNIXTIME(1636185600, '%Y-%m-%d %H:%i:%s');
.
- 示例:
GET_FORMAT(format_type)
: 返回日期格式的字符串.- 示例:
SELECT GET_FORMAT(DATE, 'EUR');
.
- 示例:
MAKEDATE(year, day_of_year)
: 返回年份和一年中的天数对应的日期值.- 示例:
SELECT MAKEDATE(2024, 310);
返回日期2024-11-05
.
- 示例:
MAKETIME(hour, minute, second)
: 返回时间值.- 示例:
SELECT MAKETIME(12, 30, 45);
返回时间12:30:45
.
- 示例:
STR_TO_DATE(string, format)
: 将字符串解析成日期或时间值.- 示例:
SELECT STR_TO_DATE('2024-11-06', '%Y-%m-%d');
返回日期2024-11-06
.
- 示例:
TIME(time)
: 返回时间或日期时间值的时间部分.- 示例:
SELECT TIME('2024-11-06 12:30:00');
返回时间部分12:30:00
.
- 示例:
TIME_FORMAT(time, format)
: 将时间格式化成指定的字符串.- 示例:
SELECT TIME_FORMAT('12:30:45', '%H-%i-%s');
返回格式化后的时间字符串。
- 示例:
TIMESTAMP(date, time)
: 将日期和时间值合并成日期时间值.- 示例:
SELECT TIMESTAMP('2024-11-06', '12:30:00');
返回日期时间值2024-11-06 12:30:00
.
- 示例:
# 3.4、日期/时间计算函数
DATEDIFF(date1, date2)
: 计算两个日期之间的天数差.- 示例:
SELECT DATEDIFF('2024-11-10', '2024-11-01');
返回天数差9
.
- 示例:
DAY(date) / DAYOFMONTH(date)
: 返回日期的天数部分.- 示例:
SELECT DAY('2024-11-06');
返回天数6
.
- 示例:
DAYNAME(date)
: 返回日期的星期几名称.- 示例:
SELECT DAYNAME('2024-11-06');
返回星期几Monday
.
- 示例:
DAYOFWEEK(date)
: 返回日期的星期几,1表示星期日,7表示星期六.- 示例:
SELECT DAYOFWEEK('2024-11-06');
返回星期几5
.
- 示例:
DAYOFYEAR(date)
: 返回日期的年份中的天数.- 示例:
SELECT DAYOFYEAR('2024-11-06');
返回年份中的天数311
.
- 示例:
EXTRACT(unit FROM date)
: 从日期或时间值中提取指定的部分.- 示例:
SELECT EXTRACT(MONTH FROM '2024-11-06');
返回提取的部分11
.
- 示例:
HOUR(time)
: 返回时间的小时部分.- 示例:
SELECT HOUR('12:30:00');
返回小时部分12
.
- 示例:
INTERVAL(expr, unit)
: 返回时间间隔的值.- 示例:
SELECT INTERVAL 10 DAY;
返回时间间隔10
.
- 示例:
LAST_DAY(date)
: 返回日期所在月份的最后一天.- 示例:
SELECT LAST_DAY('2024-11-06');
返回最后一天的日期2024-11-30
.
- 示例:
MICROSECOND(time)
: 返回时间值的微秒部分.- 示例:
SELECT MICROSECOND('12:30:45.123456');
返回微秒部分123456
.
- 示例:
MINUTE(time)
: 返回时间值的分钟部分.- 示例:
SELECT MINUTE('12:30:45');
返回分钟部分30
.
- 示例:
MONTH(date)
: 返回日期的月份部分.- 示例:
SELECT MONTH('2024-11-06');
返回月份部分11
.
- 示例:
MONTHNAME(date)
: 返回日期的月份名称.- 示例:
SELECT MONTHNAME('2024-11-06');
返回月份名称November
.
- 示例:
PERIOD_ADD(period, n)
: 将周期值加上指定的数量.- 示例:
SELECT PERIOD_ADD(202411, 2);
返回加上数量后的周期值202501
.
- 示例:
PERIOD_DIFF(period1, period2)
: 计算两个周期值之间的差.- 示例:
SELECT PERIOD_DIFF(202412, 202410);
返回周期值之间的差2
.
- 示例:
QUARTER(date)
: 返回日期所在的季度.- 示例:
SELECT QUARTER('2024-11-06');
返回季度4
.
- 示例:
SECOND(time)
: 返回时间值的秒部分.- 示例:
SELECT SECOND('12:30:45');
返回秒部分45
.
- 示例:
SEC_TO_TIME(seconds)
: 将秒数转换成时间值.- 示例:
SELECT SEC_TO_TIME(3660);
返回时间值01:01:00
.
- 示例:
TIME_TO_SEC(time)
: 将时间值转换成秒数.- 示例:
SELECT TIME_TO_SEC('01:30:00');
返回秒数5400
.
- 示例:
TIMEDIFF(time1, time2)
: 计算两个时间之间的差.- 示例:
SELECT TIMEDIFF('12:30:00', '10:00:00');
返回时间差02:30:00
.
- 示例:
TIMESTAMPADD(unit, interval, datetime_expr)
: 将时间间隔加到日期时间值上.- 示例:
SELECT TIMESTAMPADD(MINUTE, 30, '2024-11-06 12:00:00');
.
- 示例:
TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)
: 计算两个日期时间值之间的时间差.- 示例:
SELECT TIMESTAMPDIFF(MINUTE, '2024-11-06 12:00:00', '2024-11-06 12:30:00');
返回时间差30
.
- 示例:
TO_DAYS(date)
: 将日期值转换成天数值.- 示例:
SELECT TO_DAYS('2024-11-06');
返回天数值737999
.
- 示例:
WEEK(date, mode)
: 返回日期所在的周数.- 示例:
SELECT WEEK('2024-11-06');
返回周数45
.
- 示例:
WEEKDAY(date)
: 返回日期的星期几,0表示星期一,6表示星期日.- 示例:
SELECT WEEKDAY('2024-11-06');
返回星期几4
.
- 示例:
WEEKOFYEAR(date)
: 返回日期所在的周数.- 示例:
SELECT WEEKOFYEAR('2024-11-06');
返回周数45
.
- 示例:
YEAR(date)
: 返回日期的年份部分.- 示例:
SELECT YEAR('2024-11-06');
返回年份部分2024
.
- 示例:
YEARWEEK(date, mode)
: 返回日期所在的年份和周数.- 示例:
SELECT YEARWEEK('2024-11-06');
返回年份和周数2024-45
.
- 示例:
这些日期和时间函数可以用于日期和时间的操作、转换、格式化等操作,能满足MySQL中大部分的日期和时间处理需求。
# 4、聚合函数
聚合函数用于对一组值执行计算并返回单个值。以下是一些常用的聚合函数:
AVG()
: 计算某个列的平均值.- 示例:
SELECT AVG(salary) FROM employees;
返回结果为平均工资值。
- 示例:
BIT_AND()
: 对某个列的所有值执行按位AND运算.- 示例:
SELECT BIT_AND(flags) FROM data_table;
返回按位AND运算结果。
- 示例:
BIT_OR()
: 对某个列的所有值执行按位OR运算.- 示例:
SELECT BIT_OR(flags) FROM data_table;
返回按位OR运算结果。
- 示例:
BIT_XOR()
: 对某个列的所有值执行按位XOR运算.- 示例:
SELECT BIT_XOR(flags) FROM data_table;
返回按位XOR运算结果。
- 示例:
COUNT()
: 计算某个列中的行数.- 示例:
SELECT COUNT(*) FROM products;
返回行数计数结果。
- 示例:
GROUP_CONCAT()
: 将某个列中的所有值连接成一个字符串.- 示例:
SELECT GROUP_CONCAT(product_name) FROM order_details GROUP BY order_id;
返回连接后的字符串。
- 示例:
MAX()
: 计算某个列的最大值.- 示例:
SELECT MAX(price) FROM products;
返回最大值结果。
- 示例:
MIN()
: 计算某个列的最小值.- 示例:
SELECT MIN(quantity) FROM inventory;
返回最小值结果。
- 示例:
STD() / STDDEV()
: 计算某个列的标准差.- 示例:
SELECT STDDEV(sales) FROM monthly_data;
返回标准差结果。
- 示例:
STDDEV_POP()
: 计算某个列的总体标准差.- 示例:
SELECT STDDEV_POP(age) FROM population_data;
返回总体标准差结果。
- 示例:
STDDEV_SAMP()
: 计算某个列的样本标准差.- 示例:
SELECT STDDEV_SAMP(sales) FROM sales_data;
返回样本标准差结果。
- 示例:
SUM()
: 计算某个列的总和.- 示例:
SELECT SUM(total_sales) FROM yearly_data;
返回总和结果。
- 示例:
VAR_POP()
: 计算某个列的总体方差.- 示例:
SELECT VAR_POP(income) FROM population_data;
返回总体方差结果。
- 示例:
VAR_SAMP()
: 计算某个列的样本方差.- 示例:
SELECT VAR_SAMP(sales) FROM sales_data;
返回样本方差结果。
- 示例:
VARIANCE()
: 计算某个列的方差.- 示例:
SELECT VARIANCE(temperature) FROM weather_data;
返回方差结果。
- 示例:
这些聚合函数可以用于对MySQL中的数据进行统计、计算和分析,满足各种聚合操作需求。
# 5、控制流函数
在MySQL中,控制流函数用于根据条件执行特定操作。以下是对这些函数的详细说明及示例:
CASE
CASE
用于执行一系列条件测试,每个测试都包含一个表达式和一个结果。
示例:
SELECT
name,
CASE
WHEN age < 18 THEN 'Minor'
WHEN age BETWEEN 18 AND 65 THEN 'Adult'
ELSE 'Senior'
END AS age_group
FROM
users;
在这个示例中,CASE
语句根据 age
列的值返回不同的年龄组。
IF
IF
用于根据一个条件表达式的结果来执行不同的语句。
示例:
SELECT
name,
IF(age >= 18, 'Adult', 'Minor') AS age_status
FROM
users;
在这个示例中,IF
函数根据 age
列的值返回 'Adult'
或 'Minor'
。
IFNULL
IFNULL
用于测试一个表达式,如果表达式为空,则返回一个指定的值,否则返回表达式的值。
示例:
SELECT
name,
IFNULL(email, 'No email provided') AS email_status
FROM
users;
在这个示例中,如果 email
列为 NULL
,则返回 'No email provided'
,否则返回 email
列的值。
NULLIF
NULLIF
用于测试两个表达式是否相等,如果相等,则返回NULL
,否则返回第一个表达式的值。
示例:
SELECT
name,
NULLIF(age, 0) AS age
FROM
users;
在这个示例中,如果 age
列的值为 0
,则返回 NULL
,否则返回 age
列的值。
COALESCE
COALESCE
返回参数列表中第一个非NULL
的值。
示例:
SELECT
name,
COALESCE(email, phone, 'No contact info') AS contact_info
FROM
users;
在这个示例中,COALESCE
函数返回 email
、phone
列中第一个非 NULL
的值,如果两者都为 NULL
,则返回 'No contact info'
。
GREATEST
GREATEST
返回参数列表中的最大值。
示例:
SELECT
GREATEST(10, 20, 30, 5) AS max_value;
在这个示例中,GREATEST
函数返回 30
,因为它是参数列表中的最大值。
LEAST
LEAST
返回参数列表中的最小值。
示例:
SELECT
LEAST(10, 20, 30, 5) AS min_value;
在这个示例中,LEAST
函数返回 5
,因为它是参数列表中的最小值。
这些控制流函数在MySQL中非常有用,可以帮助你根据不同的条件执行不同的操作。
# 6、Json函数
JSON函数主要是针对json类型进行操作的函数。
JSON_ARRAY([value[, value] ...])
: 创建一个JSON数组,参数为JSON值。- 示例:
SELECT JSON_ARRAY('apple', 42, 'banana');
返回结果为["apple", 42, "banana"]
- 示例:
JSON_ARRAYAGG(expr)
(opens new window): 将表达式的结果作为JSON数组聚合,并返回一个JSON数组。- 示例:
SELECT JSON_ARRAYAGG(name) FROM fruits;
返回结果为["apple", "banana", "cherry"]
- 示例:
JSON_OBJECT(key, value[, key, value] ...)
: 创建一个JSON对象,参数为键值对。- 示例:
SELECT JSON_OBJECT('name', 'apple', 'quantity', 10);
返回结果为{"name": "apple", "quantity": 10}
- 示例:
JSON_OBJECTAGG(key, value)
: 将键值对的结果作为JSON对象聚合,并返回一个JSON对象。- 示例:
SELECT JSON_OBJECTAGG(name, quantity) FROM fruits;
返回结果为{"apple": 10, "banana": 20, "cherry": 15}
- 示例:
JSON_EXTRACT(json_doc, path[, path] ...)
: 从JSON文档中提取指定的值,返回一个JSON值或NULL。- 示例:
SELECT JSON_EXTRACT('{"name": "apple", "quantity": 10}', '$.name');
返回结果为"apple"
- 示例:
SELECT JSON_UNQUOTE(JSON_EXTRACT('["https://img.com/1.jpg""https://img.com/2.jpg",]','$[0]'));
返回结果为https://img.com/1.jpg
- 示例:
JSON_UNQUOTE(json_val)
: 去除JSON字符串中的引号,并返回一个字符串。- 示例:
SELECT JSON_UNQUOTE('"apple"');
返回结果为apple
- 示例:
JSON_SEARCH(json_doc, one_or_all, search_str[, escape_char[, path] ...])
: 在JSON文档中搜索指定的字符串,返回匹配的路径或NULL。- 示例:
SELECT JSON_SEARCH('{"name": "apple", "quantity": 10}', 'one', 'apple');
返回结果为$.name
- 示例:
JSON_REPLACE(json_doc, path, val[, path, val] ...)
: 用指定的值替换JSON文档中的指定路径的值,返回一个JSON文档。- 示例:
SELECT JSON_REPLACE('{"name": "apple", "quantity": 10}', '$.quantity', 20);
返回结果为{"name": "apple", "quantity": 20}
- 示例:
JSON_SET(json_doc, path, val[, path, val] ...)
: 设置JSON文档中指定路径的值为指定的值,返回一个JSON文档。- 示例:
SELECT JSON_SET('{"name": "apple", "quantity": 10}', '$.quantity', 20);
返回结果为{"name": "apple", "quantity": 20}
- 示例:
JSON_INSERT(json_doc, path, val[, path, val] ...)
: 在JSON文档中指定路径处插入指定的值,返回一个JSON文档。- 示例:
SELECT JSON_INSERT('{"name": "apple", "quantity": 10}', '$.price', 1.99);
返回结果为{"name": "apple", "quantity": 10, "price": 1.99}
- 示例:
JSON_REMOVE(json_doc, path[, path] ...)
: 从JSON文档中删除指定路径的值,返回一个JSON文档。- 示例:
SELECT JSON_REMOVE('{"name": "apple", "quantity": 10}', '$.quantity');
返回结果为{"name": "apple"}
- 示例:
JSON_DEPTH(json_doc)
: 返回JSON文档的最大深度。- 示例:
SELECT JSON_DEPTH('{"name": "apple", "quantity": 10}');
返回结果为2
- 示例:
JSON_KEYS(json_doc[, path])
: 返回JSON文档中的所有键,可指定路径。- 示例:
SELECT JSON_KEYS('{"name": "apple", "quantity": 10}');
返回结果为["name", "quantity"]
- 示例:
JSON_LENGTH(json_doc[, path])
: 返回JSON文档中指定路径的值的数量,或JSON文档的长度。- 示例:
SELECT JSON_LENGTH('{"fruits": ["apple", "banana", "cherry"]}');
返回结果为3
- 示例:
JSON_TYPE(json_val)
: 返回JSON值的类型,如OBJECT、ARRAY、STRING、NUMBER、BOOLEAN、NULL.- 示例:
SELECT JSON_TYPE('{"name": "apple", "quantity": 10}');
返回结果为OBJECT
- 示例:
JSON_MERGE_PATCH(json_doc, json_doc[, json_doc] ...)
: 合并两个或多个JSON文档,返回一个合并后的JSON文档.- 示例:
SELECT JSON_MERGE_PATCH('{"name": "apple"}', '{"quantity": 10}');
返回结果为{"name": "apple", "quantity": 10}
- 示例:
JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...)
: 合并两个或多个JSON文档,保留重复的键,返回一个合并后的JSON文档.- 示例:
SELECT JSON_MERGE_PRESERVE('{"name": "apple"}', '{"name": "banana"}');
返回结果为{"name": "banana"}
- 示例:
JSON_CONTAINS(target, candidate[, path])
: 检查JSON文档中是否包含指定的值,返回1表示包含,0表示不包含.- 示例:
SELECT JSON_CONTAINS('{"fruits": ["apple", "banana"]}', json_quote('apple'),'$.fruits');
返回结果为1
- 示例:
JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path] ...)
: 检查JSON文档中是否包含指定的路径,返回1表示包含,0表示不包含.- 示例:
SELECT JSON_CONTAINS_PATH('{"name": "apple", "quantity": 10}', 'one', '$.name');
返回结果为1
- 示例:
JSON_QUOTE(string)
: 将字符串转换为JSON格式的字符串,并返回一个JSON字符串.- 示例:
SELECT JSON_QUOTE('apple');
返回结果为"apple"
- 示例:
JSON_TABLE(expr, path COLUMNS (column_list))
: 将JSON数据展开为关系表形式,返回一个虚拟表.- 示例:
SELECT * FROM JSON_TABLE('{"name": "apple", "quantity": 10}', '$' COLUMNS (name VARCHAR(50) PATH '$.name', quantity INT PATH '$.quantity')) AS jt;
- 示例:
JSON_VALID(val)
: 检查给定的值是否是有效的JSON文档,返回1表示有效,0表示无效.- 示例:
SELECT JSON_VALID('{"name": "apple", "quantity": 10}');
返回结果为1
- 示例:
MEMBER OF()
: 它是一个操作符,用于检查一个值是否存在于 JSON 数组中.- 示例:
SELECT 'apple' MEMBER OF('["apple", "banana", "cherry"]');
返回结果为1
- 示例:
综合示例:
# 删除数组中指定元素
SELECT JSON_REMOVE('["Asian Art", "Buddha", "Chinese Works of Art", "Gilt", "Lacquer", "Lacquered"]',
JSON_UNQUOTE(
JSON_SEARCH('["Asian Art", "Buddha", "Chinese Works of Art", "Gilt", "Lacquer", "Lacquered"]','one','Buddha')
)
);
# json与字符串的转换
SELECT * FROM `product` p
WHERE CAST(p.`image_urls` AS CHAR) LIKE '%\r\n%'
AND p.`auction_id` = 1
UPDATE `product` p
SET p.`image_urls` = CAST(
REPLACE(
CAST(p.image_urls AS CHAR),
'\\r\\n',
''
) AS JSON
)
WHERE p.`auction_id` = 1
# 7、窗口函数
在数据分析和处理中,窗口函数(Window Functions)是一种非常强大的工具。窗口函数允许我们在不改变行数的情况下,对数据集进行复杂的计算。
与聚合函数不同,窗口函数不会减少结果集的行数。相反,它会返回一个与输入数据相同数量的结果集,并为每一行添加一个新的列,该列包含窗口函数计算的结果。
# 7.1、基本语法
SELECT column1, column2,
window_function(column3) OVER (window_clause)
FROM table_name;
column1
,column2
: 需要选择的列。window_function(column3)
: 要应用的窗口函数。OVER (window_clause)
: 定义窗口的子句。
# 7.2、窗口子句(Window Clause)
窗口子句用于定义窗口的范围和顺序。它由以下几个部分组成:
PARTITION BY
: 分区子句,用于将数据划分为不同的组。每个组内的行将独立计算窗口函数。ORDER BY
: 排序子句,用于确定窗口内行的顺序。ROWS
或RANGE
: 用于定义窗口的边界范围,指定哪些行属于当前窗口。
# a、PARTITION BY
PARTITION BY
将数据划分为多个分区,每个分区独立计算窗口函数。如果没有指定 PARTITION BY
,则整个结果集被视为一个分区。
示例:
SELECT
department,
employee_name,
salary,
AVG(salary) OVER (PARTITION BY department) AS avg_salary_by_dept
FROM
employees;
在这个示例中,PARTITION BY department
将数据按部门分组,并计算每个部门的平均工资。
# b、ORDER BY
ORDER BY
用于指定窗口内行的顺序。它决定了窗口函数的计算顺序,特别是在使用 LAG()
、LEAD()
、FIRST_VALUE()
等函数时非常重要。
示例:
SELECT
department,
employee_name,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank
FROM
employees;
在这个示例中,ORDER BY salary DESC
按工资降序排列,RANK()
函数计算每个部门内员工的工资排名。
# c、ROWS
或 RANGE
ROWS
和 RANGE
用于定义窗口的边界范围,指定哪些行属于当前窗口。常见的用法包括:
ROWS BETWEEN ... AND ...
: 基于行的物理位置定义窗口。RANGE BETWEEN ... AND ...
: 基于行的逻辑值范围定义窗口。
常见边界定义:
UNBOUNDED PRECEDING
: 从分区的第一行开始。UNBOUNDED FOLLOWING
: 到分区的最后一行结束。CURRENT ROW
: 当前行。n PRECEDING
: 当前行之前的第n行。n FOLLOWING
: 当前行之后的第n行。
示例 1:使用 ROWS
SELECT
employee_name,
salary,
AVG(salary) OVER (ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_salary
FROM
employees;
在这个示例中,ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
定义了窗口范围为当前行及其前两行,计算移动平均工资。
示例 2:使用 RANGE
SELECT
employee_name,
salary,
SUM(salary) OVER (ORDER BY salary RANGE BETWEEN 100 PRECEDING AND 100 FOLLOWING) AS sum_salary_within_range
FROM
employees;
在这个示例中,RANGE BETWEEN 100 PRECEDING AND 100 FOLLOWING
定义了窗口范围为当前行工资值 ±100 范围内的所有行,计算工资总和。
# d、综合示例
假设有一个员工表 employees
,包含以下数据:
employee_id | department | employee_name | salary | hire_date |
---|---|---|---|---|
1 | HR | Alice | 5000 | 2020-01-01 |
2 | HR | Bob | 6000 | 2020-02-01 |
3 | IT | Charlie | 7000 | 2020-03-01 |
4 | IT | David | 8000 | 2020-04-01 |
5 | HR | Eve | 5500 | 2020-05-01 |
查询:
SELECT
department,
employee_name,
salary,
AVG(salary) OVER (PARTITION BY department ORDER BY hire_date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS moving_avg_salary
FROM
employees;
结果:
department | employee_name | salary | moving_avg_salary |
---|---|---|---|
HR | Alice | 5000 | 5000.00 |
HR | Bob | 6000 | 5500.00 |
HR | Eve | 5500 | 5750.00 |
IT | Charlie | 7000 | 7000.00 |
IT | David | 8000 | 7500.00 |
说明:
PARTITION BY department
:按部门分组。ORDER BY hire_date
:按入职日期排序。ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
:窗口范围为当前行及其前一行。
通过合理使用 PARTITION BY
、ORDER BY
和 ROWS
/RANGE
,可以灵活定义窗口的范围和顺序,满足复杂的数据分析需求。
# 7.3、排名函数
排名函数用于为结果集中的每一行分配一个排名。常见的排名函数包括:
- ROW_NUMBER(): 为每一行分配一个唯一的行号,从1开始。
- RANK(): 为每一行分配一个排名,相同的值具有相同的排名,后续排名会跳过。
- DENSE_RANK(): 类似于
RANK()
,但不会跳过排名。 - NTILE(n): 将结果集分为
n
个桶,并为每一行分配桶编号。
假设有一个员工表employees
,包含以下数据:
id | name | department | salary |
---|---|---|---|
1 | Alice | HR | 5000 |
2 | Bob | IT | 6000 |
3 | Charlie | HR | 5000 |
4 | David | IT | 7000 |
5 | Eve | HR | 4000 |
使用ROW_NUMBER()
为每个部门的员工分配行号:
SELECT id, name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary) as row_num
FROM employees;
结果:
id | name | department | salary | row_num |
---|---|---|---|---|
1 | Alice | HR | 5000 | 2 |
3 | Charlie | HR | 5000 | 1 |
5 | Eve | HR | 4000 | 3 |
2 | Bob | IT | 6000 | 1 |
4 | David | IT | 7000 | 2 |
# 7.4、聚合函数
聚合函数可以应用于窗口,以便在每个窗口内进行聚合计算。常见的聚合窗口函数包括:
- SUM(): 求和
- AVG(): 平均值
- MIN(): 最小值
- MAX(): 最大值
- COUNT(): 计数
计算每个部门的工资总和:
SELECT department, name, salary,
SUM(salary) OVER (PARTITION BY department) as dept_total_salary
FROM employees;
结果:
department | name | salary | dept_total_salary |
---|---|---|---|
HR | Alice | 5000 | 14000 |
HR | Charlie | 5000 | 14000 |
HR | Eve | 4000 | 14000 |
IT | Bob | 6000 | 13000 |
IT | David | 7000 | 13000 |
# 7.5、移动平均和其他统计函数
你说得对,AVG()
本身是一个聚合函数,而不是窗口函数。但在 SQL 中,AVG()
可以通过与 OVER()
子句结合使用来实现窗口计算的功能。为了更准确地表述,我们可以将内容优化如下:
# 7.6、移动平均和其他统计函数
移动平均和其他统计函数常用于分析时间序列数据或滑动窗口计算。常见的窗口函数包括:
- LAG(): 获取当前行之前的某一行的值。
- LEAD(): 获取当前行之后的某一行的值。
- FIRST_VALUE(): 获取窗口内的第一行的值。
- LAST_VALUE(): 获取窗口内的最后一行的值。
- NTH_VALUE(): 获取窗口内的第n行的值。
此外,聚合函数(如 AVG()
、SUM()
等) 可以与 OVER()
子句结合使用,实现窗口计算的功能。
假设有一个销售记录表 sales
,包含以下数据:
id | product | sale_date | amount |
---|---|---|---|
1 | A | 2024-01-01 | 100 |
2 | B | 2024-01-02 | 150 |
3 | A | 2024-01-03 | 200 |
4 | B | 2024-01-04 | 250 |
5 | A | 2024-01-05 | 300 |
我们可以使用 AVG()
与 OVER()
子句计算每个产品的销售额移动平均值(窗口大小为3):
SELECT
product,
sale_date,
amount,
AVG(amount) OVER (
PARTITION BY product
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg
FROM
sales;
结果:
product | sale_date | amount | moving_avg |
---|---|---|---|
A | 2024-01-01 | 100 | 100.0000 |
A | 2024-01-03 | 200 | 150.0000 |
A | 2024-01-05 | 300 | 200.0000 |
B | 2024-01-02 | 150 | 150.0000 |
B | 2024-01-04 | 250 | 200.0000 |
说明:
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
:定义了窗口范围为当前行及其前两行。PARTITION BY product
:按产品分组计算移动平均值。ORDER BY sale_date
:按销售日期排序。
其他窗口函数示例
LAG()
:获取前一行的值。SELECT product, sale_date, amount, LAG(amount, 1) OVER (PARTITION BY product ORDER BY sale_date) AS prev_amount FROM sales;
LEAD()
:获取后一行的值。SELECT product, sale_date, amount, LEAD(amount, 1) OVER (PARTITION BY product ORDER BY sale_date) AS next_amount FROM sales;
FIRST_VALUE()
:获取窗口内的第一行值。SELECT product, sale_date, amount, FIRST_VALUE(amount) OVER (PARTITION BY product ORDER BY sale_date) AS first_amount FROM sales;
LAST_VALUE()
:获取窗口内的最后一行值。SELECT product, sale_date, amount, LAST_VALUE(amount) OVER (PARTITION BY product ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_amount FROM sales;
NTH_VALUE()
:获取窗口内的第n行值。SELECT product, sale_date, amount, NTH_VALUE(amount, 2) OVER (PARTITION BY product ORDER BY sale_date) AS second_amount FROM sales;
通过这些函数,可以更灵活地分析时间序列数据或滑动窗口中的数据趋势。AVG()
等聚合函数结合 OVER()
子句,能够实现强大的窗口计算功能。
# 7.7、案例一:销售额趋势分析
假设我们有一个电商网站的销售记录表ecommerce_sales
,包含以下列:
sale_id
: 销售IDproduct_id
: 产品IDsale_date
: 销售日期amount
: 销售金额
我们希望分析每个产品的销售额趋势,并计算过去7天的平均销售额。可以使用窗口函数来实现:
SELECT product_id, sale_date, amount,
AVG(amount) OVER (PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as avg_7day_sales
FROM ecommerce_sales;
这个查询将为每个产品计算过去7天的平均销售额,帮助我们了解销售趋势。
# 7.8、案例二:库存管理
在库存管理系统中,我们需要跟踪每个仓库的库存变化情况。假设有一个库存表inventory
,包含以下列:
warehouse_id
: 仓库IDproduct_id
: 产品IDstock_date
: 库存日期quantity
: 库存数量
我们希望计算每个仓库在过去一个月内的库存变化率。可以使用窗口函数来计算:
WITH MonthlyStock AS (
SELECT warehouse_id, product_id, stock_date, quantity,
LAG(quantity) OVER (PARTITION BY warehouse_id, product_id ORDER BY stock_date) as previous_quantity
FROM inventory
),
MonthlyChange AS (
SELECT warehouse_id, product_id, stock_date, quantity, previous_quantity,
(quantity - previous_quantity) / previous_quantity as change_rate
FROM MonthlyStock
WHERE previous_quantity IS NOT NULL AND previous_quantity != 0
)
SELECT warehouse_id, product_id, stock_date, quantity, change_rate
FROM MonthlyChange;
这个查询将计算每个仓库每个产品的月度库存变化率,帮助我们监控库存波动情况。
# 8、正则函数
MySQL提供了一些正则函数来处理基于正则表达式的字符串匹配和操作。以下是一些常用的正则函数:
REGEXP
(或RLIKE
):这是一个操作符,可以在WHERE子句中使用,用于测试一个字符串是否匹配一个正则表达式。- 示例:
SELECT column FROM table WHERE column REGEXP 'pattern';
- 示例:
REGEXP_LIKE(string, pattern[, mode])
:它类似于REGEXP,但提供了额外的模式匹配模式,例如区分大小写和多国语言支持。- 示例:
SELECT column FROM table WHERE REGEXP_LIKE(column, 'pattern', 'i');
- 示例:
REGEXP_REPLACE(string, pattern, replacement[, position[, occurrence[, match_type]]])
:它将字符串中匹配正则表达式模式的部分替换为指定的替换字符串。- 示例:
SELECT REGEXP_REPLACE('abcde', 'b', 'z');
结果为 'azcde'
- 示例:
REGEXP_INSTR(string, pattern[, position[, occurrence[, return_end[, match_type]]]])
:它返回字符串中第一次出现匹配正则表达式模式的位置。- 示例:
SELECT REGEXP_INSTR('abcde', 'b');
结果为 2
- 示例:
REGEXP_SUBSTR(string, pattern[, position[, occurrence[, match_type]]])
:它从字符串中提取匹配正则表达式模式的子字符串。- 示例:
SELECT REGEXP_SUBSTR('abcde', 'b.*d');
结果为 'bcd'
- 示例:
# 9、自定义函数
MySQL允许开发人员自定义函数,可以根据业务需求来实现自己的函数。下面是MySQL自定义函数的基本概念和使用方法:
- 函数的定义
在MySQL中,自定义函数需要使用CREATE FUNCTION语句进行定义,语法如下:
CREATE FUNCTION function_name (param1 type1, param2 type2, ...) RETURNS return_type
BEGIN
-- 函数体
END;
其中,function_name为函数名称,param1、param2等为函数的参数列表,type1、type2等为参数的数据类型,return_type为函数返回值的数据类型。函数体部分是函数实现的具体内容,可以包括各种SQL语句、流程控制语句等。
- 函数的使用
定义好函数后,就可以在MySQL中使用这个函数。使用函数的语法如下:
SELECT function_name(param1, param2, ...)
其中,function_name为自定义函数的名称,param1、param2等为函数的参数列表。
- 函数的示例
下面是一个自定义函数的示例,用于计算一个数的阶乘:
CREATE FUNCTION factorial(n INT) RETURNS INT
BEGIN
DECLARE result INT DEFAULT 1;
DECLARE i INT DEFAULT 1;
WHILE i <= n DO
SET result = result * i;
SET i = i + 1;
END WHILE;
RETURN result;
END;
定义好这个函数后,可以使用以下语句来调用该函数:
SELECT factorial(5); -- 返回120
# 六、高级技巧
# 1、多表连接查询
在实际应用中,数据通常分散在多个表中。为了从这些表中获取所需的信息,我们需要执行多表连接查询。根据连接方式的不同,连接查询可以分为内连接、左连接、右连接和全连接。在本节中,我们将详细介绍这些连接类型。
# 1.1、内连接(INNER JOIN)
内连接是最常用的连接类型,它返回两个表中满足连接条件的记录。当连接条件为真时,内连接将从两个表中返回匹配的记录。以下是一个简单的内连接示例:
假设有两个表,一个是employees
,另一个是departments
。我们想要查询每个员工所在的部门名称。
SELECT employees.name, departments.department_name
FROM employees
INNER JOIN departments ON employees.department_id = departments.id;
在这个例子中,我们通过INNER JOIN
关键字连接了employees
和departments
两个表,并使用ON
关键字指定了连接条件,即employees.department_id = departments.id
。查询结果将包含满足连接条件的记录。
# 1.2、左连接(LEFT JOIN)
左连接(也称为左外连接)返回左表中的所有记录,以及与左表匹配的右表记录。如果右表中没有匹配的记录,查询结果中将显示NULL值。以下是一个左连接示例:
假设我们想要查询所有员工及其所在的部门名称,即使某些员工没有分配部门。
SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments ON employees.department_id = departments.id;
在这个例子中,我们通过LEFT JOIN
关键字连接了employees
和departments
两个表,并指定了相同的连接条件。查询结果将包含左表(employees)的所有记录,以及与之匹配的右表(departments)记录。
# 1.3、右连接(RIGHT JOIN)
右连接(也称为右外连接)返回右表中的所有记录,以及与右表匹配的左表记录。如果左表中没有匹配的记录,查询结果中将显示NULL值。以下是一个右连接示例:
假设我们想要查询所有部门及其员工名称,即使某些部门没有员工。
SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments ON employees.department_id = departments.id;
在这个例子中,我们通过RIGHT JOIN
关键字连接了employees
和departments
两个表,并指定了相同的连接条件。查询结果将包含右表(departments)的所有记录,以及与之匹配的左表(employees)记录。
# 1.4、全连接(FULL JOIN)
全连接(也称为全外连接)返回左表和右表中的所有记录。如果某个表中没有匹配的记录,查询结果中将显示NULL值。需要注意的是,MySQL 8.0版本不直接支持FULL JOIN
关键字,但是我们可以通过组合LEFT JOIN
和RIGHT JOIN
来实现全连接的功能。以下是一个全连接示例:
假设我们想要查询所有员工及其所在的部门名称,同时也要包含没有员工的部门。
SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments ON employees.department_id = departments.id
UNION
SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments ON employees.department_id = departments.id;
在这个例子中,我们首先使用LEFT JOIN
获取所有员工及其所在的部门名称,然后使用RIGHT JOIN
获取所有部门及其员工名称。通过UNION
关键字将两个查询结果合并,从而实现全连接的功能。查询结果将包含左表(employees)和右表(departments)中的所有记录。
# 2、聚合函数与分组查询
聚合函数用于对一组值进行计算,并返回单个值。常用的聚合函数有:COUNT()
、SUM()
、AVG()
、MIN()
、MAX()
等。与聚合函数一起使用的GROUP BY
子句可以实现按特定列对查询结果进行分组。
# 2.1、聚合函数
以下是一些常用聚合函数的示例:
COUNT()
: 计算表中记录的数量。例如,查询employees
表中记录的数量:SELECT COUNT(*) FROM employees;
SUM()
: 计算表中某列值的总和。例如,查询orders
表中所有订单的总金额:SELECT SUM(total_amount) FROM orders;
AVG()
: 计算表中某列值的平均值。例如,查询employees
表中员工的平均工资:SELECT AVG(salary) FROM employees;
MIN()
: 计算表中某列值的最小值。例如,查询employees
表中最低工资:SELECT MIN(salary) FROM employees;
MAX()
: 计算表中某列值的最大值。例如,查询employees
表中最高工资:SELECT MAX(salary) FROM employees;
# 2.2、分组查询
GROUP BY
子句用于将查询结果按照一个或多个列进行分组。例如,查询每个部门的员工数量:
SELECT department_id, COUNT(*)
FROM employees
GROUP BY department_id;
HAVING
子句用于过滤分组后的结果。例如,查询员工数量超过10人的部门:
SELECT department_id, COUNT(*)
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 10;
注意:在8.0之前,Group by会默认根据作用字段(Group by的后接字段)对结果进行排序。在能利用索引的情况下,Group by不需要额外进行排序操作;但当无法利用索引排序时,Mysql优化器就不得不选择通过使用临时表然后再排序的方式来实现GROUP BY了,效率低下。在8.0之后,禁用了group by的隐形排序。
# 3、子查询与嵌套查询
子查询是嵌套在另一个查询中的查询。子查询可以出现在SELECT
、FROM
、WHERE
和HAVING
子句中。根据使用场景的不同,子查询可以分为标量子查询、行子查询和表子查询。
# 3.1、标量子查询
标量子查询返回单个值,可以在SELECT
、WHERE
和HAVING
子句中使用。例如,查询工资高于平均工资的员工:
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
# 3.2、行子查询
行子查询返回一行数据,可以在WHERE
子句中使用。例如,查询工资和奖金都高于某个员工的其他员工:
SELECT name, salary, bonus
FROM employees
WHERE (salary, bonus) > (SELECT salary, bonus FROM employees WHERE name = 'John Doe');
# 3.3、表子查询
表子查询返回一个表,可以在FROM
子句中使用。例如,查询每个部门工资最高的员工:
SELECT departments.department_name, high_salaries.name, high_salaries.salary
FROM departments
INNER JOIN (
SELECT department_id, name, salary
FROM employees
WHERE (department_id, salary) IN (
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
)
) AS high_salaries ON departments.id = high_salaries.department_id;
在这个例子中,我们首先使用表子查询找出每个部门工资最高的员工,然后将其结果与departments
表进行连接,以获取部门名称。
# 4、索引的创建、使用与优化
索引是一种数据库对象,用于提高查询速度。通过在表的一个或多个列上创建索引,可以加快查询、更新和删除操作。但是,索引并非万能的,过多的索引可能会影响数据的插入和更新性能。因此,在创建和使用索引时,需要权衡查询性能与数据修改性能之间的平衡。本节将介绍索引的创建、使用与优化方法。
# 4.1、创建索引
在MySQL中,可以使用CREATE INDEX
语句创建索引。以下是一个简单的示例:
CREATE INDEX idx_employees_department_id ON employees(department_id);
在这个例子中,我们为employees
表的department_id
列创建了一个名为idx_employees_department_id
的索引。
除了CREATE INDEX
外,还可以在创建表时使用CREATE TABLE
语句直接创建索引。例如:
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(255),
department_id INT,
INDEX idx_department_id (department_id)
);
在这个例子中,我们在创建employees
表时为department_id
列创建了一个索引。
# 4.2、使用索引
当执行查询时,MySQL会自动选择合适的索引来优化查询性能。但是,索引的使用并不总是有效的。以下几种情况可能会导致索引失效:
- 使用
LIKE
操作符进行模糊查询时,以通配符开头的模式会导致索引失效。 - 在索引列上进行计算或使用函数时,索引将不会被使用。
- 当查询条件中的
OR
操作符涉及多个列时,索引可能无法被使用。
要查看MySQL是否使用了索引,可以使用EXPLAIN
语句分析查询计划。例如:
EXPLAIN SELECT * FROM employees WHERE department_id = 10;
这个查询将返回一些关于查询执行计划的信息,包括是否使用了索引、哪个索引被使用以及索引的使用效果等。
# 4.3、索引类型
MySQL支持多种类型的索引,按照数据结构分类:
- B+Tree索引:B+Tree(Balance+ Tree)索引是MySQL中最常用的索引类型。它适用于全值匹配和范围查询,同时也支持排序和分组操作。
- Hash索引:Hash索引基于哈希表实现,适用于等值查询。但是,它不适用于范围查询、排序和分组操作。需要注意的是,InnoDB存储引擎不支持显式的Hash索引,但会自动创建隐式的Hash索引来优化等值查询。
- Full-text索引:全文索引用于文本内容的搜索,能够提供基于关键词的搜索功能。在MySQL中,全文索引主要应用于MyISAM和InnoDB存储引擎。全文索引默认分词器仅对英文做了优化,且查询功能有限,一般不在生产中使用,推荐使用搜索引擎,如Elasticsearch。
- 空间索引:空间索引用于地理空间数据类型的查询,能够提高地理空间数据的查询性能。在MySQL中,空间索引基于R-Tree实现,并主要应用于MyISAM和InnoDB存储引擎。
按照物理存储分类(参考下一节):
- 聚簇索引
- 辅助索引
按照字段特性分类:
# a、主键索引(Primary Key Index)
CREATE TABLE table_name (
column_name INT PRIMARY KEY,
...
);
主键索引用于唯一标识表中的每一行。它是一个独特的索引类型,确保每个索引键的唯一性,并用于加速与主键相关的查询。
# b、唯一索引(Unique Index)
CREATE TABLE table_name (
column_name INT UNIQUE,
...
);
唯一索引确保索引列中的每个值都是唯一的。与主键索引不同,唯一索引允许空值,但对于非空值,每个值必须是唯一的。
可以对多个字段添加唯一索引:
ALTER TABLE table_name ADD UNIQUE INDEX index_name (column1, column2, ...);
# c、普通索引(Normal Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name),
...
);
普通索引是最常见的索引类型,用于加快特定列的查询速度。它可以基于单个列或多个列创建。
# d、前缀索引(Prefix Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name(prefix_length)),
...
);
前缀索引允许您在索引中仅包含列值的前缀部分。这对于较长的列或文本列可以节省存储空间并提高查询性能。
# e、隐藏索引(Invisible Index)
ALTER TABLE table_name ALTER INDEX index_name INVISIBLE;
隐藏索引是一个特殊的索引类型,它在查询优化器的视图中不可见。通过隐藏索引,您可以对某些查询进行索引调整而不影响其他查询的执行计划。
隐藏索引主要应用于索引的 软删除 和 灰度发布。
在之前MySQL的版本中,只能通过显式的方式删除索引,如果删除后发现索引删错了,又只能通过创建索引的方式将删除的索引添加回来,如果数据库中的数据量非常大,或者表比较大,这种操作的成本非常高。在MySQL 8.0中,只需要将这个索引先设置为隐藏索引,使查询优化器不再使用这个索引,但是,此时这个索引还是需要MySQL后台进行维护,当确认将这个索引设置为隐藏索引系统不会受到影响时,再将索引彻底删除。这就是索引软删除功能。
灰度发布,就是说创建索引时,首先将索引设置为隐藏索引,通过修改查询优化器的开关,使隐藏索引对查询优化器可见,通过explain对索引进行测试,确认这个索引有效,某些查询可以使用到这个索引,就可以将其设置为可见索引,完成灰度发布的效果。
在灰度发布的场景中我们需要同过explain分析隐藏索引是否对某些SQL有帮助,但是隐藏索引又不生效,怎么办呢?
在MySQL8 中提供了一种新的测试方式,可以通过优化器的一个开关来打开某个设置,使隐藏索引对查询优化器可见。我们可以通过如下代码查看这个开关是否开启:
select @@optimizer_switch \G;
查询结果如下所示:
use_invisible_indexes=off
这个开关默认是off,off表示关闭,ON表示开启。
我们可以通过如下SQL在当前会话中开启和关闭(不影响其他会话进程):
set session optimizer_switch="use_invisible_indexes=on";
现在我们可以通过explain分析隐藏索引是否对某些SQL有帮助。
# f、降序索引(Descending Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name DESC),
...
);
降序索引用于对列进行降序排序。默认情况下,索引是升序的,但通过在列上指定 DESC
可以创建降序索引。
需要注意,如果使用了where查询,那么降序索引将不会生效。
# g、函数索引(Function Index)
CREATE TABLE table_name (
...
INDEX index_name (UPPER(column_name)),
...
);
函数索引允许您在索引中使用函数表达式,而不仅仅是列名。这可以帮助加速特定函数的查询。
# h、多值索引(Multi-Valued Index)
多值索引是在存储值数组的列上定义的二级索引。
“正常”索引的每个数据记录都有一个索引记录 (1:1)。多值索引可以具有单个数据记录的多个索引记录 (N:1)。
多值索引用于为 JSON 数组编制索引。例如,在以下 JSON 文档中的邮政编码数组上定义的多值索引将为每个邮政编码创建一个索引记录,每个索引记录引用相同的数据记录。
{
"user":"Bob",
"user_id":31,
"zipcode":[94477,94536]
}
可以建表的时候创建索引:
CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
custinfo JSON,
INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) )
);
也可以之后添加索引:
CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
custinfo JSON
);
ALTER TABLE customers ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
也可以作为组合索引的一部分:
ALTER TABLE customers ADD INDEX comp(id, modified,
(CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
验证索引是否生效:
explain
SELECT * FROM `customers` s WHERE 94477 member of (s.`custinfo`->'$.zipcode')
如果字段的值本身就是数组,比如:
INSERT INTO customers VALUES
(NULL, NOW(), '["Collectibles", "Gilt", "Ruby", "Silver"]');
那么,新增索引的时候这么做:
ALTER TABLE customers
ADD INDEX idx_zips( (CAST(custinfo->'$' AS CHAR(256) ARRAY)) );
验证索引是否生效:
explain
SELECT * FROM `customers` s WHERE 'Ruby' member of (s.`custinfo`->'$')
# 4.4、InnoDB索引模型
InnoDB存储引擎使用B-Tree作为主要的索引结构。在InnoDB中,表数据和索引都存储在一个称为聚簇索引(Clustered Index)的数据结构中。
每个InnoDB表都有一个主键索引,称为聚簇索引。同时,InnoDB还支持辅助索引(Secondary Index),也称为非聚簇索引。
聚簇索引:聚簇索引将表数据和索引存储在一起,按照主键顺序存储。这意味着,主键查询和范围查询的性能非常高。然而,这也导致了插入、更新和删除操作的性能开销较大。
辅助索引:辅助索引存储了对应列的值以及指向聚簇索引的指针。这意味着,在使用辅助索引进行查询时,可能需要进行额外的查找操作(回表)以获取完整的行数据。
# 4.5、索引维护
为了保持索引的性能,需要定期进行索引维护,包括分析索引、优化索引和重建索引。
分析索引:使用
ANALYZE TABLE
命令可以收集表和索引的统计信息,以帮助优化器选择更优的查询计划。优化索引:使用
OPTIMIZE TABLE
命令可以整理表和索引的数据,以提高查询和插入性能。重建索引:当索引出现碎片化或性能下降时,可以使用
ALTER TABLE ... DROP INDEX
和ALTER TABLE ... ADD INDEX
命令重建索引。
# a、B+树的维护
在B+树中,每个节点中的关键字都是有序的。B+树通过维护这种有序性,可以快速地查找和遍历节点中的关键字,提高查询效率。为了保持节点中的关键字有序,B+树在插入、删除节点时需要进行必要的维护。
- 插入节点
在B+树中插入新节点时,首先要找到该节点的插入位置。插入位置可以通过查找B+树的叶子节点得到。插入新节点时,需要保持节点中的关键字有序性,因此需要将新节点插入到正确的位置。插入新节点后,如果当前节点的关键字数量超过了节点的容量,就需要进行节点分裂操作。节点分裂操作可以将当前节点分裂成两个节点,从而保持B+树的平衡。
- 删除节点
在B+树中删除节点时,首先要找到要删除的节点。删除节点时,需要保持节点中的关键字有序性,因此需要将删除后的节点重新排序。如果删除后节点中的关键字数量低于了节点的最小容量,就需要进行节点合并操作。节点合并操作可以将当前节点和相邻节点合并成一个节点,从而保持B+树的平衡。
维护B+树的有序性可以保证查询和遍历节点时的效率,并且可以保持B+树的平衡,避免出现过度分裂或者过度合并的情况。因此,在设计和实现B+树索引时,需要充分考虑维护B+树的有序性,以提高查询效率和保证数据结构的稳定性。
使用非有序插入的主键,写数据的成本会比较高。
# 4.6、回表
当使用辅助索引查询数据时,可能需要进行回表操作。回表是指通过辅助索引找到对应的聚簇索引记录以获取完整的行数据。由于回表操作需要额外的I/O操作,因此会影响查询性能。为了减少回表次数,可以考虑使用覆盖索引。
比如对于sql:select * from t where k between 3 and 5
。这里的k是辅助索引。这个SQL需要执行几次树的搜索操作呢?
需要五次:
- 在k索引树上找到 k=3的记录,取得ID=300;
- 再到ID索引树查到ID=300对应的R3;
- 在k索引树取下一个值k=5,取得ID=500;
- 再回到ID索引树查到ID=500对应的R4;
- 在k索引树取下一个值k=6,不满足条件,循环结束。
可以看到,回了两次表。
# 4.7、覆盖(联合)索引
覆盖索引是一种包含查询所需所有列的辅助索引。由于覆盖索引包含了所有需要的数据,因此在查询时无需进行回表操作。使用覆盖索引可以大幅提高查询性能,尤其是在大表上。
要创建覆盖索引,可以在CREATE INDEX
语句中包含所有需要的列,例如:
CREATE INDEX idx_employees_name_department ON employees(name, department_id);
可以看到,覆盖索引是多个字段的索引,也叫联合索引或者复合索引、组合索引。
联合索引遵从最左匹配原则,如key(c1,c2,c3),那么mysql仅对最边的前缀进行有效查询。如c2=1,则用不到索引。但需要注意的是,只要c1出现在where中,就可以用到索引,与顺序无关。例如c3=1 and c1 = 2,也是可以使用的
在使用覆盖索引时,需要注意以下几点:
- 覆盖索引包含的列越多,索引的大小越大,维护成本越高。
- 覆盖索引适用于经常出现在查询条件和结果集中的列。
- 当查询中的列被索引覆盖时,可以使用
EXPLAIN
命令查看查询计划中的Extra
列,它应显示“Using index”。
# 4.8、最佳实践
在创建、使用和优化索引时,需要遵循以下最佳实践:
- 为经常出现在查询条件和排序操作中的列创建索引。
- 为主键创建索引。
- 使用覆盖索引避免额外的回表操作。
- 定期分析和优化索引,以保持索引性能。
- 避免在索引列上进行计算或使用函数,以充分利用索引优势。
- 当查询条件中的
OR
操作符涉及多个列时,考虑使用联合索引。
总之,索引是提高查询性能的重要手段。在创建、使用和优化索引时,需要充分考虑实际应用场景,并权衡查询性能与数据修改性能之间的关系。
# 5、存储过程与触发器
存储过程和触发器是数据库中的两种重要对象,它们可以帮助我们实现更复杂的业务逻辑和保持数据的一致性。
# 5.1、存储过程
存储过程是一种在数据库中存储的预定义SQL代码块,可以被调用执行。存储过程可以接收参数并返回结果,从而实现复用和封装复杂逻辑。存储过程的主要优点包括减少网络开销、提高性能和安全性以及易于维护。
创建存储过程的语法如下:
CREATE PROCEDURE procedure_name(parameter1, parameter2, ...)
BEGIN
-- SQL statements
END;
以下是一个简单的存储过程示例,用于计算两个数的和:
CREATE PROCEDURE add_numbers(IN a INT, IN b INT, OUT sum INT)
BEGIN
SET sum = a + b;
END;
调用存储过程的语法如下:
CALL procedure_name(parameter1, parameter2, ...);
例如,调用上述示例中的存储过程:
SET @result = 0;
CALL add_numbers(5, 10, @result);
SELECT @result;
# 5.2、触发器
触发器是一种特殊的存储过程,它在某个事件(如INSERT、UPDATE或DELETE)发生时自动执行。触发器可以用于实现数据的自动维护、约束检查和业务规则验证等功能。需要注意的是,触发器可能会影响性能,因此应谨慎使用。
创建触发器的语法如下:
CREATE TRIGGER trigger_name
trigger_time trigger_event
ON table_name FOR EACH ROW
BEGIN
-- SQL statements
END;
trigger_time
可以是BEFORE
或AFTER
,表示触发器在事件发生前或发生后执行。trigger_event
可以是INSERT
、UPDATE
或DELETE
。
以下是一个简单的触发器示例,用于在employees
表的salary
字段更新后自动更新salary_history
表:
CREATE TRIGGER update_salary_history
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
IF NEW.salary <> OLD.salary THEN
INSERT INTO salary_history(employee_id, old_salary, new_salary, change_date)
VALUES (OLD.id, OLD.salary, NEW.salary, NOW());
END IF;
END;
在这个示例中,触发器将在employees
表的salary
字段更新后自动将变更记录插入到salary_history
表中。
存储过程和触发器是数据库中的强大功能,可以帮助我们实现更复杂的业务逻辑和数据一致性。
# 6、公用表表达式(Common Table Expressions)
公用表表达式(CTE,Common Table Expressions)是一种临时结果集,用于简化复杂的SQL查询。CTE在查询执行期间仅存在于内存中,它能够提高代码的可读性和可维护性。CTE还可以用于实现递归查询,从而处理具有层次结构的数据。
# 6.1、创建和使用CTE
创建CTE的语法如下:
WITH cte_name (column1, column2, ...)
AS (
-- CTE的定义,可以是SELECT、INSERT、UPDATE或DELETE语句
)
-- 使用CTE的查询
以下是一个简单的CTE示例,用于计算员工的平均工资:
WITH avg_salaries (department_id, average_salary)
AS (
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
)
---
SELECT e.id, e.name, e.department_id, e.salary, a.average_salary
FROM employees e
JOIN avg_salaries a ON e.department_id = a.department_id
WITH
命名后面的column_list
可以省略,如果省略,则表示CTE的列与查询中的列相同。
WITH avg_salaries
AS (
SELECT department_id, AVG(salary) as average_salary
FROM employees
GROUP BY department_id
)
在这个示例中,我们首先创建了一个名为avg_salaries
的CTE,用于计算各部门的平均工资。然后,我们在主查询中将employees
表与CTE进行连接,以显示员工的详细信息和对应部门的平均工资。
# 6.2、递归CTE
递归CTE是一种特殊的CTE,它可以引用自身,从而实现递归查询。递归CTE通常用于处理具有层次结构的数据,如组织架构、文件系统等。
创建递归CTE的语法如下:
WITH RECURSIVE cte_name (column1, column2, ...)
AS (
-- 非递归部分(初始查询)
UNION ALL
-- 递归部分(引用CTE的查询)
)
-- 使用CTE的查询
以下是一个简单的递归CTE示例,用于查询员工的上级领导:
WITH RECURSIVE employee_hierarchy
AS (
-- 非递归部分(初始查询)
SELECT id, name, supervisor_id
FROM employees
WHERE id = 1
UNION ALL
-- 递归部分(引用CTE的查询)
SELECT e.id, e.name, e.supervisor_id
FROM employees e
JOIN employee_hierarchy eh ON e.supervisor_id = eh.id
)
SELECT * FROM employee_hierarchy;
在这个示例中,我们首先创建了一个名为employee_hierarchy
的递归CTE。然后,在主查询中使用CTE查询员工的上级领导。
# 7、临时表
临时表(Temporary Table)是一种存储临时数据的表格,它在创建后只存在于当前的会话中,并在会话结束后自动删除。
临时表可以存储临时数据,可以在多个查询之间共享数据,可以在多个会话之间共享数据,可以用于存储中间结果,提高查询的性能。
# 7.1、临时表的语法
在MySQL中,创建临时表的语法与创建普通表的语法相同,只是在表名前加上关键字TEMPORARY,表示创建的是临时表。例如,可以使用以下语句创建一个临时表:
CREATE TEMPORARY TABLE temp_table (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
);
# 7.2、临时表的属性和限制
与普通表一样,临时表也具有各种属性和限制,例如表的存储引擎、表的字符集等。不过,由于临时表的特殊性质,其具有以下限制:
- 临时表只存在于当前会话中,不能在其他会话中访问。
- 临时表的数据只存在于当前会话中,会话结束后会自动删除。
- 临时表不能有外键约束,因为外键约束需要引用其他表。
# 7.3、临时表的使用
创建临时表后,可以使用INSERT、UPDATE、DELETE等语句向表中插入数据或修改数据。临时表的使用方式与普通表相同,可以通过SELECT等语句查询表中的数据。例如,可以使用以下语句向临时表中插入数据:
INSERT INTO temp_table (name) VALUES ('John'), ('Mike'), ('Peter');
可以使用以下语句查询临时表中的数据:
SELECT * FROM temp_table;
# 7.4、临时表的实现原理
# a、内存临时表
在MySQL中,临时表可以存储在内存中或者磁盘上。如果临时表的数据量比较小,可以存储在内存中,以提高查询性能和响应速度。在内存中创建临时表的语法与创建普通表的语法相同,例如:
CREATE TEMPORARY TABLE temp_table (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MEMORY;
# b、磁盘临时表
如果临时表的数据量比较大,不能存储在内存中,需要存储在磁盘上。在MySQL中,可以使用HEAP或者MyISAM存储引擎来创建磁盘临时表。例如:
CREATE TEMPORARY TABLE temp_table (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
) ENGINE=HEAP;
或者:
CREATE TEMPORARY TABLE temp_table (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM;
需要注意的是,磁盘临时表可能会对查询性能和响应速度产生一定的影响,因此在创建临时表时需要根据实际情况进行选择。
# c、临时表的存储引擎
在MySQL中,临时表的存储引擎可以是任何一种合法的存储引擎,例如InnoDB、MyISAM、MEMORY等。不过,由于临时表的特殊性质,推荐使用MEMORY或者HEAP存储引擎,以提高查询性能和响应速度。
# 7.5、子查询与临时表
在MySQL中,子查询可能会创建临时表,具体取决于查询的执行计划。
一般来说,当子查询需要将结果保存在临时表中时,会创建临时表,例如当子查询需要使用ORDER BY或GROUP BY等操作时。此时,MySQL会将子查询结果存储到一个临时表中,并将该临时表与主查询进行Join操作。
临时表在查询语句中的使用是MySQL的优化器进行的决策,具体取决于查询的复杂性和数据量等因素。一般来说,子查询中使用的表越多,数据量越大,临时表的使用就越可能发生。
# 8、虚拟列
MySQL 5.7版本引入了虚拟列(Virtual Columns)的概念,它是一种基于表达式的计算列,它的值是通过计算其他列的值得出的,并且不需要实际存储在数据库中。虚拟列提供了一种方便的方式来获取衍生数据,而无需显式地存储这些数据。
虚拟列具有以下特点:
表达式计算: 虚拟列的值是通过计算其他列或表达式得出的。可以使用内置函数、算术运算符、逻辑运算符等来定义虚拟列的计算规则。
不存储数据: 虚拟列不需要实际存储在数据库中,它们只是在查询时动态计算出来的值。虚拟列的计算是实时进行的,每次查询时都会重新计算。
数据类型和约束: 虚拟列可以使用任何合法的数据类型,并且可以应用列级别的约束条件,例如 NOT NULL、UNIQUE、DEFAULT 值等。
索引支持: 虚拟列可以与索引一起使用,可以创建基于虚拟列的索引来提高查询性能。
要创建虚拟列,需要使用 GENERATED ALWAYS AS
子句来定义虚拟列的计算规则。以下是创建虚拟列的示例:
CREATE TABLE my_table (
id INT,
first_name VARCHAR(50),
last_name VARCHAR(50),
full_name VARCHAR(100) GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) VIRTUAL
);
在上面的示例中,full_name
是一个虚拟列,它的值是通过将 first_name
和 last_name
进行连接得出的。每当查询包含 full_name
列时,MySQL 会动态计算并返回该列的值。
虚拟列提供了一种便捷的方式来获取衍生数据,并且可以减少冗余存储。
# 9、NULL值的处理
MySQL建表的时候,如果不指定列为NOT NULL
,那么列的默认值就是NULL
。
NULL
是一种特殊的值,表示缺少数据或者未知数据。在MySQL中,NULL
值的处理方式与其他值不同,需要特别注意。
# 9.1、NULL值的比较与判断
在MySQL中,NULL
值与其他值的比较结果都是NULL
,即NULL = 1
、NULL = 0
、NULL = NULL
的结果都是NULL
。
可以使用<=>
符号、IFNULL
函数、IS NULL
和IS NOT NULL
来判断NULL
值。
SELECT NULL = 1; -- NULL
SELECT NULL = 0; -- NULL
SELECT NULL = NULL; -- NULL
SELECT NULL <=> NULL; -- 1
SELECT IFNULL(NULL,'1'); -- 1
SELECT NULL IS NULL; -- 1
SELECT NULL IS NOT NULL; -- 0
# 9.2、count
函数对于NULL
的处理
使用count(*)
或者count(null column)
结果不同,count(null column)<=count(*)
。
假设name有一列为NULL
的情况下:
select count(*),count(name) from test;
+----------+------------+
| 2 | 1 |
# 9.3、NULL
对于索引的影响
MySQL 中支持在含有NULL
值的列上使用索引,但是Oracle不支持。这就是你可能会听到如果列上含有NULL那么将会使索引失效
的原因。
严格来说,这句话对与 MySQL 来说是不准确的。
# 9.4、使用NULL
值的缺点
对含有NULL
值的列进行统计计算,比如:count()
max()
min()
,结果并不符合我们的期望值,会干扰排序,分组,去重结果。
有的时候为了消除NULL
带来的技术债务,我们需要在SQL中使用IFNULL()
来确保结果可控,但是这使程序变得复杂。
某些公司不推荐在列中设置NULL
作为列的默认值,你可以使用NOT NULL
消除默认设置,使用0
或者''
空字符串来代替NULL
。
# 10、正则查询
MySQL支持正则表达式搜索,为数据库查询提供更强大和灵活的匹配模式。正则表达式是一种特殊的字符串模式,可以用来匹配、查找和替换文本。在MySQL中,你可以使用REGEXP或RLIKE关键字(它们是等价的)来进行正则表达式查询。
正则表达式查询的基本语法是:
SELECT column
FROM table
WHERE column REGEXP 'pattern';
这将返回所有在"column"中匹配正则表达式'pattern'的行。
例如,如果你有一个"user"表,并且你想找出所有以字母"a"开始的用户名,你可以这样做:
SELECT username
FROM user
WHERE username REGEXP '^a';
这将返回所有用户名以"a"开始的用户。
请注意,MySQL中的正则表达式是大小写敏感的。如果你想进行大小写不敏感的匹配,你可以使用REGEXP_LIKE()函数,并提供'i'标志,如下所示:
SELECT column
FROM table
WHERE REGEXP_LIKE(column, 'pattern', 'i');
这将返回所有在"column"中匹配正则表达式'pattern'的行,不考虑大小写。
然而,正则表达式查询的缺点是它们通常无法利用索引,因此可能比其他类型的查询慢,特别是在处理大量数据时。你可以通过将正则表达式查询与其他类型的查询条件结合使用,或者限制查询的范围来优化查询性能。
# 七、性能优化
MySQL作为一种高性能的关系型数据库管理系统,在处理大量数据和高并发访问时需要进行性能优化,以提高系统的吞吐量和响应速度。
# 1、优化查询语句
查询语句是MySQL最常用的功能之一,也是性能优化的关键点之一。下面介绍一些优化查询语句的方法:
- 使用索引:索引是提高查询性能的重要手段,可以加快数据的查找和匹配。在使用索引时,需要考虑索引的类型、选择合适的列和优化查询语句等因素。
- 优化查询语句的条件:通过分析查询语句的条件,可以对查询语句进行优化。例如,可以使用覆盖索引(Covering Index)减少查询的IO开销,避免全表扫描等操作。
- 避免使用SELECT *:避免使用SELECT *可以减少查询的数据量和IO开销,提高查询性能。
- 使用内连接和左连接:内连接和左连接是优化查询语句的重要手段,可以将多张表的数据进行联合查询,减少数据库的IO开销。
- 避免在IN后面使用子查询:可能导致查询语句的效率降低。可以考虑使用Join操作代替子查询。
- 合理使用Group By和Order By:在使用Group By和Order By时,需要注意使用合适的索引和避免使用函数等操作,以提高查询性能。
- 避免隐式转换:比如id是long,就不要用string类型的id去查询
- where中避免使用函数:这样会不走索引
- 只要一行数据的时候,使用limit 1
- 千万不要order by rand()
# 2、explain详解
在优化查询语句时,可以通过explain命令来查看查询语句的执行计划,了解MySQL是如何执行查询语句的。
explain命令可以展示查询语句的执行计划、索引使用情况、表扫描次数等信息,为优化查询语句提供参考。
explain输出的所有列如下:
- id:查询的标识符,用于区分不同的查询。
- select_type:查询的类型,表示查询的复杂度,包括以下类型:
- SIMPLE:简单的SELECT查询,不包含子查询或UNION查询。
- PRIMARY:查询中的最外层查询,也称为主查询。
- UNION:UNION中的第二个或后续查询。
- DEPENDENT UNION:UNION中的第二个或后续查询,依赖于外部查询的结果集。
- SUBQUERY:子查询中的第一个查询。
- DEPENDENT SUBQUERY:子查询中的第一个查询,依赖于外部查询的结果集。
- DERIVED:派生表的SELECT查询,包括子查询中的派生表。
- table:查询的表名。
- partitions:查询的分区。
- type:索引的类型,表示MySQL执行查询时选择的索引类型,包括以下类型:
- null:不访问任何表和索引
- const:使用常数值匹配的索引,只有一行符合条件,速度最快。
- eq_ref:使用唯一索引或主键匹配的索引,通常用于连接查询。
- ref:使用非唯一索引匹配的索引,通常用于查询结果集较小的表。
- range:使用索引范围查找匹配的索引,通常用于对索引列进行范围查找。
- index:进行全索引扫描查找匹配的索引,通常用于查询结果集较小的表。
- index_merge:使用了索引合并优化(对多个索引分别进行条件扫描,然后将它们各自的结果进行合并)
- all:进行全表扫描查找匹配的索引,速度最慢。
- possible_keys:可能使用的索引。
- keys:实际使用的索引。
- key_len:索引使用的长度。
- ref:索引的参考列,表示使用的哪个列或常量与索引列进行匹配。
- rows:扫描的行数,表示MySQL预计要检查多少行来查找所需的行。
- filtered:返回结果的行数占总行数的比例,表示MySQL过滤了多少行。
- Extra:额外的信息,表示MySQL在查询过程中使用的一些特殊技术,包括以下类型:
- Using index:表示MySQL使用覆盖索引来避免读取行的数据,从而加快查询速度。
- Using where:表示MySQL使用WHERE过滤器来检索行,而不是使用索引。
- Using temporary:表示MySQL创建了一个临时表来处理查询中的一些数据,可能会影响查询性能。
- Using filesort:表示MySQL在对结果集进行排序时使用了文件排序算法,可能会影响查询性能。
- Using join buffer:表示MySQL使用了连接缓存来优化连接操作的性能。
- Using index condition:表示MySQL使用索引条件来过滤不符合条件的行,可能会提高查询性能。
- Impossible where:表示查询的WHERE条件无法匹配任何行。
- Select tables optimized away:表示MySQL优化查询过程中,从查询中删除了不需要的表,提高了查询速度。
- Backward index scan:mysql8的一个优化,让索引从后往前去扫描。对于倒序查询非常有用。
通过分析explain的输出结果,可以判断查询语句的执行效率和是否使用了索引,从而进行优化。例如,可以考虑优化查询语句的条件、增加索引或者调整索引的顺序等方法,以提高查询语句的执行效率。
# 3、分析慢查询日志
慢查询日志是MySQL提供的一种机制,用于记录执行时间超过一定阈值的查询语句。通过分析慢查询日志,可以发现数据库的性能瓶颈和优化点,从而对数据库进行优化。下面介绍如何分析慢查询日志:
开启慢查询日志:可以通过修改MySQL配置文件开启慢查询日志功能。找到my.cnf文件,添加以下配置项:
slow_query_log = 1 slow_query_log_file = /path/to/slow_query.log long_query_time = 1
其中,
slow_query_log
表示开启慢查询日志功能,slow_query_log_file
指定慢查询日志文件的存储位置,long_query_time
表示查询执行时间的阈值,单位为秒。在上述配置中,设置执行时间超过1秒的查询语句会被记录在慢查询日志中。收集慢查询日志:MySQL提供了
mysqldumpslow
工具,可以对慢查询日志进行分析和过滤,提取出关键信息。使用以下命令收集慢查询日志:mysqldumpslow /path/to/slow_query.log > /path/to/slow_query_report.txt
以上命令会将慢查询日志中的查询语句按照执行次数、执行时间等指标进行排序,保存在指定的报告文件中。
分析慢查询日志:可以通过查看报告文件来发现查询性能的瓶颈和优化点。可以根据执行次数、执行时间等指标来确定需要优化的查询语句,并进行相应的调整。
优化查询语句:根据分析结果,对查询语句进行优化。例如,可以添加索引、重构查询语句、缓存查询结果等操作,以提高数据库的性能。
使用慢查询日志功能可以帮助我们发现查询性能的瓶颈和优化点,并且可以针对性地进行性能优化,提高数据库的性能。
# 4、Performance Schema
Performance Schema是MySQL提供的一种性能监控工具,用于收集和展示MySQL数据库服务器的性能信息。
通过Performance Schema,可以了解MySQL的运行状况、识别性能瓶颈、优化查询语句、提高系统的可用性和性能。
# 4.1、Performance Schema概述
Performance Schema是MySQL 5.5及以上版本中提供的一种性能监控工具,可以收集MySQL服务器的性能信息,并将其保存到内存中或者持久化到磁盘中,供后续查询和分析。
Performance Schema可以收集的性能信息包括:SQL语句的执行时间、IO负载、锁等信息,以及各种MySQL内部子系统的性能指标。
与其他监控工具相比,Performance Schema具有以下优点:
- 精度高:可以提供非常详细的性能信息,包括每个SQL语句的执行时间、IO负载等细节信息。
- 低开销:可以通过配置控制Performance Schema的采样频率和采样粒度,减少对系统性能的影响。
- 灵活性高:可以根据需要对不同子系统的性能信息进行采样和过滤,实现定制化的监控方案。
但是,由于Performance Schema收集的性能信息较为详细,因此也需要较高的系统配置和资源消耗。需要根据实际情况进行配置和使用,避免对系统性能造成不必要的影响。
# 4.2、Performance Schema的使用方法
Performance Schema的使用方法分为以下几步:
# a、开启Performance Schema
在MySQL 5.5及以上版本中,Performance Schema默认是关闭的。需要在my.cnf配置文件中添加以下配置项,才能开启Performance Schema:
[mysqld]
performance_schema=1
以上配置表示开启Performance Schema功能。
# b、配置Performance Schema
Performance Schema提供了丰富的配置选项,可以根据需要进行调整。例如,可以配置Performance Schema的采样频率、采样粒度、过滤规则等,以便更好地监控MySQL的性能。
Performance Schema的配置项可以通过SQL语句进行配置,也可以通过配置文件进行配置。下面介绍如何使用SQL语句进行配置:
SET GLOBAL performance_schema_events_waits_history_size = 10000;
SET GLOBAL performance_schema_max_statement_classes = 200;
以上SQL语句分别设置Performance Schema的等待事件历史记录大小和最大语句类数。
# c、查询Performance Schema的性能信息
查询Performance Schema的性能信息需要使用SQL语句。可以使用以下SQL语句来查询Performance Schema的性能信息:
SELECT * FROM performance_schema.events_waits_summary_global_by_event_name;
以上SQL语句可以查询等待事件的汇总信息,包括等待事件名称、等待事件次数、等待事件总时间等指标。类似地,还可以查询其他子系统的性能指标,如:
-- 查询语句的执行次数和平均执行时间
SELECT * FROM performance_schema.events_statements_summary_by_digest;
-- 查询锁等待事件的汇总信息
SELECT * FROM performance_schema.table_lock_waits_summary_by_table;
使用Performance Schema可以非常方便地了解MySQL的性能状况,识别性能瓶颈,并进行优化。
# 4.3、Performance Schema的优化实践
下面介绍一些实际案例,展示如何使用Performance Schema进行性能优化。
# a、优化查询语句
查询语句的性能是MySQL性能优化的关键之一。通过Performance Schema可以监控每个查询语句的执行时间、执行次数等指标,并识别慢查询语句。下面以实际案例为例,介绍如何使用Performance Schema优化查询语句。
假设我们有一个用户表user
,其中包含id
、name
和age
三个字段,现在要查询所有年龄大于等于20岁的用户,并按照ID升序排序。查询语句如下:
SELECT id, name FROM user WHERE age >= 20 ORDER BY id ASC;
通过使用Performance Schema,可以收集该查询语句的性能信息,并找到其优化点。可以使用以下SQL语句来查询该查询语句的性能信息:
SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE '%SELECT id, name FROM user WHERE age >= 20 ORDER BY id ASC%';
以上SQL语句可以查询该查询语句的执行次数、总执行时间等指标。如果发现该查询语句的执行时间较长,可以考虑进行以下优化:
- 添加索引:如果查询语句中使用了WHERE子句或者ORDER BY子句,可以添加适当的索引来提高查询效率。
- 优化查询语句:可以通过重构查询语句、分解查询语句等方式来优化查询性能。
# b、优化锁等待事件
锁等待事件是MySQL性能优化的另一个关键点。通过Performance Schema,可以监控锁等待事件的情况,并识别锁等待事件的原因。下面以实际案例为例,介绍如何使用Performance Schema优化锁等待事件。
假设我们有一个订单表order
,其中包含id
、user_id
、amount
和status
四个字段,现在要向该表插入一条记录,并将订单状态设置为已完成。插入记录的SQL语句如下:
INSERT INTO `order` (`user_id`, `amount`, `status`) VALUES (123, 100, 'COMPLETED');
通过使用Performance Schema,可以收集该SQL语句的性能信息,并找到其优化点。可以使用以下SQL语句来查询该SQL语句的性能信息:
SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE '%INSERT INTO `order`%';
以上SQL语句可以查询该SQL语句的执行次数、总执行时间等指标。如果发现该SQL语句的锁等待时间较长,可以考虑进行以下优化:
- 优化锁等待策略:可以使用不同的锁等待策略来避免锁等待事件,例如使用行级锁或者乐观锁等。
- 优化事务的范围:可以将事务的范围缩小,减少锁等待事件的发生。
# 5、读写分离与主从复制
# 5.1、读写分离的原理与优点
# a、读写分离的原理
读写分离(Read-Write Separation)是指将数据库的读操作和写操作分离到不同的服务器上,以提高系统性能。在这种架构中,通常有一个主服务器(Master)负责处理写操作,以及多个从服务器(Slave)负责处理读操作。主服务器会将数据变更同步到从服务器,从而保证数据一致性。
# b、读写分离的优点
- 负载均衡:通过将读操作分布到多个从服务器上,可以有效减轻主服务器的压力,实现负载均衡。
- 提高系统可用性:当主服务器出现故障时,可以快速切换到从服务器上继续提供服务,提高系统可用性。
- 提高查询性能:从服务器可以根据实际需求创建不同的索引,从而提高查询性能。
- 实现数据备份与恢复:从服务器可以作为主服务器的数据备份,便于数据恢复。
# 5.2、主从复制的原理与配置
# a、主从复制的原理
主从复制(Master-Slave Replication)是指主服务器将数据变更同步到从服务器的过程。在MySQL中,主从复制通常采用基于二进制日志(Binary Log,简称binlog)的异步复制机制。主服务器在执行写操作时,会将变更记录到binlog中,从服务器通过获取并执行这些变更记录,实现与主服务器的数据同步。
# b、配置主从复制
配置主从复制主要包括以下几个步骤:
- 配置主服务器:修改主服务器的配置文件,启用binlog,设置唯一的服务器ID(server-id),并重启服务。
- 配置从服务器:修改从服务器的配置文件,设置唯一的服务器ID(server-id),并重启服务。
- 创建复制用户:在主服务器上创建一个具有复制权限的用户,并记录用户信息。
- 初始化从服务器数据:将主服务器的数据导出,并导入到从服务器上,保证二者数据一致。
- 启动主从复制:在从服务器上执行CHANGE MASTER命令,指定主服务器的地址、端口、复制用户信息以及binlog位置等信息,然后启动从服务器的复制线程。
# 5.3、主从复制的常见问题与优化
# a、主从延迟
由于主从复制采用异步复制机制,从服务器可能会出现与主服务器数据不一致的情况。为减小主从延迟,可以采取以下措施:
- 优化网络连接:确保主从服务器之间的网络连接稳定且高速,降低网络延迟。
- 减小事务大小:尽量避免执行大事务,以减小单次复制的数据量,加快复制速度。
- 优化从服务器硬件:提高从服务器的硬件配置,如CPU、内存和磁盘性能,以加快数据同步速度。
- 并行复制:启用MySQL的并行复制功能,允许从服务器同时执行多个复制线程,提高复制效率。
# b、主从不一致
主从不一致是指从服务器的数据与主服务器的数据不完全相同。这可能是由于网络故障、从服务器故障或其他原因导致的。解决主从不一致的方法包括:
- 监控主从状态:定期检查主从服务器的复制状态,确保复制过程正常进行。
- 数据校验:使用工具(如pt-table-checksum)定期对比主从服务器的数据,发现并修复不一致。
- 故障恢复:当从服务器出现故障时,可以使用主服务器的备份数据进行恢复,然后重新启动复制。
# 5.4、读写分离与主从复制的结合
将读写分离与主从复制结合使用,可以进一步提高数据库系统的性能和可用性。实现这一目标的关键在于合理地分配读写请求:
- 读请求分配:将读请求发送到从服务器,以减轻主服务器的负担。可根据从服务器的负载情况动态调整分配策略,实现负载均衡。
- 写请求分配:将写请求发送到主服务器,确保数据的一致性。当主服务器出现故障时,可以将写请求切换到备用主服务器上,提高系统可用性。
为实现这些功能,可以使用负载均衡器、代理服务器等工具,如HAProxy、MySQL Router等。这些工具可以帮助实现自动切换、负载均衡和故障恢复等功能,提高数据库系统的性能和稳定性。
# 6、优化提示
优化提示(Optimizer Hints)是MySQL中的一种特性,它允许在编写SQL查询时向查询优化器提供关于如何执行查询的额外信息。优化提示可以帮助开发者在特定情况下手动干预查询优化器的决策,以提高查询性能。需要注意的是,优化提示并非万能,正确使用它需要对查询优化器的工作原理和表结构有一定的了解。此外,滥用优化提示可能会导致查询性能降低。
在MySQL 8.0及更高版本中,优化提示使用/*+ ... */
注释形式包含在查询中。以下是一些常用的优化提示示例:
# 6.1、使用指定的索引
当查询优化器没有选择合适的索引时,可以使用USE_INDEX
或FORCE_INDEX
提示指定要使用的索引。例如:
SELECT /*+ USE_INDEX(idx_employees_name) */ * FROM employees WHERE name = 'John Doe';
# 6.2、忽略指定的索引
当查询优化器错误地选择了某个索引时,可以使用IGNORE_INDEX
提示忽略该索引。例如:
SELECT /*+ IGNORE_INDEX(idx_employees_name) */ * FROM employees WHERE name = 'John Doe';
# 6.3、控制连接顺序
在多表连接查询中,可以使用STRAIGHT_JOIN
提示强制按照FROM子句中的表顺序进行连接。例如:
SELECT /*+ STRAIGHT_JOIN */ e.name, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id;
# 6.4、控制连接类型
在某些情况下,可以使用优化提示强制使用特定类型的连接(如NESTED_LOOP
、HASH_JOIN
或MERGE_JOIN
)。例如:
SELECT /*+ HASH_JOIN(d) */ e.name, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id;
# 6.5、控制子查询执行策略
可以使用优化提示强制将子查询转换为连接(SUBQUERY_TO_DERIVED
)或将连接转换为子查询(DERIVED_TO_SUBQUERY
)。例如:
SELECT /*+ SUBQUERY_TO_DERIVED(t2) */ *
FROM table1 t1
WHERE t1.id = (SELECT id FROM table2 t2 WHERE t2.t1_id = t1.id);
在使用优化提示时需要谨慎,因为滥用它可能会导致查询性能降低。在使用优化提示之前,建议先分析查询计划并充分了解查询优化器的工作原理和表结构。
# 八、分库分表
# 1、分库分表的概念与场景
随着业务的发展和数据量的增长,单一数据库可能无法满足性能、容量和可用性的需求。在这种情况下,我们可以采用分库分表(Sharding)的方式对数据库进行水平切分,将数据分散到多个数据库和表中,提高系统的性能、可扩展性和可用性。
分库分表的主要应用场景包括:
- 数据量巨大:当单表数据量达到数百万甚至数十亿时,查询和维护数据的性能可能受到影响。通过分库分表,我们可以将数据分散到多个表中,降低单表的数据量,提高查询性能。
- 高并发访问:在高并发访问的场景下,单一数据库可能成为系统性能的瓶颈。分库分表可以将请求分散到多个数据库实例中,提高系统的并发处理能力。
- 可用性:通过分库分表,我们可以在不同的服务器上部署数据库实例,提高系统的可用性,降低单点故障的风险。
# 2、分库分表策略
分库分表的策略主要有以下几种:
- 基于范围的分库分表:根据数据的某个属性值(如时间、ID等)划分范围,将数据分配到不同的库或表中。这种策略简单易实现,但需要预先规划好数据的分布范围,否则可能导致数据分布不均匀。
- 基于哈希的分库分表:通过某个属性值(如用户ID)计算哈希值,根据哈希值将数据分配到不同的库或表中。这种策略可以实现较好的数据分布,但在扩容或缩容时需要重新计算哈希值,可能导致数据迁移的开销较大。
- 基于取模的分库分表:通过某个属性值(如订单ID)对库或表的数量取模,将数据分配到不同的库或表中。这种策略简单易实现,数据分布相对均匀,但在扩容或缩容时可能需要重新分配数据。
# 3、分库分表实现方式
在MySQL中,有多种方式可以实现分库分表:
- 使用应用程序进行分库分表:在应用程序中实现分库分表的逻辑,根据数据的属性值选择合适的数据库和表。这种方式对应用程序的侵入性较大,但灵活性高,易于实现。
- 使用数据库代理进行分库分表:通过数据库代理(如ShardingSphere、MyCat等)对客户端的请求进行拦截和路由,将请求分配到不同的数据库实例中。这种方式对应用程序的侵入性较小,但需要引入额外的组件进行管理和维护。
- 使用MySQL自带的分区表功能进行分库分表:在MySQL 5.1及以上版本中,提供了分区表(Partitioning)的功能,可以将一个大表按照某个属性值进行分区,实现数据的水平切分。这种方式不需要引入额外的组件,但需要对表结构进行修改。
上述实现方式的具体使用场景:
- 分库:数据库系统的CPU和IO资源紧张,需要将数据分散到多个数据库实例中,实现资源的分布式利用,提高系统的性能和可用性。复杂度较高。
- 分表:表数据量太大,查询和插入性能下降,需要把一张表按一定的规则分解成N个具有独立存储空间的实体表,提高查询和插入的性能。复杂度较高。
- 分区表:表数据量太大,查询和插入性能下降,需要把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的。灵活性不如分表。
参考资料:
# 4、分库分表的优缺点及其应用实践
分库分表的优点包括:
- 提高了数据库的性能和容量:将数据分散到多个数据库和表中,可以提高查询和更新数据的效率,增加系统的容量和可用性。
- 改善了系统的可扩展性:可以根据数据的增长情况对数据库进行扩容或缩容,实现系统的无缝扩展。
- 提高了系统的可用性:通过将数据分散到多个数据库实例中,可以提高系统的容错能力,降低单点故障的风险。
分库分表的缺点包括:
- 管理和维护的成本增加:分库分表后,需要对多个数据库和表进行管理和维护,增加了管理和运维的复杂性和成本。
- 可能导致数据一致性问题:在分库分表的过程中,需要考虑如何保证数据的一致性,避免数据重复、丢失或者不一致的问题。
- 部分SQL语句难以优化:对于一些跨分片的SQL查询(如Join操作),可能需要进行全局扫描,影响性能。
在应用实践中,需要根据具体的场景和需求选择合适的分库分表策略和实现方式。同时,在分库分表的过程中,需要考虑数据的一致性和性能优化等问题,确保系统的稳定性和可用性。
# 5、分库分表的未来趋势:分布式数据库
分布式数据库是指将数据库分布在多个节点上进行存储和处理的一种技术。
与传统的单机数据库相比,分布式数据库具有以下几个优势:
高可扩展性:分布式数据库可以将数据和计算负载分布在多个节点上,可以根据需求动态增加或减少节点,从而实现高可扩展性。而分库分表可以将数据和计算负载分散到多个子数据库和表中,进一步提高了可扩展性。
高可用性:分布式数据库通过将数据复制到多个节点上实现高可用性,当某个节点出现故障时,可以自动切换到其他节点上继续提供服务。而分库分表可以将数据复制到多个子数据库和表中,进一步提高了可用性。
提高性能:分布式数据库可以将数据和计算负载分布在多个节点上,并行处理数据和请求,从而提高了性能。而分库分表可以将数据和计算负载分散到多个子数据库和表中,进一步提高了性能。
灵活性:分布式数据库可以根据需求将数据和计算负载分布在不同的节点上,可以灵活地进行调整和配置。而分库分表可以将数据和计算负载分散到不同的子数据库和表中,进一步提高了灵活性。
我们以OLTP的场景为例,目前流行的分布式数据库包括TiDB、OceanBase、CockroachDB等。
我们看TiDB的官网介绍:
TiDB 具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 协议和 MySQL 生态等重要特性。 TiDB 适合高可用、强一致要求较高、数据规模较大等各种应用场景。
TiDB的实现原理参考 TiDB 整体架构 (opens new window)
# 九、事务与隔离
# 1、事务的概念与特性
事务(Transaction)是数据库管理系统(DBMS)中一个独立的、逻辑上的操作序列,包括对数据的一系列读取和/或修改操作。一个事务应该具有以下四个特性,简称为ACID特性:
- 原子性(Atomicity):事务中的所有操作要么全部成功执行,要么全部失败执行,不会出现部分成功、部分失败的情况。
- 一致性(Consistency):事务开始前和结束后,数据库的状态都必须是一致的。一致性保证了数据库从一个一致状态转换到另一个一致状态。
- 隔离性(Isolation):多个事务在并发执行时,它们之间应该是相互隔离的,一个事务不应该影响其他正在执行的事务。
- 持久性(Durability):一旦事务提交,其对数据库的修改将永久保存,即使系统崩溃或断电,也能保证数据的完整性。
# 2、事务隔离级别
事务隔离级别用来定义事务之间的隔离程度,以解决多个事务并发执行时可能出现的问题,如脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)。SQL标准定义了四个隔离级别:
- 读未提交(Read Uncommitted):事务可以读取其他未提交事务的修改。可能导致脏读、不可重复读和幻读。
- 读已提交(Read Committed):事务只能读取其他已提交事务的修改。可以避免脏读,但仍可能导致不可重复读和幻读。
- 可重复读(Repeatable Read):在同一事务内,多次读取同一数据,结果始终一致。可避免脏读和不可重复读,但仍可能导致幻读。
- 串行化(Serializable):事务完全串行执行,最严格的隔离级别。可以避免脏读、不可重复读和幻读,但并发性能较差。
# 3、MySQL的事务实现与管理
MySQL使用InnoDB
引擎作为默认的事务存储引擎,它支持ACID
特性和四种隔离级别。以下是MySQL中事务的实现与管理:
# 3.1、事务操作命令
- 开启事务:可以通过命令
START TRANSACTION
或BEGIN
来显式开启一个事务。在自动提交模式下,每个单独的SQL语句都被视为一个事务。
-- 显式开启事务
START TRANSACTION;
-- 或者
BEGIN;
-- 查看自动提交状态
SHOW VARIABLES LIKE 'autocommit';
-- 关闭自动提交
SET autocommit = 0;
- 提交事务:使用
COMMIT
命令提交事务,将对数据库的修改永久保存。
COMMIT;
- 回滚事务:使用
ROLLBACK
命令回滚事务,撤销对数据库的修改。
ROLLBACK;
- 保存点(Savepoint):可以在事务中设置保存点,用于在事务执行过程中回滚到某个特定点。
-- 创建保存点
SAVEPOINT sp1;
-- 执行一些操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 创建另一个保存点
SAVEPOINT sp2;
-- 执行更多操作
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 回滚到sp1
ROLLBACK TO sp1;
-- 释放保存点
RELEASE SAVEPOINT sp1;
# 3.2、MVCC(多版本并发控制)实现原理
InnoDB
引擎通过MVCC
机制实现高并发性能。MVCC
的核心组件包括:
# a、隐藏列
每个InnoDB
表都有三个隐藏列:
DB_TRX_ID
:6字节,记录最后一次插入或更新该行的事务IDDB_ROLL_PTR
:7字节,回滚指针,指向undo log
记录DB_ROW_ID
:6字节,行ID(如果没有主键时使用)
# b、Undo Log
Undo Log
保存了数据的历史版本,用于事务回滚和MVCC
读取:
-- 查看undo log相关参数
SHOW VARIABLES LIKE '%undo%';
-- 查看undo表空间
SELECT * FROM information_schema.FILES WHERE FILE_TYPE='UNDO LOG';
# c、Read View
Read View
是事务在某个时间点的数据库快照,包含:
m_ids
:活跃事务ID列表min_trx_id
:最小活跃事务IDmax_trx_id
:下一个要分配的事务IDcreator_trx_id
:创建该Read View
的事务ID
# d、可见性判断规则
-- 示例:演示MVCC的可见性
-- Session 1
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 看到版本1
-- Session 2
START TRANSACTION;
UPDATE users SET name = 'new_name' WHERE id = 1;
COMMIT;
-- Session 1(在可重复读级别下)
SELECT * FROM users WHERE id = 1; -- 仍然看到版本1
COMMIT;
# 3.3、锁机制详解
InnoDB
提供多种锁类型:
# a、行级锁类型
- 记录锁(Record Lock):锁定索引记录
- 间隙锁(Gap Lock):锁定索引记录之间的间隙
- 临键锁(Next-Key Lock):记录锁+间隙锁的组合
-- 查看当前锁信息
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待信息
SELECT * FROM performance_schema.data_lock_waits;
# b、意向锁
意向锁是表级锁,用于快速判断表中是否有行锁:
IS
(意向共享锁):事务想要获取表中某些行的共享锁IX
(意向排他锁):事务想要获取表中某些行的排他锁
# 3.4、事务隔离级别实践
-- 查看当前隔离级别
SELECT @@tx_isolation;
-- MySQL 8.0使用
SELECT @@transaction_isolation;
-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# a、隔离级别对比示例
-- 创建测试表
CREATE TABLE test_isolation (
id INT PRIMARY KEY,
value INT
);
INSERT INTO test_isolation VALUES (1, 100), (2, 200);
-- 脏读示例(READ UNCOMMITTED)
-- Session 1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM test_isolation WHERE id = 1;
-- Session 2
START TRANSACTION;
UPDATE test_isolation SET value = 150 WHERE id = 1;
-- 不提交
-- Session 1
SELECT * FROM test_isolation WHERE id = 1; -- 能看到未提交的150
-- 不可重复读示例(READ COMMITTED)
-- Session 1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM test_isolation WHERE id = 1; -- value = 100
-- Session 2
UPDATE test_isolation SET value = 150 WHERE id = 1;
COMMIT;
-- Session 1
SELECT * FROM test_isolation WHERE id = 1; -- value = 150(变化了)
-- 幻读示例(REPEATABLE READ)
-- Session 1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT COUNT(*) FROM test_isolation WHERE value > 100; -- 假设是1
-- Session 2
INSERT INTO test_isolation VALUES (3, 300);
COMMIT;
-- Session 1
SELECT COUNT(*) FROM test_isolation WHERE value > 100; -- 仍然是1
UPDATE test_isolation SET value = value + 10 WHERE value > 100; -- 会更新2行!
# 3.5、事务性能优化建议
- 控制事务大小:避免大事务,将大事务拆分为多个小事务
- 尽快提交:减少锁持有时间
- 合理使用索引:避免全表扫描导致的表锁
- 避免死锁:按相同顺序访问表和行
- 监控长事务:
-- 查找长事务
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
-- 查看事务锁等待
SHOW ENGINE INNODB STATUS;
在实际应用中,应根据业务需求和性能要求选择合适的事务隔离级别。高并发场景下,可以适当降低隔离级别以提高性能,但需要权衡可能出现的数据一致性问题。反之,在需要严格保证数据一致性的场景下,应选择较高的隔离级别,如串行化。
# 十、锁机制
# 1、锁的概念与分类
在数据库系统中,锁是一种用于实现并发控制的机制。当多个用户并发访问数据库时,锁可以确保数据的一致性和完整性。根据不同的使用场景和实现方式,锁可以分为以下几种:
- 共享锁(Shared Locks):允许多个事务同时读取同一数据项,但在持有共享锁的情况下,不允许任何事务对数据进行修改。
- 排他锁(Exclusive Locks):当一个事务持有排他锁时,不允许其他事务读取或修改此数据项。排他锁用于数据修改操作,如更新、删除等。
- 意向锁(Intention Locks):意向锁是一种预先声明锁类型的锁,用于避免锁冲突。意向锁分为意向共享锁(Intention Shared Locks)和意向排他锁(Intention Exclusive Locks)。
- 记录锁(Record Locks):记录锁是针对数据表中的单个记录进行加锁,用于保护数据行。
- 表锁(Table Locks):表锁是对整个数据表进行加锁,防止其他事务对表中的任何记录进行操作。
- 页锁(Page Locks):页锁是针对数据表中的一页数据进行加锁,介于记录锁和表锁之间。
# 2、MySQL的锁实现
MySQL根据存储引擎的不同,实现了不同的锁机制。这里以InnoDB存储引擎为例,介绍MySQL的锁实现。
- 行级锁(Row-level Locking):InnoDB存储引擎支持行级锁,即对单个数据行进行加锁。行级锁可以有效减少锁冲突,提高并发性能。在InnoDB中,行级锁包括记录锁、间隙锁(Gap Locks)和临键锁(Next-key Locks)。
- 表级锁(Table-level Locking):MyISAM存储引擎使用表级锁来实现锁机制。表级锁粒度较大,可能导致锁冲突和性能问题。在某些场景下,InnoDB也会使用表级锁。
# 3、锁的优化与应用场景
根据实际应用场景选择合适的锁策略,可以提高数据库性能。以下是一些锁优化和应用场景的建议:
# 3.1、锁优化策略
# a、避免长时间持有锁
长时间持有锁可能导致其他事务等待,降低系统并发性能:
-- 不好的做法:大事务
START TRANSACTION;
SELECT * FROM orders WHERE status = 'pending';
-- 进行复杂的业务逻辑处理(耗时)
UPDATE orders SET status = 'processed' WHERE id IN (...);
COMMIT;
-- 好的做法:拆分事务
START TRANSACTION;
SELECT * FROM orders WHERE status = 'pending';
COMMIT;
-- 处理业务逻辑
START TRANSACTION;
UPDATE orders SET status = 'processed' WHERE id IN (...);
COMMIT;
# b、尽量使用行级锁
在InnoDB
存储引擎中,行级锁能够有效降低锁冲突的可能性:
-- 使用主键或唯一索引(行级锁)
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 避免无索引条件(可能导致表锁)
UPDATE users SET balance = balance - 100 WHERE name = 'John'; -- name无索引
# c、合理使用索引
使用索引可以帮助数据库更精确地锁定数据行:
-- 创建合适的索引
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_user_date ON transactions(user_id, created_date);
-- 查看锁定的行数
SHOW STATUS LIKE 'Innodb_row_lock%';
# 3.2、死锁检测与处理
# a、死锁的产生
死锁是指两个或多个事务相互等待对方释放锁的情况:
-- 示例:死锁场景
-- Transaction 1
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 等待...
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Transaction 2(同时执行)
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
-- 等待...
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
-- 死锁发生!
# b、死锁检测
MySQL会自动检测死锁并选择回滚其中一个事务:
-- 查看最近的死锁信息
SHOW ENGINE INNODB STATUS\G
-- 查看死锁相关参数
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 监控死锁
SELECT * FROM performance_schema.events_errors_summary_by_thread_by_error
WHERE ERROR_NAME = 'ER_LOCK_DEADLOCK';
# c、避免死锁的最佳实践
- 按相同顺序访问表和行:
-- 所有事务都按id升序更新
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
- 使用较小的事务:
-- 将大事务拆分为小事务
-- 批量更新时使用LIMIT
UPDATE large_table SET status = 'processed'
WHERE status = 'pending'
LIMIT 1000;
- 设置锁等待超时:
-- 设置锁等待超时时间(秒)
SET innodb_lock_wait_timeout = 50;
-- 在应用层处理超时
BEGIN;
-- 执行SQL
-- 如果超时,捕获异常并重试
COMMIT;
# 3.3、乐观锁与悲观锁
# a、悲观锁实现
悲观锁假设会发生并发冲突,提前加锁:
-- 使用SELECT ... FOR UPDATE加悲观锁
START TRANSACTION;
SELECT * FROM inventory WHERE product_id = 1 FOR UPDATE;
-- 检查库存
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 1;
COMMIT;
-- 使用LOCK IN SHARE MODE加共享锁
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE;
# b、乐观锁实现
乐观锁通过版本号或时间戳实现:
-- 添加版本号字段
ALTER TABLE products ADD COLUMN version INT DEFAULT 0;
-- 更新时检查版本号
UPDATE products
SET price = 99.99, version = version + 1
WHERE id = 1 AND version = 5;
-- 检查影响行数,如果为0说明版本已变化
-- 应用层代码示例
IF (affected_rows == 0) {
-- 版本冲突,重新读取并重试
}
# 3.4、锁监控与诊断
-- 查看当前锁等待
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
-- 查看锁的详细信息
SELECT * FROM performance_schema.data_locks;
-- 查看元数据锁
SELECT * FROM performance_schema.metadata_locks;
-- 终止长时间运行的事务
KILL CONNECTION <thread_id>;
# 3.5、特定场景的锁策略
# a、高并发秒杀场景
-- 使用Redis分布式锁或队列
-- 数据库层面使用乐观锁
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0;
# b、批量数据处理
-- 分批处理,避免长时间锁表
DELIMITER $$
CREATE PROCEDURE batch_update()
BEGIN
DECLARE done INT DEFAULT 0;
REPEAT
UPDATE large_table
SET processed = 1
WHERE processed = 0
LIMIT 1000;
SET done = ROW_COUNT();
-- 暂停,让其他事务执行
DO SLEEP(0.1);
UNTIL done < 1000 END REPEAT;
END$$
DELIMITER ;
# c、读写分离场景
-- 主库写入
INSERT INTO orders (...) VALUES (...);
-- 从库读取(避免锁竞争)
SELECT * FROM orders WHERE user_id = 1;
通过以上优化措施和应用场景的选择,可以有效提高MySQL数据库的性能和并发能力,满足不同业务场景的需求。
# 十一、备份与恢复
# 1、备份策略与工具
备份是确保数据安全和可用性的重要手段。MySQL数据库支持两种主要的备份策略:物理备份和逻辑备份。
# 1.1、物理备份
物理备份是指将数据库中的数据文件、日志文件和其他相关文件复制到备份介质的过程。物理备份通常更快,恢复时也能节省时间。以下是一些常用的物理备份工具:
mysqldump
:mysqldump
工具可以将数据库数据导出为SQL语句,但也可以导出为二进制文件(使用--hex-blob
选项),从而实现物理备份。mysqlhotcopy
:mysqlhotcopy
工具可以在线进行物理备份,但仅适用于MyISAM和Archive存储引擎。- Percona XtraBackup: Percona XtraBackup是一个开源的物理备份工具,支持在线备份InnoDB和XtraDB存储引擎的数据。
# 1.2、逻辑备份
逻辑备份是指将数据库中的数据导出为逻辑格式(如SQL语句),这种备份方法具有更好的可移植性,但备份和恢复速度通常较慢。以下是一些常用的逻辑备份工具:
mysqldump
:mysqldump
工具是MySQL官方提供的逻辑备份工具,可以将数据库数据导出为SQL语句。mysqlpump
:mysqlpump
工具与mysqldump
类似,但支持并行备份,性能更高。MyDumper
: MyDumper (opens new window)是一个开源的逻辑备份工具,具有并行备份、压缩备份等功能,性能优于mysqldump
。
# 2、恢复策略与方法
根据备份数据和恢复需求的不同,可以选择不同的恢复策略和方法。
# 2.1、全量恢复
全量恢复是指使用全量备份数据将数据库恢复到备份时的状态。全量恢复适用于数据丢失或损坏的情况,恢复过程相对简单。
常用的工具:
mysql
: 使用mysql客户端工具,可以执行由mysqldump生成的SQL脚本文件,实现全量恢复。mysqlhotcopy
: 对于MyISAM和Archive存储引擎,可以使用mysqlhotcopy工具进行恢复。Percona XtraBackup
: 使用Percona XtraBackup工具生成的备份文件,可以通过innobackupex或xtrabackup命令进行恢复。
# 2.2、增量恢复
增量恢复是指在全量恢复的基础上,应用增量备份数据将数据库恢复到某个时间点的状态。增量恢复可以减少数据丢失的风险,但恢复过程较为复杂。
常用的工具:
Percona XtraBackup
: Percona XtraBackup不仅支持全量恢复,还支持增量恢复。通过--incremental选项可以实现增量备份,然后再使用innobackupex或xtrabackup命令进行恢复。mysqlbinlog
: mysqlbinlog工具可以解析二进制日志文件(Binary Logs),将其转换为SQL语句,然后通过mysql客户端工具执行,实现增量恢复。
# 2.3、点时间恢复
点时间恢复(PITR)是指将数据库恢复到某个特定时间点的状态。通过应用二进制日志(Binary Logs)或其他增量备份数据,可以实现点时间恢复。点时间恢复适用于误操作或部分数据损坏的情况,可以最大限度地减少数据丢失。
常用的工具:
mysqlbinlog
: 点时间恢复(PITR)可以通过mysqlbinlog工具实现。使用--start-datetime和--stop-datetime选项,可以指定需要恢复的时间范围。解析出相应时间范围内的SQL语句后,通过mysql客户端工具执行,实现点时间恢复。
# 2.4、数据库备份与恢复的最佳实践
为了确保数据安全和可用性,遵循以下备份与恢复的最佳实践是非常重要的:
- 定期备份:根据业务需求和数据变化情况,制定合理的备份计划,定期进行全量备份和增量备份。
- 备份验证:定期验证备份数据的完整性和可用性,确保在需要时能够顺利恢复。
- 离线存储备份:将备份数据存储在离线介质(如磁带、光盘等),以防止在线存储设备的故障导致数据丢失。
- 远程备份:将备份数据存储在远程位置,以防止本地灾难导致数据丢失。
- 监控备份任务:对备份任务进行监控,确保备份任务按计划执行,并及时处理备份失败等异常情况。
- 恢复演练:定期进行恢复演练,验证恢复流程的可行性,提高恢复能力。
- 文档化备份与恢复流程:编写详细的备份与恢复流程文档,确保在紧急情况下能够快速找到恢复所需的信息。
遵循以上最佳实践,可以有效保障MySQL数据库的数据安全和可用性,降低数据丢失风险。
# 十二、MySQL安全管理
确保数据库的安全性是非常重要的,本章将介绍MySQL的安全管理相关内容,包括用户与权限管理、数据库加密与安全传输、审计日志与监控以及常见安全漏洞与防范。
# 1、用户与权限管理
用户和权限管理是数据库安全管理的基础。在MySQL中,可以通过以下方法进行用户与权限管理:
- 创建用户:使用
CREATE USER
语句创建新用户,并通过IDENTIFIED BY
指定密码。例如:CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
。 - 授权:使用
GRANT
语句为用户分配权限,例如:GRANT SELECT, INSERT ON database_name.* TO 'username'@'localhost';
。 - 撤销权限:使用
REVOKE
语句撤销用户的权限,例如:REVOKE INSERT ON database_name.* FROM 'username'@'localhost';
。 - 删除用户:使用
DROP USER
语句删除用户,例如:DROP USER 'username'@'localhost';
。 - 修改密码:使用
ALTER USER
语句修改用户密码,例如:ALTER USER 'username'@'localhost' IDENTIFIED BY 'new_password';
。
为了确保数据库安全,应遵循最小权限原则,只授予用户必要的权限。
# 2、数据库加密与安全传输
数据库加密和安全传输是确保数据安全的关键措施。在MySQL中,可以采用以下方法实现数据加密和安全传输:
- 数据加密:可以使用MySQL的内置加密函数(如
AES_ENCRYPT()
、AES_DECRYPT()
等)进行数据加密。此外,MySQL Enterprise版还提供了透明数据加密(TDE)功能,可以自动加密存储在磁盘上的数据。 - 安全传输:为了确保客户端与服务器之间的通信安全,可以启用SSL/TLS加密。在MySQL服务器配置文件中,通过
require_secure_transport
选项启用安全传输,并配置相应的证书文件。
# 3、审计日志与监控
审计日志和监控是数据库安全管理的重要组成部分。在MySQL中,可以通过以下方法进行审计日志与监控:
- 审计日志:MySQL Enterprise版提供了审计日志插件,可以记录用户的操作日志。对于社区版,可以使用第三方插件(如
MariaDB Audit Plugin
)实现审计功能。 - 监控:可以使用
SHOW PROCESSLIST
命令查看当前正在执行的进程;使用SHOW STATUS
命令查看服务器状态信息。此外,还可以使用Performance Schema
收集性能数据,以便进行性能监控和调优。
# 4、常见安全漏洞与防范
以下是一些常见的MySQL安全漏洞及防范方法:
- 弱密码:使用强密码策略,避免简单或者易猜的密码,增加密码的复杂性和长度。
- SQL注入:对用户输入进行验证和过滤,使用预编译语句(例如:
PreparedStatements
)以避免SQL注入攻击。 - 未授权访问:限制用户对数据的访问权限,实施IP地址白名单策略,仅允许特定IP地址访问数据库。
- 暴力破解:限制用户登录失败次数,超过一定次数后锁定账户;启用防火墙,限制外部对数据库的访问。
- 信息泄露:不要在错误信息中泄露敏感数据,例如数据库用户名、密码等。
- 不安全配置:定期检查和更新数据库配置,确保数据库软件运行在安全的环境中。例如,禁用不必要的存储引擎、限制文件权限等。
遵循以上安全管理实践,可以有效提高MySQL数据库的安全性,降低受到攻击和数据泄露的风险。在实际运维中,应根据具体业务需求和安全策略,结合多种安全措施,确保数据库安全。
# 十三、工具
Mysql可视化工具推荐:
- SQLyog (opens new window)
- Navicat (opens new window)
- MySQL Workbench (opens new window)
- HeidiSQL (opens new window)
- DBeaver (opens new window)
Mysql数据同步工具推荐:
# 十四、总结
本文从各个方面展示了MySQL数据库的关键知识点。
通过阅读本文,读者可以深入了解MySQL数据库的特性、功能和应用场景,掌握使用MySQL进行数据操作和管理的基本技巧,以及如何进行性能优化、备份恢复和安全管理。
希望本文能够帮助读者在数据库领域建立扎实的基础,更好地应用MySQL数据库解决实际问题。
祝你变得更强!