理解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、PostgreSQL等)
- Oracle:Oracle数据库是一种商业关系型数据库,具有强大的功能、高性能和稳定性。但是,Oracle的许可费较高,对于中小型企业来说可能成本较大。
- SQL Server:SQL Server是微软推出的关系型数据库,与其它微软产品有很好的兼容性。SQL Server在性能和功能上与Oracle类似,但许可费较低。
- PostgreSQL:PostgreSQL是一种开源关系型数据库,具有丰富的功能和高度可扩展性。相较于MySQL,PostgreSQL支持更多的SQL标准和高级功能,如物化视图和存储过程等。
- MySQL:MySQL是一种开源关系型数据库,具有较好的性能和稳定性。与其他关系型数据库相比,MySQL的优势在于其开源、免费、易用和有丰富的社区资源。
# 3. MySQL的数据类型及常用场景
MySQL支持多种数据类型,这些数据类型可以分为以下几类:
# 数值类型(Numeric Types)
- TINYINT:1字节,有符号范围为-128到127,无符号范围为0到255。用于存储很小的整数,如开关状态、状态码等。
- SMALLINT:2字节,有符号范围为-32768到32767,无符号范围为0到65535。用于存储较小的整数,如年龄、身高等。
- MEDIUMINT:3字节,有符号范围为-8388608到8388607,无符号范围为0到16777215。用于存储中等大小的整数,如IP地址。
- INT:4字节,有符号范围为-2147483648到2147483647,无符号范围为0到4294967295。用于存储大多数整数类型数据,如订单号、用户ID等。
- BIGINT:8字节,有符号范围为-9223372036854775808到9223372036854775807,无符号范围为0到18446744073709551615。用于存储非常大的整数,如全球唯一的ID号。
- FLOAT:4字节,用于存储单精度浮点数,能够表示的范围为-3.4028235 x 10^38到3.4028235 x 10^38,精度为大约7位小数。用于存储需要浮点数精度的数据,如价格、温度等。
- DOUBLE:8字节,用于存储双精度浮点数,能够表示的范围为-1.7976931348623157 x 10^308到1.7976931348623157 x 10^308,精度为大约15位小数。用于存储需要更高精度的数据,如经纬度、科学计算结果等。
- DECIMAL:变长,占用空间与精度有关(5-17个字节)。用于存储任意精度的十进制数,适用于需要精确计算的场景,如财务应用、金融应用等。
# 字符串类型(String Types)
- CHAR:定长字符串,占用指定长度的存储空间,最大长度为255个字符。用于存储长度固定的字符数据,如国家代码等。
- VARCHAR:变长字符串,占用实际存储的字符数加1个字节的空间,最大长度为65535个字符。用于存储长度不固定的字符数据,如用户名、地址等。
- TINYTEXT:变长字符串,占用实际存储的字符数加1个字节的空间,最大长度为255个字符。用于存储较短的文本数据,如短消息、评论等。
- TEXT:可变长度的字符数据,最大长度为65535个字符。用于存储较大的文本数据,如文章、公告、邮件等。
- MEDIUMTEXT:可变长度的字符数据,最大长度为16777215个字符。用于存储更大的文本数据,如新闻、报告等。
- LONGTEXT:可变长度的字符数据,最大长度为4294967295个字符,或者4GB。用于存储非常大的文本数据,如长篇小说、电子书等。
- BINARY:定长二进制数据,占用指定长度的存储空间,最大长度为255个字节。用于存储长度固定的二进制数据,如图片的缩略图等。
- VARBINARY:变长二进制数据,占用实际存储的字节数加1个字节的空间,最大长度为65535个字节。用于存储长度不固定的二进制数据,如图片、音频、视频等。
- TINYBLOB:变长二进制数据,占用实际存储的字节数加1个字节的空间,最大长度为255个字节。用于存储较小的二进制数据,如二进制文件、压缩文件等。
- BLOB:可变长度的二进制数据,最大长度为65535个字节。用于存储较大的二进制数据,如二进制文件、音频、视频等。
- MEDIUMBLOB:可变长度的二进制数据,最大长度为16777215个字节。用于存储更大的二进制数据,如高清视频、音乐文件等。
- LONGBLOB:可变长度的二进制数据,最大长度为4294967295个字节,或者4GB。用于存储非常大的二进制数据,如4K、8K高清视频等。
- ENUM:枚举类型,用于存储一个固定的值集合中的一个值。用于存储取值范围固定的数据,如星期几、性别等。
- SET:集合类型,用于存储一个固定的值集合中的多个值。用于存储多选项数据,如颜色、标签等。
# 日期时间类型(Date and Time Types)
- DATE:日期类型,用于存储日期数据,格式为'YYYY-MM-DD'。用于存储日期数据,如出生日期、创建日期等。
- TIME:时间类型,用于存储时间数据,格式为'HH:MM:SS'。用于存储时间数据,如会议时间、课程时长等。
- YEAR:年份类型,用于存储2位或4位的年份数据。用于存储年份数据,如毕业年份等。
- DATETIME:日期时间类型,用于存储日期和时间数据,格式为'YYYY-MM-DD HH:MM:SS'。用于存储精确到秒的日期和时间数据,如日程安排、日志记录等。
- TIMESTAMP:时间戳类型,用于存储日期和时间数据,格式为'YYYY-MM-DD HH:MM:SS'。与DATETIME类型类似,但是它的取值范围更广,可以存储的时间范围为'1970-01-01 00:00:01'到'2038-01-19 03:14:07'。常用于存储时间戳、日志记录等。
# JSON类型
- JSON:JSON类型,用于存储JSON格式的数据,如JSON对象和JSON数组。用于存储半结构化的数据,如用户个人资料、设备信息等。
JSON是5.7版本新增的数据类型,可能大家不是那么的熟悉,请记住以下知识点:
- 查询JSON数据中的内容:
SELECT sentence->"$.mascot" FROM facts;
->
查询出属性的值。->>
查询出属性的值,并转换为文本格式- 查询的时候
$
作为根元素,灵活应用,比如$.name
、$[0]
、$[0].name
、$[0][0]
、$[*]
、$.c[*]
、$[1 to 3]
、$**.b
、$[last-3 to last-1]
、$[last]
、$[1].b[1]
、$[*]
、$[*]
、$[*]
等 - JSON中字段的类型有:
BLOB
、BIT
、OPAQUE
、DATETIME
、TIME
、DATE
、BOOLEAN
、ARRAY
、OBJECT
、STRING
、INTEGER, DOUBLE
、NULL
- 关于JSON ARRAY的比较和排序:如果两个 JSON 数组具有相同的长度,并且数组中相应位置的值相等,则它们相等。如果数组不相等,则它们的顺序由存在差异的第一个位置的元素确定。在该位置具有较小值的数组首先排序。如果较短数组的所有值都等于较长数组中的相应值,则较短数组首先排序:
[] < ["a"] < ["ab"] < ["ab", "cd", "ef"] < ["ab", "ef"]
# UUID类型
- UUID:UUID类型,用于存储128位的全局唯一标识符。用于存储需要全局唯一标识符的数据,如主键、唯一ID等。
# 二进制类型
- BIT:BIT类型,用于存储位数据,可以存储的位数为1到64位。用于存储二进制数据,如开关状态、权限控制等。
# 空间数据类型(Spatial Data Types)
- GEOMETRY:几何类型,用于存储点、线、面等几何形状。用于存储地理位置信息、图形数据等。
- POINT:点类型,用于存储二维平面上的一个点。用于存储经纬度、坐标等信息。
- LINESTRING:线类型,用于存储二维平面上的一条线。用于存储道路、路径等信息。
- POLYGON:面类型,用于存储二维平面上的一个面。用于存储区域、范围等信息。
- MULTIPOINT:多点类型,用于存储二维平面上的多个点。用于存储多个坐标点等信息。
- MULTILINESTRING:多线类型,用于存储二维平面上的多条线。用于存储多条道路、路径等信息。
- MULTIPOLYGON:多面类型,用于存储二维平面上的多个面。用于存储多个区域、范围等信息。
- GEOMETRYCOLLECTION:几何集合类型,用于存储多种几何类型的组合。用于存储复杂的地理信息、图形信息等。
# 4. MySQL字符集
字符集定义了可以在数据库中使用的字符和编码规则。
下面是一些常见的 MySQL 字符集:
UTF-8:UTF-8 是一种通用的多字节编码字符集,支持全球范围内的大多数字符。它是最常用的字符集之一,适用于多语言环境和国际化应用。
Latin1:Latin1(也称为ISO 8859-1)是最基本的单字节字符集,支持欧洲语言的字符。它是 MySQL 的默认字符集之一。
GBK:GBK 是用于中文的字符集,支持简体中文和繁体中文字符。它是 GB2312 的扩展,支持更多的中文字符。
UTF-16:UTF-16 是一种双字节编码字符集,可以表示 Unicode 字符的所有范围。它适用于存储较少的字符,对于存储大量文本可能会占用更多的存储空间。
UTF-32:UTF-32 是一种四字节编码字符集,可以表示 Unicode 字符的所有范围。它和 UTF-16 相比,占用更多的存储空间,但是可以更高效地处理字符。
需要特别注意的是MySQL中真正的Unicode字符集是utf8mb4,而不是utf8。
它们的区别是:
- 存储范围:
utf8
:原始的utf8
字符集只能存储最多 3 个字节的 Unicode 字符,覆盖了大部分常用字符,但不支持一些特殊字符(如 Emoji 表情)。utf8mb4
:utf8mb4
字符集支持存储最多 4 个字节的 Unicode 字符,可以覆盖几乎所有的 Unicode 字符,包括 Emoji 表情等特殊字符。
- 存储空间:
utf8
:每个字符占用最多 3 个字节的存储空间。utf8mb4
:每个字符占用最多 4 个字节的存储空间。
- 兼容性:
utf8
:在较早的 MySQL 版本中,utf8
实际上只实现了一个子集,被称为 "MySQL's utf8",并不完全符合标准的 UTF-8 编码规范。这意味着在使用utf8
字符集时,可能会出现一些不兼容或意外的行为。utf8mb4
:utf8mb4
字符集是完全符合标准的 UTF-8 编码规范,可以正常存储和处理任何 Unicode 字符。
在使用 utf8mb4
字符集时,需要确保数据库、表和连接客户端的字符集设置都正确配置为 utf8mb4
,以避免字符集不匹配导致的问题。
在 MySQL 中,可以在不同的级别上设置字符集:
服务器级别:可以在 MySQL 服务器的配置文件中设置默认的字符集。这将影响新创建的数据库和表的默认字符集。
数据库级别:可以为每个数据库单独设置字符集。在创建数据库时,可以指定所需的字符集。
表级别:可以为每个表单独设置字符集。在创建表时,可以指定表的字符集。
列级别:可以为每个列单独设置字符集。在创建表的列时,可以指定列的字符集。
为了正确处理和存储数据,应该根据实际需求选择适当的字符集。在多语言环境下,常用的做法是使用 UTF-8 字符集,以支持各种语言的字符。
# 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层,它包括以下组件:
# a. 连接器
连接器(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整体性能和可靠性的关键一步。
# b. 查询缓存
查询缓存(Query Cache)用于缓存已经执行过的SELECT语句和结果集。如果一个SELECT语句和结果集已经被缓存,那么MySQL会直接返回结果,而不需要执行查询操作,从而提高查询性能。查询缓存可以减少数据库的负载,提高响应速度,并降低了查询的CPU和I/O开销。
查询缓存的工作原理如下:
客户端发送查询请求。客户端通过连接器向MySQL服务器发送SELECT查询请求。
查询缓存查找。MySQL服务器接收到查询请求后,会先查找查询缓存中是否有相应的结果集。如果查询缓存中存在相应的结果集,则MySQL直接返回结果集给客户端,不需要执行查询操作。
执行查询。如果查询缓存中不存在相应的结果集,则MySQL服务器会执行查询操作,并将结果集返回给客户端。同时,MySQL还将查询结果写入查询缓存,以便下次查询时可以直接返回结果。
查询缓存的使用需要注意以下几点:
查询缓存对于只读型的应用性能提升比较明显,对于有写入操作的应用,由于写入操作会使查询缓存失效,所以查询缓存的效果并不显著。
查询缓存需要占用大量的内存,如果内存不足,查询缓存的效果会逐渐降低,甚至会对MySQL的性能造成负面影响。
在MySQL 8.0版本中,查询缓存被废弃,并在以后的版本中删除。这是因为查询缓存可能导致数据库的性能下降,特别是在高并发和动态数据的情况下,而且查询缓存还会占用大量的内存。
查询缓存是MySQL中一个重要的性能优化组件,它可以提高查询性能和降低数据库的负载。但是,在使用查询缓存时需要注意其限制和适用场景,以充分发挥其优势。
# c. 分析器
查询分析器(Query Analyzer)负责对SQL语句进行解析和优化,将SQL语句转换为可执行的计划。MySQL的查询优化器会根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划,以提高查询的性能和效率。
查询分析器的工作原理如下:
解析SQL语句。查询分析器首先对SQL语句进行解析,将其分解成多个语句块,包括SELECT语句、FROM子句、WHERE子句等。
生成执行计划。MySQL的查询优化器根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。执行计划是一组操作序列,用于执行SQL查询并生成结果集。执行计划包括访问方法、表连接顺序、过滤条件、排序方式等。
执行查询。MySQL根据执行计划执行SQL查询,并生成结果集。
查询分析器还具有以下特点:
语法分析。查询分析器可以识别和分析SQL语句的语法结构,从而检测和报告错误的语法。
语义分析。查询分析器可以分析SQL语句的语义,检测和报告不合理的查询和错误的查询操作。
优化查询。查询分析器可以优化SQL查询,提高查询性能和效率。MySQL的查询优化器会尽可能利用索引和其他优化技术,生成一个最优的执行计划。
常量表达式计算。查询分析器可以计算SQL查询中的常量表达式,并将其转换为常量,从而减少计算量,提高查询性能。
查询分析器是MySQL中非常重要的一个组件,它可以优化SQL查询,提高查询性能和效率。了解查询分析器的工作原理和特点,对于优化MySQL的性能和解决查询问题非常重要。
# d. 优化器
查询优化器(Query Optimizer)优化SQL查询,以提高查询性能和效率。MySQL的查询优化器可以根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。最优的执行计划可以使查询更快、更高效地执行,从而提高MySQL的整体性能。
查询优化器的工作原理如下:
解析SQL语句。查询优化器首先对SQL语句进行解析,将其分解成多个语句块,包括SELECT语句、FROM子句、WHERE子句等。
分析查询。查询优化器分析查询,包括表的选择、连接类型、过滤条件、排序方式等,以生成一个可能的执行计划。
生成执行计划。MySQL的查询优化器会根据索引、表统计信息和查询类型等因素,生成一个最优的执行计划。执行计划是一组操作序列,用于执行SQL查询并生成结果集。执行计划包括访问方法、表连接顺序、过滤条件、排序方式等。
执行查询。MySQL根据执行计划执行SQL查询,并生成结果集。
MySQL的查询优化器还具有以下特点:
选择最优索引。查询优化器会根据查询条件和索引的选择性,选择最优的索引,以提高查询性能和效率。
预测行数。查询优化器可以预测查询语句返回的行数,从而帮助MySQL优化查询执行计划,提高查询性能。
确定表连接顺序。查询优化器可以确定表连接顺序,以减少查询的I/O操作,提高查询性能。
优化常量表达式。查询优化器可以优化SQL查询中的常量表达式,从而减少计算量,提高查询性能。
查询优化器是MySQL中非常重要的一个组件,它可以优化SQL查询,提高查询性能和效率。
# e. 执行器
执行器(Executor)它负责执行SQL语句并返回结果集。执行器的主要工作是管理查询缓存、读取数据、排序、分组、聚合、计算表达式等,以生成结果集并将其返回给客户端。
执行器的工作流程如下:
客户端发送SQL查询请求。客户端通过连接器向MySQL服务器发送SQL查询请求。
查询缓存查找。MySQL服务器接收到查询请求后,会先查找查询缓存中是否有相应的结果集。如果查询缓存中存在相应的结果集,则MySQL直接返回结果集给客户端,不需要执行查询操作。
执行查询。如果查询缓存中不存在相应的结果集,则MySQL服务器会根据查询优化器生成的执行计划,执行查询操作,并将结果集返回给执行器。
处理结果集。执行器对结果集进行排序、分组、聚合、计算表达式等操作,并将最终结果集返回给客户端。
执行器还具有以下特点:
管理事务。执行器负责管理MySQL的事务,包括事务的开始、提交和回滚等操作。
锁管理。执行器负责管理MySQL的锁机制,包括行级锁和表级锁等,以保证并发性和一致性。
资源管理。执行器负责管理MySQL的资源,包括缓存、连接池和线程池等,以提高MySQL的性能和效率。
执行器是MySQL中非常重要的一个组件,它直接影响MySQL的查询性能和稳定性。优化执行器的性能和可靠性是提高MySQL整体性能和可靠性的关键一步。
# 6. 安装与配置MySQL
# a. 安装要求
根据您的操作系统和硬件配置选择合适的MySQL版本。确保您的系统满足安装MySQL的最低要求,如内存、磁盘空间和操作系统版本等。
# b. 环境变量配置
安装完成后,需要将MySQL的可执行文件路径添加到系统的环境变量中,以便在命令行中直接使用mysql
命令。
# c. 验证安装
在命令行中输入mysql --version
,如果显示出MySQL的版本信息,则表示安装成功。接下来,可以使用mysql -u root -p
命令登录到MySQL数据库,并进行进一步的配置和使用。
# 三、SQL基础
# 1. SQL语言的概念与分类
SQL(结构化查询语言,Structured Query Language)是用于管理关系型数据库的一种编程语言。它可以让用户创建、查询、更新和删除数据库中的数据。根据功能和用途,SQL可以分为以下三种类型:
# a. DDL(数据定义语言)
DDL(Data Definition Language)主要用于定义和管理数据库对象(如数据库、表、视图、索引等)。常见的DDL语句包括:
- CREATE:用于创建数据库或表
- ALTER:用于修改数据库或表的结构
- DROP:用于删除数据库、表或其他数据库对象
- TRUNCATE:用于删除表中的所有数据,但保留表结构
# b. DML(数据操作语言)
DML(Data Manipulation Language)主要用于对数据库中的数据进行操作。常见的DML语句包括:
- SELECT:用于查询数据
- INSERT:用于插入数据
- UPDATE:用于更新数据
- DELETE:用于删除数据
# c. DCL(数据控制语言)
DCL(Data Control Language)主要用于控制数据库的访问权限和数据的安全性。常见的DCL语句包括:
- GRANT:用于授权用户访问数据库的权限
- REVOKE:用于撤销用户的访问权限
# 2. SQL语句的基本结构与书写规范
SQL语句通常由关键字、表达式和子句组成。关键字是SQL语言的保留字,如SELECT、FROM、WHERE等。为了提高可读性,建议遵循以下书写规范:
- 关键字大写:将关键字大写可以使SQL语句更易读,例如,
SELECT
、FROM
、WHERE
等。 - 换行和缩进:对于复杂的SQL语句,可以使用换行和缩进来组织代码,使其更易读。
- 注释:使用
--
或/*...*/
来添加注释,说明SQL语句的功能和目的。
# 3. 数据库、表的创建与删除
在MySQL中,可以使用DDL语句来创建和删除数据库及表。以下是一些常见的操作示例:
# 创建数据库
CREATE DATABASE database_name;
# 删除数据库
DROP DATABASE database_name;
# 创建表
CREATE TABLE table_name (
column1 data_type constraints,
column2 data_type constraints,
...
);
# 删除表
DROP TABLE table_name;
# 4. 增、删、改、查数据操作
使用DML语句,可以在数据库中进行数据的增加、删除、修改和查询操作。以下是一些常见的操作示例:
# 查询数据
SELECT column1, column2, ...
FROM table_name
WHERE conditions;
# 插入数据
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
# 更新数据
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE conditions;
# 删除数据
DELETE FROM table_name
WHERE conditions;
通过熟练掌握SQL基础知识,包括DDL、DML和DCL语句,您将能够在MySQL数据库中创建、管理和操作数据。
# 四、函数
# 1. 字符串函数
在处理文本数据时,字符串函数具有显著的作用。MySQL 提供了众多的内置字符串函数,下面是一些常用的函数及其简述:
# 数据转换函数
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
.
- 示例:
# 字符串操作函数
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
.
- 示例:
# 其他字符串函数
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提供了许多内置的数值函数:
# 基本数学函数
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
.
- 示例:
# 三角函数
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
.
- 示例:
# 对数和指数函数
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
.
- 示例:
# 角度和弧度转换
DEGREES(number)
: 将弧度转换为角度.- 示例:
SELECT DEGREES(1.57);
返回结果为89.95437383553924
.
- 示例:
RADIANS(number)
: 将角度转换为弧度.- 示例:
SELECT RADIANS(90);
返回结果为1.5707963267948966
.
- 示例:
# 随机数生成
RAND()
: 返回一个0~1之间的随机数.- 示例:
SELECT RAND();
返回一个介于 0 和 1 之间的随机数。
- 示例:
这些数值函数可以用于数值的计算、转换等操作,能够满足MySQL中大部分的数值处理需求。
# 3. 日期和时间函数
处理日期和时间数据时,日期和时间函数非常有用。以下是一些常用的日期和时间函数:
# 日期/时间增减函数
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
.
- 示例:
# 日期/时间获取函数
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
.
- 示例:
# 日期/时间格式转换函数
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
.
- 示例:
# 日期/时间计算函数
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)是一种非常强大的工具。窗口函数允许我们在不改变行数的情况下,对数据集进行复杂的计算。
与聚合函数不同,窗口函数不会减少结果集的行数。相反,它会返回一个与输入数据相同数量的结果集,并为每一行添加一个新的列,该列包含窗口函数计算的结果。
# 基本语法
SELECT column1, column2,
window_function(column3) OVER (window_clause)
FROM table_name;
column1
,column2
: 需要选择的列。window_function(column3)
: 要应用的窗口函数。OVER (window_clause)
: 定义窗口的子句。
# 窗口子句(Window Clause)
窗口子句用于定义窗口的范围和顺序。它由以下几个部分组成:
PARTITION BY
: 分区子句,用于将数据划分为不同的组。每个组内的行将独立计算窗口函数。ORDER BY
: 排序子句,用于确定窗口内行的顺序。ROWS
或RANGE
: 用于定义窗口的边界范围,指定哪些行属于当前窗口。
# 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
将数据按部门分组,并计算每个部门的平均工资。
# 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()
函数计算每个部门内员工的工资排名。
# 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 范围内的所有行,计算工资总和。
# 综合示例
假设有一个员工表 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
,可以灵活定义窗口的范围和顺序,满足复杂的数据分析需求。
# 排名函数
排名函数用于为结果集中的每一行分配一个排名。常见的排名函数包括:
- 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 |
# 聚合函数
聚合函数可以应用于窗口,以便在每个窗口内进行聚合计算。常见的聚合窗口函数包括:
- 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 |
# 移动平均和其他统计函数
你说得对,AVG()
本身是一个聚合函数,而不是窗口函数。但在 SQL 中,AVG()
可以通过与 OVER()
子句结合使用来实现窗口计算的功能。为了更准确地表述,我们可以将内容优化如下:
# 移动平均和其他统计函数
移动平均和其他统计函数常用于分析时间序列数据或滑动窗口计算。常见的窗口函数包括:
- 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()
子句,能够实现强大的窗口计算功能。
# 案例一:销售额趋势分析
假设我们有一个电商网站的销售记录表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天的平均销售额,帮助我们了解销售趋势。
# 案例二:库存管理
在库存管理系统中,我们需要跟踪每个仓库的库存变化情况。假设有一个库存表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. 多表连接查询
在实际应用中,数据通常分散在多个表中。为了从这些表中获取所需的信息,我们需要执行多表连接查询。根据连接方式的不同,连接查询可以分为内连接、左连接、右连接和全连接。在本节中,我们将详细介绍这些连接类型。
# a. 内连接(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
。查询结果将包含满足连接条件的记录。
# b. 左连接(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)记录。
# c. 右连接(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)记录。
# d. 全连接(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
子句可以实现按特定列对查询结果进行分组。
# a. 聚合函数
以下是一些常用聚合函数的示例:
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;
# b. 分组查询
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
子句中。根据使用场景的不同,子查询可以分为标量子查询、行子查询和表子查询。
# a. 标量子查询
标量子查询返回单个值,可以在SELECT
、WHERE
和HAVING
子句中使用。例如,查询工资高于平均工资的员工:
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
# b. 行子查询
行子查询返回一行数据,可以在WHERE
子句中使用。例如,查询工资和奖金都高于某个员工的其他员工:
SELECT name, salary, bonus
FROM employees
WHERE (salary, bonus) > (SELECT salary, bonus FROM employees WHERE name = 'John Doe');
# c. 表子查询
表子查询返回一个表,可以在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. 索引的创建、使用与优化
索引是一种数据库对象,用于提高查询速度。通过在表的一个或多个列上创建索引,可以加快查询、更新和删除操作。但是,索引并非万能的,过多的索引可能会影响数据的插入和更新性能。因此,在创建和使用索引时,需要权衡查询性能与数据修改性能之间的平衡。本节将介绍索引的创建、使用与优化方法。
# a. 创建索引
在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
列创建了一个索引。
# b. 使用索引
当执行查询时,MySQL会自动选择合适的索引来优化查询性能。但是,索引的使用并不总是有效的。以下几种情况可能会导致索引失效:
- 使用
LIKE
操作符进行模糊查询时,以通配符开头的模式会导致索引失效。 - 在索引列上进行计算或使用函数时,索引将不会被使用。
- 当查询条件中的
OR
操作符涉及多个列时,索引可能无法被使用。
要查看MySQL是否使用了索引,可以使用EXPLAIN
语句分析查询计划。例如:
EXPLAIN SELECT * FROM employees WHERE department_id = 10;
这个查询将返回一些关于查询执行计划的信息,包括是否使用了索引、哪个索引被使用以及索引的使用效果等。
# c. 索引类型
MySQL支持多种类型的索引,按照数据结构分类:
- B+Tree索引:B+Tree(Balance+ Tree)索引是MySQL中最常用的索引类型。它适用于全值匹配和范围查询,同时也支持排序和分组操作。
- Hash索引:Hash索引基于哈希表实现,适用于等值查询。但是,它不适用于范围查询、排序和分组操作。需要注意的是,InnoDB存储引擎不支持显式的Hash索引,但会自动创建隐式的Hash索引来优化等值查询。
- Full-text索引:全文索引用于文本内容的搜索,能够提供基于关键词的搜索功能。在MySQL中,全文索引主要应用于MyISAM和InnoDB存储引擎。全文索引默认分词器仅对英文做了优化,且查询功能有限,一般不在生产中使用,推荐使用搜索引擎,如Elasticsearch。
- 空间索引:空间索引用于地理空间数据类型的查询,能够提高地理空间数据的查询性能。在MySQL中,空间索引基于R-Tree实现,并主要应用于MyISAM和InnoDB存储引擎。
按照物理存储分类(参考下一节):
- 聚簇索引
- 辅助索引
按照字段特性分类:
# 1. 主键索引(Primary Key Index)
CREATE TABLE table_name (
column_name INT PRIMARY KEY,
...
);
主键索引用于唯一标识表中的每一行。它是一个独特的索引类型,确保每个索引键的唯一性,并用于加速与主键相关的查询。
# 2. 唯一索引(Unique Index)
CREATE TABLE table_name (
column_name INT UNIQUE,
...
);
唯一索引确保索引列中的每个值都是唯一的。与主键索引不同,唯一索引允许空值,但对于非空值,每个值必须是唯一的。
可以对多个字段添加唯一索引:
ALTER TABLE table_name ADD UNIQUE INDEX index_name (column1, column2, ...);
# 3. 普通索引(Normal Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name),
...
);
普通索引是最常见的索引类型,用于加快特定列的查询速度。它可以基于单个列或多个列创建。
# 4. 前缀索引(Prefix Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name(prefix_length)),
...
);
前缀索引允许您在索引中仅包含列值的前缀部分。这对于较长的列或文本列可以节省存储空间并提高查询性能。
# 5. 隐藏索引(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有帮助。
# 6. 降序索引(Descending Index)
CREATE TABLE table_name (
...
INDEX index_name (column_name DESC),
...
);
降序索引用于对列进行降序排序。默认情况下,索引是升序的,但通过在列上指定 DESC
可以创建降序索引。
需要注意,如果使用了where查询,那么降序索引将不会生效。
# 7. 函数索引(Function Index)
CREATE TABLE table_name (
...
INDEX index_name (UPPER(column_name)),
...
);
函数索引允许您在索引中使用函数表达式,而不仅仅是列名。这可以帮助加速特定函数的查询。
# 8. 多值索引(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`->'$')
# d. InnoDB索引模型
InnoDB存储引擎使用B-Tree作为主要的索引结构。在InnoDB中,表数据和索引都存储在一个称为聚簇索引(Clustered Index)的数据结构中。
每个InnoDB表都有一个主键索引,称为聚簇索引。同时,InnoDB还支持辅助索引(Secondary Index),也称为非聚簇索引。
聚簇索引:聚簇索引将表数据和索引存储在一起,按照主键顺序存储。这意味着,主键查询和范围查询的性能非常高。然而,这也导致了插入、更新和删除操作的性能开销较大。
辅助索引:辅助索引存储了对应列的值以及指向聚簇索引的指针。这意味着,在使用辅助索引进行查询时,可能需要进行额外的查找操作(回表)以获取完整的行数据。
# e. 索引维护
为了保持索引的性能,需要定期进行索引维护,包括分析索引、优化索引和重建索引。
分析索引:使用
ANALYZE TABLE
命令可以收集表和索引的统计信息,以帮助优化器选择更优的查询计划。优化索引:使用
OPTIMIZE TABLE
命令可以整理表和索引的数据,以提高查询和插入性能。重建索引:当索引出现碎片化或性能下降时,可以使用
ALTER TABLE ... DROP INDEX
和ALTER TABLE ... ADD INDEX
命令重建索引。
# B+树的维护
在B+树中,每个节点中的关键字都是有序的。B+树通过维护这种有序性,可以快速地查找和遍历节点中的关键字,提高查询效率。为了保持节点中的关键字有序,B+树在插入、删除节点时需要进行必要的维护。
- 插入节点
在B+树中插入新节点时,首先要找到该节点的插入位置。插入位置可以通过查找B+树的叶子节点得到。插入新节点时,需要保持节点中的关键字有序性,因此需要将新节点插入到正确的位置。插入新节点后,如果当前节点的关键字数量超过了节点的容量,就需要进行节点分裂操作。节点分裂操作可以将当前节点分裂成两个节点,从而保持B+树的平衡。
- 删除节点
在B+树中删除节点时,首先要找到要删除的节点。删除节点时,需要保持节点中的关键字有序性,因此需要将删除后的节点重新排序。如果删除后节点中的关键字数量低于了节点的最小容量,就需要进行节点合并操作。节点合并操作可以将当前节点和相邻节点合并成一个节点,从而保持B+树的平衡。
维护B+树的有序性可以保证查询和遍历节点时的效率,并且可以保持B+树的平衡,避免出现过度分裂或者过度合并的情况。因此,在设计和实现B+树索引时,需要充分考虑维护B+树的有序性,以提高查询效率和保证数据结构的稳定性。
使用非有序插入的主键,写数据的成本会比较高。
# f. 回表
当使用辅助索引查询数据时,可能需要进行回表操作。回表是指通过辅助索引找到对应的聚簇索引记录以获取完整的行数据。由于回表操作需要额外的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,不满足条件,循环结束。
可以看到,回了两次表。
# g. 覆盖(联合)索引
覆盖索引是一种包含查询所需所有列的辅助索引。由于覆盖索引包含了所有需要的数据,因此在查询时无需进行回表操作。使用覆盖索引可以大幅提高查询性能,尤其是在大表上。
要创建覆盖索引,可以在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”。
# h. 最佳实践
在创建、使用和优化索引时,需要遵循以下最佳实践:
- 为经常出现在查询条件和排序操作中的列创建索引。
- 为主键创建索引。
- 使用覆盖索引避免额外的回表操作。
- 定期分析和优化索引,以保持索引性能。
- 避免在索引列上进行计算或使用函数,以充分利用索引优势。
- 当查询条件中的
OR
操作符涉及多个列时,考虑使用联合索引。
总之,索引是提高查询性能的重要手段。在创建、使用和优化索引时,需要充分考虑实际应用场景,并权衡查询性能与数据修改性能之间的关系。
# 5. 存储过程与触发器
存储过程和触发器是数据库中的两种重要对象,它们可以帮助我们实现更复杂的业务逻辑和保持数据的一致性。
# a. 存储过程
存储过程是一种在数据库中存储的预定义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;
# b. 触发器
触发器是一种特殊的存储过程,它在某个事件(如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还可以用于实现递归查询,从而处理具有层次结构的数据。
# a. 创建和使用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进行连接,以显示员工的详细信息和对应部门的平均工资。
# b. 递归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)是一种存储临时数据的表格,它在创建后只存在于当前的会话中,并在会话结束后自动删除。
临时表可以存储临时数据,可以在多个查询之间共享数据,可以在多个会话之间共享数据,可以用于存储中间结果,提高查询的性能。
# 1. 临时表的语法
在MySQL中,创建临时表的语法与创建普通表的语法相同,只是在表名前加上关键字TEMPORARY,表示创建的是临时表。例如,可以使用以下语句创建一个临时表:
CREATE TEMPORARY TABLE temp_table (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
);
# 2. 临时表的属性和限制
与普通表一样,临时表也具有各种属性和限制,例如表的存储引擎、表的字符集等。不过,由于临时表的特殊性质,其具有以下限制:
- 临时表只存在于当前会话中,不能在其他会话中访问。
- 临时表的数据只存在于当前会话中,会话结束后会自动删除。
- 临时表不能有外键约束,因为外键约束需要引用其他表。
# 3. 临时表的使用
创建临时表后,可以使用INSERT、UPDATE、DELETE等语句向表中插入数据或修改数据。临时表的使用方式与普通表相同,可以通过SELECT等语句查询表中的数据。例如,可以使用以下语句向临时表中插入数据:
INSERT INTO temp_table (name) VALUES ('John'), ('Mike'), ('Peter');
可以使用以下语句查询临时表中的数据:
SELECT * FROM temp_table;
# 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存储引擎,以提高查询性能和响应速度。
# 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
值的处理方式与其他值不同,需要特别注意。
# a. 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
# b. count
函数对于NULL
的处理
使用count(*)
或者count(null column)
结果不同,count(null column)<=count(*)
。
假设name有一列为NULL
的情况下:
select count(*),count(name) from test;
+----------+------------+
| 2 | 1 |
# c. NULL
对于索引的影响
MySQL 中支持在含有NULL
值的列上使用索引,但是Oracle不支持。这就是你可能会听到如果列上含有NULL那么将会使索引失效
的原因。
严格来说,这句话对与 MySQL 来说是不准确的。
# d. 使用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的运行状况、识别性能瓶颈、优化查询语句、提高系统的可用性和性能。
# 1. Performance Schema概述
Performance Schema是MySQL 5.5及以上版本中提供的一种性能监控工具,可以收集MySQL服务器的性能信息,并将其保存到内存中或者持久化到磁盘中,供后续查询和分析。
Performance Schema可以收集的性能信息包括:SQL语句的执行时间、IO负载、锁等信息,以及各种MySQL内部子系统的性能指标。
与其他监控工具相比,Performance Schema具有以下优点:
- 精度高:可以提供非常详细的性能信息,包括每个SQL语句的执行时间、IO负载等细节信息。
- 低开销:可以通过配置控制Performance Schema的采样频率和采样粒度,减少对系统性能的影响。
- 灵活性高:可以根据需要对不同子系统的性能信息进行采样和过滤,实现定制化的监控方案。
但是,由于Performance Schema收集的性能信息较为详细,因此也需要较高的系统配置和资源消耗。需要根据实际情况进行配置和使用,避免对系统性能造成不必要的影响。
# 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的性能状况,识别性能瓶颈,并进行优化。
# 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. 读写分离与主从复制
# 1. 读写分离的原理与优点
# a. 读写分离的原理
读写分离(Read-Write Separation)是指将数据库的读操作和写操作分离到不同的服务器上,以提高系统性能。在这种架构中,通常有一个主服务器(Master)负责处理写操作,以及多个从服务器(Slave)负责处理读操作。主服务器会将数据变更同步到从服务器,从而保证数据一致性。
# b. 读写分离的优点
- 负载均衡:通过将读操作分布到多个从服务器上,可以有效减轻主服务器的压力,实现负载均衡。
- 提高系统可用性:当主服务器出现故障时,可以快速切换到从服务器上继续提供服务,提高系统可用性。
- 提高查询性能:从服务器可以根据实际需求创建不同的索引,从而提高查询性能。
- 实现数据备份与恢复:从服务器可以作为主服务器的数据备份,便于数据恢复。
# 2. 主从复制的原理与配置
# a. 主从复制的原理
主从复制(Master-Slave Replication)是指主服务器将数据变更同步到从服务器的过程。在MySQL中,主从复制通常采用基于二进制日志(Binary Log,简称binlog)的异步复制机制。主服务器在执行写操作时,会将变更记录到binlog中,从服务器通过获取并执行这些变更记录,实现与主服务器的数据同步。
# b. 配置主从复制
配置主从复制主要包括以下几个步骤:
- 配置主服务器:修改主服务器的配置文件,启用binlog,设置唯一的服务器ID(server-id),并重启服务。
- 配置从服务器:修改从服务器的配置文件,设置唯一的服务器ID(server-id),并重启服务。
- 创建复制用户:在主服务器上创建一个具有复制权限的用户,并记录用户信息。
- 初始化从服务器数据:将主服务器的数据导出,并导入到从服务器上,保证二者数据一致。
- 启动主从复制:在从服务器上执行CHANGE MASTER命令,指定主服务器的地址、端口、复制用户信息以及binlog位置等信息,然后启动从服务器的复制线程。
# 3. 主从复制的常见问题与优化
# a. 主从延迟
由于主从复制采用异步复制机制,从服务器可能会出现与主服务器数据不一致的情况。为减小主从延迟,可以采取以下措施:
- 优化网络连接:确保主从服务器之间的网络连接稳定且高速,降低网络延迟。
- 减小事务大小:尽量避免执行大事务,以减小单次复制的数据量,加快复制速度。
- 优化从服务器硬件:提高从服务器的硬件配置,如CPU、内存和磁盘性能,以加快数据同步速度。
- 并行复制:启用MySQL的并行复制功能,允许从服务器同时执行多个复制线程,提高复制效率。
# b. 主从不一致
主从不一致是指从服务器的数据与主服务器的数据不完全相同。这可能是由于网络故障、从服务器故障或其他原因导致的。解决主从不一致的方法包括:
- 监控主从状态:定期检查主从服务器的复制状态,确保复制过程正常进行。
- 数据校验:使用工具(如pt-table-checksum)定期对比主从服务器的数据,发现并修复不一致。
- 故障恢复:当从服务器出现故障时,可以使用主服务器的备份数据进行恢复,然后重新启动复制。
# 4. 读写分离与主从复制的结合
将读写分离与主从复制结合使用,可以进一步提高数据库系统的性能和可用性。实现这一目标的关键在于合理地分配读写请求:
- 读请求分配:将读请求发送到从服务器,以减轻主服务器的负担。可根据从服务器的负载情况动态调整分配策略,实现负载均衡。
- 写请求分配:将写请求发送到主服务器,确保数据的一致性。当主服务器出现故障时,可以将写请求切换到备用主服务器上,提高系统可用性。
为实现这些功能,可以使用负载均衡器、代理服务器等工具,如HAProxy、MySQL Router等。这些工具可以帮助实现自动切换、负载均衡和故障恢复等功能,提高数据库系统的性能和稳定性。
# 6.优化提示
优化提示(Optimizer Hints)是MySQL中的一种特性,它允许在编写SQL查询时向查询优化器提供关于如何执行查询的额外信息。优化提示可以帮助开发者在特定情况下手动干预查询优化器的决策,以提高查询性能。需要注意的是,优化提示并非万能,正确使用它需要对查询优化器的工作原理和表结构有一定的了解。此外,滥用优化提示可能会导致查询性能降低。
在MySQL 8.0及更高版本中,优化提示使用/*+ ... */
注释形式包含在查询中。以下是一些常用的优化提示示例:
# a. 使用指定的索引
当查询优化器没有选择合适的索引时,可以使用USE_INDEX
或FORCE_INDEX
提示指定要使用的索引。例如:
SELECT /*+ USE_INDEX(idx_employees_name) */ * FROM employees WHERE name = 'John Doe';
# b. 忽略指定的索引
当查询优化器错误地选择了某个索引时,可以使用IGNORE_INDEX
提示忽略该索引。例如:
SELECT /*+ IGNORE_INDEX(idx_employees_name) */ * FROM employees WHERE name = 'John Doe';
# c. 控制连接顺序
在多表连接查询中,可以使用STRAIGHT_JOIN
提示强制按照FROM子句中的表顺序进行连接。例如:
SELECT /*+ STRAIGHT_JOIN */ e.name, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id;
# d. 控制连接类型
在某些情况下,可以使用优化提示强制使用特定类型的连接(如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;
# e. 控制子查询执行策略
可以使用优化提示强制将子查询转换为连接(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中事务的实现与管理:
开启事务:可以通过命令
START TRANSACTION
或BEGIN
来显式开启一个事务。在自动提交模式下,每个单独的SQL语句都被视为一个事务。提交事务:使用
COMMIT
命令提交事务,将对数据库的修改永久保存。回滚事务:使用
ROLLBACK
命令回滚事务,撤销对数据库的修改。如果事务执行过程中遇到错误,数据库会自动回滚到事务开始之前的状态。保存点(Savepoint):可以在事务中设置保存点,用于在事务执行过程中回滚到某个特定点。通过
SAVEPOINT savepoint_name
创建保存点,通过ROLLBACK TO savepoint_name
回滚到指定保存点。锁机制:InnoDB引擎提供了行级锁(Row-level Locking)和表级锁(Table-level Locking)机制。行级锁可以在记录级别上对数据进行加锁,提高并发性能,而表级锁则在整张表上加锁,适用于读取大量数据的情况。
MVCC(多版本并发控制):InnoDB引擎通过MVCC机制实现高并发性能。在同一时刻,多个事务可以并发访问同一数据,每个事务都会看到数据的一个“快照”,从而实现事务之间的隔离。MVCC在可重复读和读已提交隔离级别下工作得最好。
设置事务隔离级别:可以通过命令
SET TRANSACTION ISOLATION LEVEL level_name
设置事务隔离级别,其中level_name
是隔离级别的名称(如READ UNCOMMITTED、READ COMMITTED等)。默认情况下,MySQL的事务隔离级别是可重复读。
在实际应用中,应根据业务需求和性能要求选择合适的事务隔离级别。高并发场景下,可以适当降低隔离级别以提高性能,但需要权衡可能出现的数据一致性问题。反之,在需要严格保证数据一致性的场景下,应选择较高的隔离级别,如串行化。
# 十、锁机制
# 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. 锁的优化与应用场景
根据实际应用场景选择合适的锁策略,可以提高数据库性能。以下是一些锁优化和应用场景的建议:
- 避免长时间持有锁:长时间持有锁可能导致其他事务等待,降低系统并发性能。尽量减小事务处理时间,以便更快地释放锁资源。
- 尽量使用行级锁:在InnoDB存储引擎中,行级锁能够有效降低锁冲突的可能性,提高并发性能。在编写SQL语句时,尽量使用明确的条件,使得数据库可以精确地锁定需要操作的数据行。
- 合理使用索引:在InnoDB存储引擎中,使用索引可以帮助数据库更精确地锁定数据行,避免全表扫描和全表锁。合理创建和使用索引,可以提高查询性能和并发性能。
- 避免锁升级:锁升级是指由于锁冲突而导致锁粒度的提升。例如,当一个事务请求某个数据行的排他锁,而另一个事务已经持有该表的共享锁时,系统可能会将共享锁升级为排他锁。避免锁升级可以减少锁冲突,提高并发性能。在实际应用中,可以通过调整事务隔离级别或合理安排事务的执行顺序来避免锁升级。
- 使用锁超时:在某些情况下,事务可能会长时间等待锁资源,导致系统性能下降。设置锁超时可以避免事务长时间等待,提高系统响应速度。
- 使用乐观锁:乐观锁是一种基于数据版本控制的并发控制策略。相比悲观锁(Pessimistic Locking),乐观锁在数据处理过程中不加锁,只在提交时检查数据版本。如果数据版本发生变化,说明存在并发冲突,事务需要回滚并重新执行。乐观锁适用于读多写少的场景,可以降低锁冲突的概率,提高系统并发性能。
通过以上优化措施和应用场景的选择,可以有效提高MySQL数据库的性能和并发能力,满足不同业务场景的需求。
# 十一、备份与恢复
# 1. 备份策略与工具
备份是确保数据安全和可用性的重要手段。MySQL数据库支持两种主要的备份策略:物理备份和逻辑备份。
# a. 物理备份
物理备份是指将数据库中的数据文件、日志文件和其他相关文件复制到备份介质的过程。物理备份通常更快,恢复时也能节省时间。以下是一些常用的物理备份工具:
mysqldump
:mysqldump
工具可以将数据库数据导出为SQL语句,但也可以导出为二进制文件(使用--hex-blob
选项),从而实现物理备份。mysqlhotcopy
:mysqlhotcopy
工具可以在线进行物理备份,但仅适用于MyISAM和Archive存储引擎。- Percona XtraBackup: Percona XtraBackup是一个开源的物理备份工具,支持在线备份InnoDB和XtraDB存储引擎的数据。
# b. 逻辑备份
逻辑备份是指将数据库中的数据导出为逻辑格式(如SQL语句),这种备份方法具有更好的可移植性,但备份和恢复速度通常较慢。以下是一些常用的逻辑备份工具:
mysqldump
:mysqldump
工具是MySQL官方提供的逻辑备份工具,可以将数据库数据导出为SQL语句。mysqlpump
:mysqlpump
工具与mysqldump
类似,但支持并行备份,性能更高。MyDumper
: MyDumper (opens new window)是一个开源的逻辑备份工具,具有并行备份、压缩备份等功能,性能优于mysqldump
。
# 2. 恢复策略与方法
根据备份数据和恢复需求的不同,可以选择不同的恢复策略和方法。
# a. 全量恢复
全量恢复是指使用全量备份数据将数据库恢复到备份时的状态。全量恢复适用于数据丢失或损坏的情况,恢复过程相对简单。
常用的工具:
mysql
: 使用mysql客户端工具,可以执行由mysqldump生成的SQL脚本文件,实现全量恢复。mysqlhotcopy
: 对于MyISAM和Archive存储引擎,可以使用mysqlhotcopy工具进行恢复。Percona XtraBackup
: 使用Percona XtraBackup工具生成的备份文件,可以通过innobackupex或xtrabackup命令进行恢复。
# b. 增量恢复
增量恢复是指在全量恢复的基础上,应用增量备份数据将数据库恢复到某个时间点的状态。增量恢复可以减少数据丢失的风险,但恢复过程较为复杂。
常用的工具:
Percona XtraBackup
: Percona XtraBackup不仅支持全量恢复,还支持增量恢复。通过--incremental选项可以实现增量备份,然后再使用innobackupex或xtrabackup命令进行恢复。mysqlbinlog
: mysqlbinlog工具可以解析二进制日志文件(Binary Logs),将其转换为SQL语句,然后通过mysql客户端工具执行,实现增量恢复。
# c. 点时间恢复
点时间恢复(PITR)是指将数据库恢复到某个特定时间点的状态。通过应用二进制日志(Binary Logs)或其他增量备份数据,可以实现点时间恢复。点时间恢复适用于误操作或部分数据损坏的情况,可以最大限度地减少数据丢失。
常用的工具:
mysqlbinlog
: 点时间恢复(PITR)可以通过mysqlbinlog工具实现。使用--start-datetime和--stop-datetime选项,可以指定需要恢复的时间范围。解析出相应时间范围内的SQL语句后,通过mysql客户端工具执行,实现点时间恢复。
# 3. 数据库备份与恢复的最佳实践
为了确保数据安全和可用性,遵循以下备份与恢复的最佳实践是非常重要的:
- 定期备份:根据业务需求和数据变化情况,制定合理的备份计划,定期进行全量备份和增量备份。
- 备份验证:定期验证备份数据的完整性和可用性,确保在需要时能够顺利恢复。
- 离线存储备份:将备份数据存储在离线介质(如磁带、光盘等),以防止在线存储设备的故障导致数据丢失。
- 远程备份:将备份数据存储在远程位置,以防止本地灾难导致数据丢失。
- 监控备份任务:对备份任务进行监控,确保备份任务按计划执行,并及时处理备份失败等异常情况。
- 恢复演练:定期进行恢复演练,验证恢复流程的可行性,提高恢复能力。
- 文档化备份与恢复流程:编写详细的备份与恢复流程文档,确保在紧急情况下能够快速找到恢复所需的信息。
遵循以上最佳实践,可以有效保障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数据库解决实际问题。
祝你变得更强!