MySQL-SQL基础
MySQL-SQL基础
xiaoyan在数据库管理中,SQL(Structured Query Language)是操作关系型数据库的核心工具。本文将深入探讨MySQL中的SQL基础知识,从数据库类型选择、设计范式、连接查询、数据插入、字符串类型、外键约束、关键字使用、内置函数、查询执行顺序、请求执行过程到存储引擎等多个方面进行详细解析。
SQL与NoSQL数据库
概述
SQL数据库
SQL(Structured Query Language)代表关系型数据库管理系统(RDBMS),常见的代表包括SQL Server、Oracle、MySQL和PostgreSQL。关系型数据库以结构化方式存储数据,数据逻辑通过二维表(即行和列)来表示。每一列代表数据的属性,每一行则代表一个数据实体。
NoSQL数据库
NoSQL(Not Only SQL)代表非关系型数据库,主要代表有MongoDB和Redis。NoSQL数据库不使用传统的二维表结构,而是采用如JSON文档、键值对、列族、图等多种数据模型来存储数据。
选择数据库的考量因素
ACID与BASE
关系型数据库通常支持ACID特性(原子性、一致性、隔离性、持久性),确保数据的强一致性和事务的完整性。而NoSQL数据库通常采用BASE模型(基本可用性、软状态、最终一致性),提供更高的灵活性和可扩展性,但可能在一致性方面有所妥协。
选择数据库时需根据具体应用场景来决定。例如,银行系统需要严格遵守ACID特性以防止资金重复使用,而社交软件则可以容忍一定程度的延迟和数据不一致。
扩展性
NoSQL数据库由于其非关系型数据结构,数据之间通常不存在强关联,因此更容易实现水平扩展。例如,Redis提供了主从复制、哨兵模式和切片集群等扩展机制。
相比之下,SQL数据库中的数据可能存在复杂的关联关系,扩展时需要解决分布式事务等复杂问题。
数据库设计三大范式
第一范式(1NF)
第一范式要求数据库表中的每一列都必须是不可再分的原子项。这意味着每一列中的数据项必须是单一的、不可分割的值。
举例说明
假设有一张表存储用户信息,其中包含“联系方式”列,该列包含电话和电子邮件。为了满足1NF,应将“联系方式”拆分为“电话”和“电子邮件”两列。
不满足1NF的表结构
用户ID | 用户名 | 联系方式 |
---|---|---|
1 | 张三 | 123-4567,zhangsan@example.com |
2 | 李四 | 890-1234,lisi@example.com |
满足1NF的表结构
用户ID | 用户名 | 电话 | 电子邮件 |
---|---|---|---|
1 | 张三 | 123-4567 | zhangsan@example.com |
2 | 李四 | 890-1234 | lisi@example.com |
第二范式(2NF)
第二范式在满足1NF的基础上,要求非主属性必须完全依赖于候选码(即主键)。换句话说,非主属性不能部分依赖于主键的一部分。
举例说明
假设有一张订单表,主键为“订单ID”和“产品ID”。如果表中有一列“产品名称”,它只依赖于“产品ID”,而不依赖于“订单ID”,则该表不满足2NF。应将“产品名称”移到另一张产品表中。
不满足2NF的表结构
订单ID | 产品ID | 产品名称 | 数量 |
---|---|---|---|
1 | 101 | 苹果 | 5 |
2 | 102 | 香蕉 | 3 |
3 | 101 | 苹果 | 2 |
满足2NF的表结构
订单表
订单ID | 产品ID | 数量 |
---|---|---|
1 | 101 | 5 |
2 | 102 | 3 |
3 | 101 | 2 |
产品表
产品ID | 产品名称 |
---|---|
101 | 苹果 |
102 | 香蕉 |
第三范式(3NF)
第三范式在满足2NF的基础上,要求非主属性不依赖于其他的非主属性(即消除传递依赖)。
举例说明
假设有一张员工表,包含“员工ID”、“员工姓名”、“部门ID”和“部门名称”。如果“部门名称”依赖于“部门ID”,而“部门ID”依赖于“员工ID”,则存在传递依赖,不满足3NF。应将“部门名称”移到另一张部门表中。
不满足3NF的表结构
员工ID | 员工姓名 | 部门ID | 部门名称 |
---|---|---|---|
1 | 张三 | D01 | 研发部 |
2 | 李四 | D02 | 销售部 |
3 | 王五 | D01 | 研发部 |
满足3NF的表结构
员工表
员工ID | 员工姓名 | 部门ID |
---|---|---|
1 | 张三 | D01 |
2 | 李四 | D02 |
3 | 王五 | D01 |
部门表
部门ID | 部门名称 |
---|---|
D01 | 研发部 |
D02 | 销售部 |
数据库连接查询
在数据库查询中,连接(Join)操作用于将两个或多个表中的数据组合在一起。常见的连接类型包括内连接、左外连接、右外连接和全外连接。以下是每种连接类型的详细说明及其SQL语法示例。
1. 内连接(Inner Join)
内连接返回两个表中具有匹配关系的行。只有当两个表中的记录在连接条件上匹配时,才会返回结果。
示例解释
假设有两个表:Orders
(订单表)和Customers
(客户表),我们希望查询所有有订单的客户信息。
1 | SELECT Orders.OrderID, Customers.CustomerName |
2. 左外连接(Left Outer Join)
左外连接返回左表中的所有行,即使右表中没有匹配的行。对于右表中没有匹配的行,结果集中对应的数据为NULL
。
示例解释
假设我们希望查询所有客户及其订单信息,包括那些没有订单的客户。
1 | SELECT Customers.CustomerName, Orders.OrderID |
3. 右外连接(Right Outer Join)
右外连接与左外连接相似,返回右表中的所有行,即使左表中没有匹配的行。对于左表中没有匹配的行,结果集中对应的数据为NULL
。
示例解释
假设我们希望查询所有订单及其对应的客户信息,包括那些没有客户的订单。
1 | SELECT Orders.OrderID, Customers.CustomerName |
4. 全外连接(Full Outer Join)
全外连接返回两个表中的所有行,包括那些在另一个表中没有匹配的行。对于没有匹配的行,结果集中对应的数据为NULL
。
示例解释
假设我们希望查询所有客户和订单的信息,包括那些没有订单的客户和没有客户的订单。
1 | SELECT Customers.CustomerName, Orders.OrderID |
MySQL如何避免插入重复数据
在MySQL中,有多种方法可以避免插入重复数据。以下是三种常见的方法及其详细说明。
方式一:使用UNIQUE约束
UNIQUE约束用于确保表中某一列或一组列的值是唯一的。如果在插入数据时违反了UNIQUE约束,MySQL将拒绝插入该数据。
创建表时添加UNIQUE约束
1 | CREATE TABLE Users ( |
如果尝试插入重复的email
,MySQL将返回错误。
方式二:使用 INSERT ... ON DUPLICATE KEY UPDATE
INSERT ... ON DUPLICATE KEY UPDATE
语句在插入数据时,如果遇到重复键值(如UNIQUE约束或PRIMARY KEY),可以选择更新原有记录为新插入的记录。
插入或更新数据
1 | INSERT INTO Products (product_id, product_name, price) |
如果product_id
为1的记录已经存在,则更新其product_name
和price
字段。
方式三:使用 INSERT IGNORE
INSERT IGNORE
语句在插入数据时,如果遇到重复键值(如UNIQUE约束或PRIMARY KEY),将忽略该插入操作,不会抛出错误。
1 | INSERT IGNORE INTO Customers (customer_id, customer_name) |
如果customer_id
为1的记录已经存在,则忽略该插入操作,不会抛出错误。
MySQL中的字符串类型
在MySQL中,字符串类型用于存储文本数据。常见的字符串类型包括CHAR
、VARCHAR
和TEXT
。每种类型都有其特定的用途和存储特性。
CHAR
CHAR
是一种固定长度的字符串类型。在定义时需要指定长度,存储时会在末尾填充空格以达到指定的长度。
特点
- 固定长度:无论实际存储的字符串长度如何,都会占用指定长度的存储空间。
- 存储效率:对于长度固定的字符串(如国家代码、性别等),
CHAR
类型可以提高存储效率。
VARCHAR
VARCHAR
是一种可变长度的字符串类型。在定义时需要指定最大长度,存储时根据实际长度占用存储空间。
特点
- 可变长度:实际存储的字符串长度决定了占用的存储空间,节省存储空间。
- 灵活性:适用于长度不固定的字符串(如用户名、地址等)。
TEXT
TEXT
用于存储超长文本数据。TEXT
类型有多种子类型,包括TEXT
、MEDIUMTEXT
和LONGTEXT
,分别用于存储不同大小的文本。
特点
大容量存储:适用于存储大段文本(如文章内容、日志等)。
存储限制:
TEXT
:最大存储65,535字节(约64KB)。MEDIUMTEXT
:最大存储16,777,215字节(约16MB)。LONGTEXT
:最大存储4,294,967,295字节(约4GB)。
**
CHAR
**:适用于固定长度的字符串,存储效率高。**
VARCHAR
**:适用于长度不固定的字符串,节省存储空间。**
TEXT
**:适用于存储超长文本,有多种子类型满足不同存储需求。
根据具体需求选择合适的字符串类型,可以优化数据库的存储和查询性能。
外键约束
外键约束(Foreign Key Constraint)是关系型数据库中用于维护表与表之间关系的一种机制。它确保数据的完整性和一致性,防止在关联表中插入无效数据。
作用
- 维护数据完整性:外键约束确保在子表中插入或更新的记录在父表中存在对应的记录。如果父表中不存在对应的记录,数据库将拒绝插入或更新操作。
- 防止无效数据:外键约束防止在子表中插入或更新无效数据,从而保持数据的正确性和一致性。
- 级联操作:外键约束可以配置级联操作,如级联删除(CASCADE DELETE)或级联更新(CASCADE UPDATE),以确保在父表中的记录被删除或更新时,子表中的相关记录也相应地被删除或更新。
假设有两个表:Orders
(订单表)和Customers
(客户表),我们希望在Orders
表中定义一个外键约束,确保每个订单都关联到一个有效的客户。
1 | CREATE TABLE Customers ( |
MySQL关键字:IN
和 EXISTS
在MySQL中,IN
和EXISTS
是两个常用的关键字,用于在查询中进行条件判断。它们各自有不同的用途和特点。
IN
关键字
IN
关键字用于检查左边的表达式是否存在于右边的列表或子查询中。如果存在,则返回true
,否则返回false
。
假设我们有一个Orders
表,我们希望查询所有订单ID为1、2或3的订单。
1 | SELECT * |
或者使用子查询:
1 | SELECT * |
EXISTS
关键字
EXISTS
关键字用于检查子查询是否至少返回一行数据。它不关心子查询返回的具体数据,只关注是否有返回数据。如果有返回数据,则返回true
,否则返回false
。
假设我们有一个Customers
表和一个Orders
表,我们希望查询所有有订单的客户。
1 | SELECT * |
区别与适用场景
IN
适用场景
- 简单列表匹配:当需要检查某个值是否存在于一个固定的列表中时,
IN
非常适用。 - 子查询结果匹配:当需要检查某个值是否存在于子查询的结果集中时,
IN
也很适用。
EXISTS
适用场景
- 存在性检查:当只需要检查是否存在满足条件的记录,而不关心具体返回的数据时,
EXISTS
非常适用。 - 性能优化:在某些情况下,
EXISTS
的性能可能优于IN
,特别是在子查询返回大量数据时。
MySQL基本函数
MySQL提供了丰富的内置函数,用于处理字符串、数值、日期和聚合数据。以下是一些常用的MySQL函数及其说明。
字符串函数
CONCAT(str1, str2, ...)
连接多个字符串,返回拼接结果。
1 | SELECT CONCAT('Hello', ' ', 'World'); -- 返回 'Hello World' |
LENGTH(str)
返回字符串的长度(字符数)。
1 | SELECT LENGTH('Hello World'); -- 返回 11 |
SUBSTRING(str, start, length)
从指定位置开始,截取指定长度的字符串。
1 | SELECT SUBSTRING('Hello World', 7, 5); -- 返回 'World' |
REPLACE(str, from_str, to_str)
将字符串中的指定字符替换为另一个字符。
1 | SELECT REPLACE('Hello World', 'o', 'a'); -- 返回 'Hella Warld' |
数值函数
ABS(num)
返回数值的绝对值。
1 | SELECT ABS(-10); -- 返回 10 |
POWER(num, n)
返回数值的n次幂。
1 | SELECT POWER(2, 3); -- 返回 8 |
日期函数
NOW()
返回当前日期和时间。
1 | SELECT NOW(); -- 返回当前日期和时间,例如 '2023-10-01 12:34:56' |
CURDATE()
返回当前日期。
1 | SELECT CURDATE(); -- 返回当前日期,例如 '2023-10-01' |
聚合函数
COUNT(column)
计算指定列中的非NULL值的个数。
1 | SELECT COUNT(OrderID) FROM Orders; -- 返回订单表中OrderID列的非NULL值个数 |
SUM(column)
计算指定列的总和。
1 | SELECT SUM(Price) FROM Products; -- 返回产品表中Price列的总和 |
AVG(column)
计算指定列的平均值。
1 | SELECT AVG(Price) FROM Products; -- 返回产品表中Price列的平均值 |
MAX(column)
返回指定列的最大值。
1 | SELECT MAX(Price) FROM Products; -- 返回产品表中Price列的最大值 |
MIN(column)
返回指定列的最小值。
1 | SELECT MIN(Price) FROM Products; -- 返回产品表中Price列的最小值 |
SQL查询执行顺序
所有的查询语句都是从FROM开始执行,在执行过程中,每个步骤都会生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入,最后一个步骤产生的虚拟表即为输出结果。执行顺序如下:
sql查询语句执行顺序注释:
1 | -- (9) 选择要查询的列 |
SQL请求执行过程详解
- 连接管理
连接器:客户端通过连接器与MySQL服务器建立连接。连接器负责验证客户端的身份和权限,并维护连接状态。
- 查询缓存
缓存查询:在MySQL 5.7及之前版本中,查询缓存功能默认关闭,而在MySQL 8.0中已被移除。如果查询缓存功能开启且命中缓存,服务器将直接返回缓存结果,从而提高查询效率。
- 语法解析
解析器:解析器负责对SQL语句进行词法和语法分析。词法分析确保SQL语句中的关键词和标识符正确无误,语法分析则验证SQL语句的结构是否符合语法规则。解析完成后,生成一棵语法树(AST)。
- 预处理
预处理阶段:预处理器对语法树进行进一步处理,检查表和字段是否存在,并验证SQL语句的语义正确性。此阶段还会处理SQL语句中的占位符,确保查询的完整性和一致性。
- 查询优化
优化器:优化器是SQL执行过程中的核心组件,负责选择最优的执行计划。优化器会评估多种可能的执行路径,并根据成本模型选择查询成本最小的执行计划。优化过程包括但不限于索引选择、表连接顺序优化、子查询优化等。
- 执行引擎
执行阶段:执行引擎根据优化器生成的执行计划,从存储引擎中读取数据。执行过程中,数据可能被缓存以提高后续查询的效率。最终,执行引擎将结果集返回给客户端。
MySQL存储引擎
InnoDB
- 默认引擎:InnoDB是MySQL的默认存储引擎,适用于高并发场景。
- 事务支持:提供
ACID
(原子性、一致性、隔离性、持久性)事务特性,确保数据的一致性和完整性。 - 锁机制:支持行级锁,减少锁冲突,提高并发性能。
- 外键约束:支持外键约束,确保表之间的数据完整性。
MyISAM
- 存储优化:MyISAM具有较低的存储和内存消耗,适用于大量读操作的场景。
- 性能特点:不支持事务,没有行级锁和外键约束,因此在高并发写操作场景下性能较差。
- 适用场景:适合读密集型应用,如数据仓库、日志记录等。
Memory
- 内存存储:Memory引擎将数据存储在内存中,适用于需要快速读取的场景。
- 性能优势:由于数据存储在内存中,读取速度极快,适合临时数据存储和高频读取操作。
- 数据持久性:不支持事务、行级锁和外键索引。数据在服务器重启或崩溃时会丢失,因此不适合需要持久化的数据存储。
通过选择合适的存储引擎,可以根据应用的具体需求优化数据库性能和数据管理策略。InnoDB适用于需要高并发和事务支持的场景,MyISAM适合读密集型应用,而Memory引擎则适用于需要快速读取的临时数据存储。
数据文件
在MySQL中,我们可以创建一个测试数据库test_db
和一张测试表test_order
。在数据库的存储路径下,可以看到以下三个文件:
- db.opt:该文件记录了当前数据库的默认字符集和校验规则。
- test_order.frm:此文件存储了表的结构信息,包括字段定义、索引等,主要用于描述表的结构。
- test_order.idb:所有的元数据和实际数据都存储在这个文件中,包括表的数据、索引等。
联合索引
联合索引是由多个字段共同组成的索引。例如,索引可以基于字段A、B和C创建。联合索引的构建遵循最左匹配原则,即首先按照联合索引中的第一个字段进行分组排序,然后在此基础上按第二个字段分组排序,依此类推。
最左匹配原则
最左匹配原则意味着查询必须从联合索引的最左边的字段开始匹配。例如,如果联合索引是基于字段A、B和C创建的,那么查询条件中必须包含字段A,才能利用该索引。如果查询条件只包含字段B和C,则无法利用该联合索引,因为往后的索引是全局无序的,而只是相对上一个字段局部有序。
索引区分度的重要性
在构建联合索引时,优先选择区分度高的字段作为前缀。区分度的计算公式为:区分度 = (不同值的数量) / (总记录数)
。区分度越高,索引的选择性越好,查询效率也越高。例如,使用性别作为联合索引的首个字段,由于其区分度极低(接近于0),即使通过索引找到了结果,由于是非聚簇索引,仍需执行回表操作,增加IO次数,因此不建议使用。
索引失效的情况
从索引的物理存储特性来看,以下行为可能导致索引失效:
- 模糊查询:使用通配符开头的模糊查询(如
LIKE '%keyword'
)通常会导致索引失效,因为数据库无法利用索引来快速定位匹配的记录。 - 范围查询:在联合索引中,范围查询(如
>
、<
、BETWEEN
等)可能会导致后续字段的索引失效。例如,如果查询条件是A > 10 AND B = 5
,那么字段B的索引可能无法被有效利用。 - 函数操作:对索引字段进行函数操作(如
LOWER(column)
)会导致索引失效,因为数据库无法直接使用索引进行比较。
通过合理设计和使用联合索引,可以显著提高查询性能,减少数据库的I/O操作。