MySQL8数据库高级特性
一、主键(Primary Key)
特性
唯一标识一条记录,不能有重复值
一个表只能有一个主键
可以是单列或多列的组合
自动定义为 NOT NULL
作用
数据的唯一性标识:通过为主表中的每一行数据分配一个唯一的主键值,可以在整个表甚至整个数据库的范围内准确地识别和区分每一条记录。
数据的完整性维护:主键的非空和唯一性约束强制保证了数据的完整性。当插入或更新数据时,数据库会自动检查主键值是否满足要求,若不满足则拒绝操作,防止出现重复或无效的数据。
提高查询性能:由于主键自带索引,在根据主键进行查询时,数据库能够快速定位到目标记录,大大减少了查询所需的时间和资源,尤其是在处理大型数据集时效果更为显著。
建立表间关系的基础:在关系型数据库中,主键常用于建立不同表之间的关联关系。通过将一个表的主键作为另一个表的外键,可以实现多表之间的数据关联和操作,如连接查询、级联操作等。
创建
CREATE TABLE users ( id INT AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password varchar(50) NOT NULL, PRIMARY KEY (id) );
修改
不直接支持修改现有的主键约束,但你可以删除并重新创建它:
ALTER TABLE users DROP PRIMARY KEY; ALTER TABLE users ADD PRIMARY KEY (id);
删除
ALTER TABLE users DROP PRIMARY KEY;
二、外键(Foreign Key)
特性
确保子表中的数据在父表中有对应值
可以实现级联更新和删除
外键一定是某一张表的主键
作用
维护数据一致性:外键约束强制从表中的外键值必须与主表中的主键值相对应,防止出现孤立的数据或无效的引用。当在主表中删除或更新一条记录时,数据库会根据外键约束的设置,自动处理从表中与之相关的记录,确保数据的一致性和完整性。
实现多表关联操作:通过外键可以方便地将多个表关联起来,以便在查询和操作数据时能够从多个相关表中获取所需的信息。这种关联使得数据库能够模拟现实世界中的复杂关系,如客户与订单、学生与课程等关系,从而更好地组织和管理数据。
简化数据模型设计:使用外键可以清晰地表达不同表之间的关系,使数据库的数据模型更加规范化和易于理解。在设计数据库结构时,合理地使用外键可以避免数据的冗余存储,提高数据的存储效率和可维护性。
创建
CREATE TABLE orders ( order_id INT AUTO_INCREMENT, user_id INT, owner varchar(50) NOT NULL, level int NOT NULL, age int, PRIMARY KEY (order_id), FOREIGN KEY (user_id) REFERENCES users(id) );
修改
通常,外键是在创建表的时候一同定义的,修改外键需要先删除再重新创建:
ALTER TABLE orders DROP FOREIGN KEY fk_user_id; ALTER TABLE orders ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES new_users(id);
语句结构及含义
ALTER TABLE:这是 SQL 中的关键字组合,用于修改已存在的表结构。在这里,它表明接下来要对 orders 表进行某种结构上的变更操作。
orders:是你要修改的目标表的名称,也就是想要在这个表上添加外键约束的表。
ADD CONSTRAINT fk_user_id:
ADD CONSTRAINT 表示要添加一个约束条件。约束条件可以是多种类型,比如主键约束、外键约束、唯一约束等,在这里明确是要添加外键约束。
fk_user_id 是给这个外键约束起的名字。给外键约束命名是一个良好的实践,方便后续在查看表结构、处理约束相关问题(比如删除约束)时能够清晰地识别它。
FOREIGN KEY (user_id):指定了在 orders 表中的哪一列将作为外键列,这里是 user_id 列,意味着该列的值将与另一个表中的主键或唯一键列建立关联,以确保数据的参照完整性。
REFERENCES new_users(id):指出了外键所引用的表和列。
REFERENCES 是用于表示引用关系的关键字。
new_users 是被引用的主表名称,表明 orders 表中的外键列 user_id 的值需要参照 new_users 表中的 id 列来取值,并且 new_users 表中的 id 列通常应为主键或者唯一键,这样才能保证数据的一致性和完整性。
删除
ALTER TABLE orders DROP FOREIGN KEY fk_user_id;
三、索引(Index)
特性
通过快速定位数据来提高查询速度
可以是唯一的(unique)或非唯一的
类型
按数据结构分类可分为:B+tree索引、Hash索引、Fulltext索引。
按物理存储分类可分为:聚簇索引、二级索引(辅助索引)。
按字段特性分类可分为:主键索引、普通索引、前缀索引。
按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
按数据结构分类
B+tree 索引
解析:B+tree 是一种自平衡的多路查找树,它的非叶子节点只存储索引关键字和指向下一层节点的指针,数据都存储在叶子节点上,且叶子节点之间通过指针相连形成有序链表。这种结构使得在进行范围查询和排序时,只需遍历叶子节点链表即可,效率较高,同时也能保持较好的平衡性,保证查询的稳定性和高效性。
应用场景:适用于大多数的查询场景,特别是对范围查询、排序查询以及精确查询都有较好的支持。常用于经常需要按照某个范围查找数据,或者对查询结果有排序需求的列。
创建案例:假设在一个名为 students 的表中,有 age 列,经常需要根据年龄范围查询学生信息,就可以创建 B+tree 索引。在 MySQL中,使用 CREATE INDEX 语句创建索引,如下所示:
CREATE INDEX idx_age ON students(age);
Hash 索引
解析:Hash 索引基于哈希表实现,通过对索引列的值进行哈希运算,将运算结果作为存储位置的索引,从而快速定位到对应的数据行。它的优点是查找速度极快,时间复杂度几乎为常数级,但不支持范围查询和排序操作,因为哈希值是无序的,且存在哈希冲突的可能性。
应用场景:适用于等值查询的场景,即当查询条件是精确匹配某个值时,Hash 索引能快速定位到数据。比如在一个存储用户登录信息的表中,根据用户名精确查询用户密码等信息时,Hash 索引能发挥很好的性能。
创建案例:在 MySQL中,如果使用的存储引擎支持 Hash 索引,例如 Memory 存储引擎,可以在创建表时指定使用 Hash 索引。示例如下:
CREATE TABLE users (id INT,username VARCHAR(50),password VARCHAR(50),INDEX USING HASH (username) ) ENGINE = Memory;
Fulltext 索引
解析:Fulltext 索引主要用于对文本内容的快速检索,它会对文本进行分词处理,将文本内容分解成一个个的单词或词组,并建立索引。查询时,可以通过匹配这些单词或词组来查找包含特定关键词的文本记录,支持自然语言的模糊查询。
应用场景:常用于对大量文本数据进行模糊查询和搜索的场景,如博客文章表、新闻文章表、电商产品描述表等,用户可通过输入关键词查找相关文章或产品。
创建案例:假设有一个 articles 表,其中 content 列存储文章内容,要在该列创建 Fulltext 索引,可使用以下语句:
CREATE FULLTEXT INDEX idx_content ON articles(content);
按物理存储分类
聚簇索引
解析:聚簇索引将数据存储与索引存储在一起,表中的数据按照聚簇索引列的值进行物理排序存储,其叶子节点就是数据页,存储了实际的数据记录。一个表只能有一个聚簇索引,通常主键会自动成为聚簇索引,如果没有定义主键,My 会选择一个唯一且非空的列作为聚簇索引,如果都不满足,则会隐式创建一个自增长的整数列作为聚簇索引。
应用场景:适合经常按照主键或聚簇索引列进行范围查询、排序以及精确查询的情况。由于数据在物理上是按照聚簇索引列排序的,所以这些操作的性能较好。
创建案例:一般情况下,在创建表时定义主键,MySQL 就会自动创建聚簇索引。例如:
CREATE TABLE products (id INT PRIMARY KEY,product_name VARCHAR(100),price DECIMAL(10,2) );
这里的 id 列既是主键,也是聚簇索引列。
二级索引(辅助索引)
解析:二级索引是除聚簇索引之外的所有索引,它的叶子节点存储的不是实际的数据记录,而是聚簇索引列的值或者是指向数据记录的指针。当通过二级索引查询数据时,首先根据二级索引找到对应的聚簇索引列的值或指针,然后再通过聚簇索引去查找实际的数据记录。
应用场景:用于在除聚簇索引列之外的其他列上创建索引,以提高对这些列的查询效率。当查询条件不是聚簇索引列,但又需要快速定位数据时,二级索引就非常有用。
创建案例:在上述 products 表中,如果经常需要根据产品名称查询产品信息,可以创建二级索引。语句如下:
CREATE INDEX idx_product_name ON products(product_name);
按字段特性分类
主键索引
解析:主键索引是一种特殊的唯一索引,其特殊性在于每个表只能有一个主键索引,且主键列的值不能为空且具有唯一性。它主要用于标识表中的每一条记录,通过主键可以快速定位到表中的某条具体记录。
应用场景:几乎在所有的数据库表设计中都会用到主键索引,用于唯一标识每一条记录,方便对单条记录进行精确的查询、更新和删除操作。
创建案例:如前面提到的 products 表中定义 id 列为主键,即创建了主键索引:
CREATE TABLE products (id INT PRIMARY KEY,product_name VARCHAR(100),price DECIMAL(10,2) );
普通索引
解析:普通索引是最基本的索引类型,没有任何特殊限制,可以在创建索引时不指定索引类型,My 会默认创建普通索引。它可以创建在表的任何列上,允许列中的值重复。
应用场景:适用于对数据进行一般性的快速查询和检索,当需要经常根据某个列的值来查找数据,但该列的值并不唯一时,普通索引就很有用。
创建案例:假设在 students 表中有一个 gender 列,用于表示学生性别,经常需要根据性别查询学生信息,可创建普通索引:
CREATE INDEX idx_gender ON students(gender);
前缀索引
解析:前缀索引是对索引列值的前一部分字符创建索引,而不是对整个列值创建索引。这样可以减少索引的存储空间,提高索引的查询效率,但可能会降低索引的选择性,导致查询时需要扫描更多的行。
应用场景:适用于索引列的值较长,且前一部分字符具有较好的区分度的情况。比如在一个存储网址的表中,网址通常较长,但可能前几个字符就能区分大部分网址,此时可以创建前缀索引。
创建案例:例如在 urls 表中,url 列存储网址,创建前缀索引:
CREATE INDEX idx_url_prefix ON urls(url(10));
这里表示对 url 列的前 10 个字符创建前缀索引。
按字段个数分类
单列索引
解析:单列索引是指在单个列上创建的索引,它只对这一个列的值进行索引,查询时根据该列的值进行快速定位和检索。
应用场景:当只需要根据某一个列的值进行查询,且该列的选择性较高时,适合创建单列索引。
创建案例:如在 users 表中,根据 users_id 列进行查询时,可以创建单列索引:
CREATE INDEX idx_users_id ON users(users_id);
联合索引(复合索引、组合索引)
解析:联合索引是在多个列上创建的索引,它可以看成是多个列的联合约束和索引。在使用联合索引时,只有当查询条件中使用了联合索引中的最左边的列,并且按照从左到右的顺序依次使用列时,联合索引才会被有效使用。
应用场景:当经常需要同时根据多个列的组合来查询数据时,可以创建联合索引。它可以减少索引的数量,提高查询效率,特别是在多表连接查询中,如果连接条件涉及多个列,联合索引的效果更明显。
创建案例:假设在 orders 表中,经常需要根据 customer_id 和 order_date 两个列的组合来查询订单信息,可以创建联合索引:
CREATE INDEX idx_customer_order ON orders(customer_id, order_date);
创建
#创建普通索引 CREATE INDEX idx_username ON users(username); #创建联合索引 create index orders_index on orders(owner,level);
查看
show index from users;
修改
索引不能直接修改,但可以重建:
DROP INDEX idx_username ON users; CREATE INDEX idx_username ON users (username(10));
删除
DROP INDEX idx_username ON users;
强制使用索引
select * from users force index (idx_username) where username='tom';
索引的过程追踪
explain select id from users;
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | users | NULL | index | NULL | users_index1 | 152 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
字段解析:
基本信息
id
表示查询中执行的顺序编号,编号越大越先执行。如果是相同的 id,则按照从上到下的顺序依次执行;如果是子查询,id 的值会递增,用于标识嵌套的层次关系。
select_type
表示查询的类型,常见的类型有:
SIMPLE:表示简单查询,即不包含子查询或 UNION 等复杂操作的查询。
PRIMARY:表示主查询,当查询中包含子查询时,最外层的查询被标记为 PRIMARY。
SUBQUERY:表示子查询,在主查询中嵌套的内层查询。
UNION:表示 UNION 操作的第二个及其之后的查询部分。当使用 UNION 将多个查询结果合并时,第一个查询的 select_type 为 PRIMARY,其余的为 UNION。
UNION RESULT:表示 UNION 操作的合并结果集。
涉及表的信息
table
显示当前查询所涉及的表名,这里是 info,表示该查询操作针对的是名为 info 的表。
partitions
如果表使用了分区,此字段会显示查询所涉及的分区信息。如果表没有分区,则值为 NULL,像这里显示为 NULL,说明 info 表没有采用分区。
索引使用及数据读取方式
type
表示查询时访问数据的类型,也就是 My 在表中找到所需行的方式,其性能从优到劣依次为:
system:表中只有一行数据,这是 const 类型的特例,基本很少遇到。
const:通过索引一次就找到了,用于 PRIMARY KEY 或 UNIQUE 索引的所有部分与常数比较时,速度非常快。
eq_ref:类似 ref,区别在于使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,常用于多表连接中使用主键或唯一键作为连接条件的情况。
ref:表示使用普通索引,通过索引值可以匹配到表中的多条记录,即返回匹配某个单独值的所有行。
range:表示使用索引进行范围查询,例如使用 >、<、BETWEEN 等操作符对索引列进行范围筛选。
index:表示全索引扫描,和 ALL 类似,但只扫描索引树,通常比 ALL 快一些,因为索引文件通常比数据文件小。
ALL:表示全表扫描,即需要遍历表中的每一条记录来查找满足条件的数据,性能最差,当没有合适的索引或者查询条件无法有效利用索引时会出现这种情况。这里显示为 ALL,说明该查询对 info 表进行了全表扫描。
possible_keys
显示查询时可能用到的索引,这里为 NULL,说明 My 认为当前查询没有可能使用的索引,可能是因为没有创建合适的索引或者查询条件无法匹配到已有的索引。
key
表示实际使用的索引,如果为 NULL,则表示没有使用索引,和当前的 ALL 类型相匹配,即该查询确实没有使用索引进行数据查找。
key_len
表示实际使用的索引长度,以字节为单位。如果没有使用索引,则值为 NULL。可用于判断索引是否被充分利用,比如一个 VARCHAR 类型的列创建了索引,若 key_len 小于该列定义的最大长度,可能是因为只使用了索引的前缀部分。
ref
显示与索引比较的列或常量值,如果是使用常量值进行比较,会显示具体的常量值;如果是连接查询,会显示连接的表的列名。这里为 NULL,是因为没有使用索引,所以不存在与索引比较的列或值。
结果集相关信息
rows
表示 My 根据统计信息估算的需要扫描的行数,这里估算需要扫描 3 行。这个值只是一个估算,实际执行时可能会有所不同,但可以作为评估查询性能的一个参考,扫描的行数越少通常性能越好。
filtered
表示按表条件过滤后剩余行的百分比,这里是 100.00%,意味着查询条件没有对结果集进行额外的过滤筛选,返回的所有行都满足查询条件。
Extra
包含一些额外的信息,用于进一步说明查询的执行情况,常见的有:
Using filesort:表示需要进行文件排序,通常是因为查询中包含 ORDER BY 子句,且无法使用索引来满足排序要求,这可能会导致性能下降。
Using temporary:表示需要使用临时表来存储中间结果,例如在使用 GROUP BY 子句时,如果无法使用索引来完成分组操作,就可能会创建临时表,这也会对性能产生一定的影响。
NULL:如当前情况,表示没有其他特殊的额外信息需要说明。
四、Check 约束(Check Constraint)
特性
保证列数据满足特定条件
在 MySQL 8.0.16 及以后的版本中可用
作用
数据验证:CHECK 约束提供了一种在数据库层面验证数据有效性的机制。它可以防止用户输入不符合特定业务规则或逻辑要求的数据,确保数据的准确性和一致性。例如,限制员工的年龄在 18 到 60 岁之间,或者确保订单金额为正数等。
维护数据完整性:与主键约束、外键约束等一起,共同构成了维护数据库数据完整性的体系。通过在列上设置 CHECK 约束,可以避免数据因误操作或非法输入而出现错误,从而保证数据库中数据的质量和可靠性。
简化应用程序逻辑:将数据验证逻辑放在数据库中,使用 CHECK 约束,可以减少应用程序中对数据验证的重复代码编写。这样,无论数据是通过何种应用程序或接口进行插入或更新,都能自动遵循相同的验证规则,提高了代码的可维护性和复用性。
创建
ALTER TABLE users ADD CONSTRAINT chk_username_format CHECK (username REGEXP '^[a-zA-Z][a-zA-Z0-9_]{2,}$');
查看
select * from information_schema.TABLE_CONSTRAINTS where TABLE_NAME='users'\G;
修改
创建后,无法直接修改,通常需要删除并重新定义:
ALTER TABLE users DROP CONSTRAINT chk_username_nonempty; ALTER TABLE users ADD CONSTRAINT chk_username_nonempty CHECK (username <> '' AND username IS NOT NULL);
删除
ALTER TABLE users DROP CONSTRAINT chk_username_nonempty;
应用场景
限制数值范围
-- 创建员工表,限制员工年龄在18至60岁之间 CREATE TABLE users (users_id INT PRIMARY KEY,users_name VARCHAR(50),age INT CHECK (age >= 18 AND age <= 60) );
在上述示例中,CHECK
约束确保了插入users
表的age
列的数据必须在 18 到 60 岁这个范围内。
限制字符串格式
-- 创建产品表,限制产品编号必须以'P'开头,后面跟5位数字 CREATE TABLE products (product_id VARCHAR(10) PRIMARY KEY CHECK (product_id REGEXP '^P[0-9]{5}$'),product_name VARCHAR(50) );
这里的CHECK
约束规定了product_id
列的数据格式,必须是以字母P
开头,后面跟着 5 位数字的字符串,以此来规范产品编号的格式。
基于多列的条件限制
-- 创建订单表,确保订单金额大于0,且折扣率在0到1之间 CREATE TABLE orders (order_id INT PRIMARY KEY,order_date DATE,total_amount DECIMAL(10,2) CHECK (total_amount > 0),discount_rate DECIMAL(3,2) CHECK (discount_rate >= 0 AND discount_rate <= 1),final_amount DECIMAL(10,2) );
-- 创建表时先不设置涉及多列运算的那个CHECK约束 CREATE TABLE orders (order_id INT PRIMARY KEY,order_date DATE,total_amount DECIMAL(10,2) CHECK (total_amount > 0),discount_rate DECIMAL(3,2) CHECK (discount_rate >= 0 AND discount_rate <= 1),final_amount DECIMAL(10,2) ); -- 创建插入操作前的触发器,用于检查final_amount的值是否符合计算逻辑 DELIMITER $$ CREATE TRIGGER check_final_amount_before_insert BEFORE INSERT ON orders FOR EACH ROW BEGINIF NEW.final_amount <> NEW.total_amount * (1 - NEW.discount_rate) THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'final_amount value does not match the calculation';END IF; END $$ DELIMITER ; -- 创建更新操作前的触发器,同样用于检查final_amount的值在更新时是否符合计算逻辑 DELIMITER $$ CREATE TRIGGER check_final_amount_before_update BEFORE UPDATE ON orders FOR EACH ROW BEGINIF NEW.final_amount <> NEW.total_amount * (1 - NEW.discount_rate) THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'final_amount value does not match the calculation';END IF; END $$ DELIMITER ;