MySQL8数据库高级特性

 创建测试表

 create database jx character set utf8;
 use jx;
 my> desc users;
 +----------+-------------+------+-----+---------+----------------+
 | Field    | Type        | Null | Key | Default | Extra          |
 +----------+-------------+------+-----+---------+----------------+
 | id       | int         | NO   | PRI | NULL    | auto_increment |
 | username | varchar(50) | NO   | MUL | NULL    |                |
 | password | varchar(50) | NO   |     | NULL    |                |
 +----------+-------------+------+-----+---------+----------------+
 my> desc orders;
 +----------+-------------+------+-----+---------+----------------+
 | Field    | Type        | Null | Key | Default | Extra          |
 +----------+-------------+------+-----+---------+----------------+
 | order_id | int         | NO   | PRI | NULL    | auto_increment |
 | user_id  | int         | YES  | MUL | NULL    |                |
 | owner    | varchar(50) | NO   |     | NULL    |                |
 | level    | int         | NO   |     | NULL    |                |
 | age      | int         | YES  |     | NULL    |                |
 +----------+-------------+------+-----+---------+----------------+

一、主键(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 子表名 
 ADD CONSTRAINT 新外键名称 
 FOREIGN KEY (子表外键列) 
 REFERENCES 新父表名(新主键列)
 [ON DELETE 规则] [ON UPDATE 规则]; 
语句结构及含义
  • 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 个字符创建前缀索引。

按字段个数分类
  • 单列索引

    • 解析:单列索引是指在单个列上创建的索引,它只对这一个列的值进行索引,查询时根据该列的值进行快速定位和检索。

    • 应用场景:当只需要根据某一个列的值进行查询,且该列的选择性较高时,适合创建单列索引。

    • 创建案例:如在 employees 表中,根据 employee_id 列进行查询时,可以创建单列索引:

 CREATE INDEX idx_employee_id ON employees(employee_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_nonempty CHECK (username <> '');
查看
 ​
 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 employees (
     employee_id INT PRIMARY KEY,
     employee_name VARCHAR(50),
     age INT CHECK (age >= 18 AND age <= 60)
 );

在上述示例中,CHECK约束确保了插入employees表的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
 BEGIN
     IF NEW.final_amount <> NEW.total_amount * (1 - NEW.discount_rate) THEN
         SIGNAL 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
 BEGIN
     IF NEW.final_amount <> NEW.total_amount * (1 - NEW.discount_rate) THEN
         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'final_amount value does not match the calculation';
     END IF;
 END $$
 DELIMITER ;

使用注意事项

  • 性能影响:虽然 CHECK 约束有助于保证数据的准确性,但过多复杂的 CHECK 约束可能会对数据库的性能产生一定影响,特别是在进行大量数据插入和更新操作时。因为每次操作都需要对约束条件进行检查和计算,增加了数据库的处理负担。因此,在使用时应避免设置过于复杂或不必要的约束条件。

  • 约束条件的合理性:设置的约束条件应基于合理的业务规则和数据逻辑,既要确保能够有效地验证数据的有效性,又不能过于严格或不合理,以免影响正常的数据操作和业务流程。同时,要考虑到数据的变化和扩展性,避免因业务需求的变化而导致约束条件频繁修改。

  • 数据库兼容性:不同的数据库管理系统对 CHECK 约束的支持和实现方式可能略有不同。例如,某些数据库可能对 CHECK 约束中的函数和表达式的使用有限制,或者在处理复杂约束条件时的行为有所差异。因此,在开发跨数据库的应用程序时,需要注意兼容性问题,并进行充分的测试。

五、存储过程(Stored Procedure)

一组为了完成特定功能而预先编译好并存储在数据库中的 SQL 语句集合。

特性

  • 封装一段SQL语句,便于复用和维护

  • 可以包含业务逻辑

优点

  • 提高性能

    • 存储过程在创建时就经过了预编译和优化,当被调用执行时,无需再次进行编译,能够直接运行,大大减少了执行时间和资源消耗,提高了数据库的运行效率。

    • 例如,一个复杂的多表联合查询操作,如果将其封装成存储过程,每次执行时就不必重复解析和编译该查询语句,能快速得到结果。

  • 增强安全性

    • 通过存储过程,数据库管理员可以对用户的操作进行限制和授权。用户只能通过执行特定的存储过程来访问和修改数据,而不能直接对底层表进行任意操作,从而保证了数据的安全性和完整性。

    • 比如,只允许用户通过执行存储过程来更新员工工资信息,而不允许直接对工资表进行更新操作,这样可以防止用户误操作或恶意篡改数据。

  • 简化复杂操作

    • 它可以将一系列复杂的 SQL 语句和业务逻辑封装在一起,形成一个独立的单元,使代码更加模块化,易于理解和维护。当需要执行这些复杂操作时,只需调用相应的存储过程即可,而无需在应用程序中重复编写大量的 SQL 语句。

    • 假设要实现一个根据订单状态更新库存的复杂业务逻辑,涉及多张表的关联和数据更新,如果写成存储过程,在应用程序中只需调用该存储过程,而不用每次都去处理复杂的 SQL 逻辑。

  • 减少网络流量

    • 应用程序与数据库之间的通信通常是通过网络进行的。当使用存储过程时,只需在网络上传输存储过程的名称和参数,而不需要传输大量的 SQL 语句文本,从而减少了网络传输的数据量,提高了系统的响应速度。

    • 例如,执行一个包含多条 SQL 语句的复杂业务逻辑,如果通过存储过程调用,网络传输的数据量会大大减少,相比直接传输多条 SQL 语句能更快得到响应。

查看
查看所有
 SHOW PROCEDURE status\G;
查看指定数据库的存储过程
 SHOW PROCEDURE status where Db='databaseName'\G;
查看指定存储过程
 show create procedure GetAllUsers\G;

创建

 -- 语法
 CREATE PROCEDURE procedure_name ([parameter1 datatype1 [, parameter2 datatype2,...]])
 BEGIN
     -- SQL语句和逻辑处理
     [DECLARE variable_name datatype;] -- 声明局部变量
     -- 一系列SQL操作
 END;
 ​
 -- 示例:创建一个简单的存储过程,用于查询指定员工的信息
 DELIMITER //
 CREATE PROCEDURE get_employee_info(IN employee_id INT)
 BEGIN
     SELECT * FROM employees WHERE id = employee_id;
 END //
 DELIMITER ;
 ###############
 DELIMITER //     -- 修改语句结束符号为“//”
 ​
 CREATE PROCEDURE GetAllUsers()
 ​
 BEGIN
 ​
 SELECT * FROM jx.users;
 ​
 END //
 ​
 DELIMITER ;   -- 修改为“;”

修改

 DELIMITER //
 ​
 DROP PROCEDURE IF EXISTS GetAllUsers;
 ​
 CREATE PROCEDURE GetAllUsers()
 ​
 BEGIN
 ​
 SELECT username FROM users;
 ​
 END //
 ​
 DELIMITER ;

删除

 DROP PROCEDURE IF EXISTS GetAllUsers;

调用

 -- 语法
 CALL procedure_name ([parameter_value1 [, parameter_value2,...]]);
 ​
 -- 示例:调用上述创建的存储过程
 CALL get_employee_info(1001);
 ​
 CALL jx.GetAllUsers();

应用场景

  • 数据处理和转换

    • 当需要对大量数据进行复杂的处理和转换操作时,存储过程是一个很好的选择。比如,从多个表中获取数据,进行数据清洗、计算、汇总等操作,然后将结果存储到新的表中或返回给应用程序。

    • 例如,每月要对销售数据进行统计分析,包括计算销售额、销售量、平均售价等指标,就可以编写一个存储过程来实现这些数据处理和计算功能。

  • 业务逻辑封装

    • 对于一些特定的业务逻辑,如订单处理、库存管理、用户认证等,可以将其封装在存储过程中,以便在不同的应用程序或模块中重复使用。这样不仅提高了代码的复用性,还能保证业务逻辑的一致性。

    • 以订单处理为例,从创建订单、更新库存、计算订单金额到订单状态的跟踪等一系列操作,都可以封装在一个或多个存储过程中,方便统一管理和调用。

  • 系统维护和管理

    • 存储过程可用于执行一些系统维护和管理任务,如备份数据、恢复数据、优化数据库性能等。通过编写存储过程,可以将这些任务自动化,定期执行,减少人工操作的工作量和风险。

    • 比如,编写一个存储过程用于每天定时备份重要的数据表,确保数据的安全性和可恢复性。

六、触发器(Trigger)

一种特殊类型的存储过程,它与表紧密关联,会在特定的表操作事件发生时自动执行相应的 SQL 语句块 。

特性

  • 在 INSERT、UPDATE 或 DELETE 操作之前或之后自动执行

  • 用于自动化维护数据的完整性或实施业务规则

触发器的类型

  • 按触发事件分类

    • INSERT 触发器:在向表中插入数据时触发。例如,在一个员工信息表中插入新员工记录时,可以通过 INSERT 触发器自动向其他相关表中插入相应的关联数据,或者对插入的数据进行一些合法性检查和预处理。

    • UPDATE 触发器:当表中的数据被更新时触发。比如,在更新员工工资表中的工资数据时,UPDATE 触发器可以自动计算并更新与工资相关的其他统计数据,如平均工资、工资总额等。

    • DELETE 触发器:在从表中删除数据时触发。假设在删除某个部门的信息时,DELETE 触发器可以自动删除该部门下的所有员工记录,或者将这些员工记录转移到其他部门,以保证数据的完整性和关联性。

  • 按触发时间分类

    • BEFORE 触发器:在触发事件(如 INSERT、UPDATE、DELETE)发生之前执行。它可以用于在数据真正被修改之前进行一些预处理操作,如数据验证、默认值设置等。如果在 BEFORE 触发器中执行了 ROLLBACK 语句,那么触发事件将被取消,数据不会被修改。

    • AFTER 触发器:在触发事件发生之后执行。通常用于在数据修改完成后进行一些后续处理操作,如记录日志、更新缓存、发送通知等。由于 AFTER 触发器是在数据修改后执行,所以它无法阻止触发事件的发生,但可以根据数据的修改情况进行相应的处理。

创建

 -- 创建触发器的基本语法
 CREATE TRIGGER trigger_name
 {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
 FOR EACH ROW
 BEGIN
     -- 触发器主体,包含要执行的SQL语句
     [DECLARE variable_name datatype;] -- 可声明局部变量
     -- 一系列SQL操作
 END;
 -- 示例
 -- 创建一个BEFORE INSERT触发器,用于在向orders表中插入新订单时设置订单日期为当前日期
 DELIMITER //
 CREATE TRIGGER set_order_date_before_insert
 BEFORE INSERT ON orders
 FOR EACH ROW
 BEGIN
     SET NEW.order_date = CURDATE();
 END //
 DELIMITER ;
 -- 创建一个AFTER DELETE触发器,用于在从prders表中删除会员信息时,将相关的用户记录也删除;orders表中必须只能匹配一条关于users表中的id记录
 DELIMITER //
 CREATE TRIGGER delete_users_after_delete
 AFTER DELETE ON orders
 FOR EACH ROW
 BEGIN
     DELETE FROM users WHERE id = OLD.user_id;
 END//
 DELIMITER ;
 ##########################################################
 DELIMITER //
 ​
 CREATE TRIGGER before_user_insert BEFORE INSERT ON users
 ​
 FOR EACH ROW
 ​
 BEGIN
 ​
 SET NEW.username = NOW();  # username为users表的某列名称
 ​
 END //
 ​
 DELIMITER ;
 ​
 ##############################
 DELIMITER //
 CREATE TRIGGER before_users_insert BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
 set @var1 = 'A';
 SET NEW.username = concat(NEW.username,@var1);  # username为users表的某列名称
 END //
 DELIMITER ;
查看
 show triggers\G;

分析

Before与After区别
  • BEFORE触发器是在实际数据更改操作之前执行的。它们通常用于验证将要插入、更新或删除的数据是否符合特定的条件,以及在执行操作之前对其进行修改。BEFORE触发器还可以用于检查外键约束或确保数据的完整性。

  • AFTER触发器是在数据插入、更新或删除操作已经完成之后执行的。它们通常用于记录日志或执行其他操作,这些操作可能需要在数据进行更改后才能执行。AFTER触发器还可以用于计算汇总值或执行其他聚合操作。

NEW和OLD

NEW:新行

OLD:旧行

  • 当在触发器中编写 INSERT、UPDATE 或 DELETE 语句时,“new” 和 “old” 分别代表触发器所针对的表中被修改(即被更新、插入或删除)的新行和旧行。

  • 如果触发器是在 INSERT 操作之前被触发,那么 “new” 代表将要被插入到表中的新行。

  • 如果是在 UPDATE 或 DELETE 操作之前被触发,那么 “old” 代表将要被更新或删除的行,而 “new” 代表更新后或者删除后将要被插入到表中的新行。

修改

与存储过程类似,必须先删除再重新创建:

DELIMITER //

DROP TRIGGER IF EXISTS before_user_insert;

CREATE TRIGGER before_user_insert BEFORE INSERT ON users

FOR EACH ROW

BEGIN

SET NEW.username = NOW();

END;

DELIMITER ;

删除

DROP TRIGGER before_user_insert;

七、事务(Transaction)

事务(Transaction)是一组逻辑相关的SQL操作,这些操作要么全部成功执行,要么全部失败回滚,保证数据库从一个一致状态转换到另一个一致状态

适用场景:银行转账(扣款与入账需同时成功)、订单创建(更新库存、生成订单、支付状态需一致)等

特性

  • 一组 SQL操作,要么全部成功,要么全部失败

  • 用于保证数据的一致性和完整性

  • 支持提交(COMMIT)和回滚(ROLLBACK)

  • 提交:将事务中所有临时修改永久保存到数据库,其他会话可立即看到修改结果,提交后数据不可撤销(持久性)

  • 回滚:撤销事务中所有未提交的操作,恢复数据到事务开始前的状态

  • 适用场景:操作出错(如库存不足)、业务逻辑校验失败时

ACID属性(必须背会)

  1. 原子性(Atomicity): 事务被视为最小的不可分割的工作单位,只有事务中所有操作都完成,事务才算成功。如果事务中的任一操作失败,那么整个事务也会失败并且系统会自动撤销或回滚事务的所有操作,保持数据的一致性。

  2. 一致性(Consistency): 事务必须保证它在不破坏数据库的完整性和约束的前提下执行。在事务开始之前和结束之后,数据库必须保持一致状态。

  3. 隔离性(Isolation): 事务在提交之前,对其它事务是不可见的。这是为了防止多个并发事务相互干扰。依赖于隔离级别,事务可能会"看到"其他并非提交的事务所做的改动,反之亦然。MySQL提供不同的隔离级别如READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ (默认的隔离级别), 和SERIALIZABLE。

  4. 持久性(Durability): 一旦事务提交,其所做的改动就会被永久保存在数据库中,即使数据库系统发生故障也不会丢失这些改动。

开始事务

START TRANSACTION;

执行事务中的操作例如插入、更新:

INSERT INTO users (username) VALUES ('john_doe');

UPDATE orders SET user_id = LAST_INSERT_ID() WHERE order_id = 123;

提交事务

COMMIT;

回滚事务

ROLLBACK;

事务编写案例

银行账户转账
创建数据库
CREATE  DATABASE bank;
创建数据表
CREATE TABLE accounts (

account_number INT PRIMARY KEY,

account_name VARCHAR(100),

balance DECIMAL(15, 2)  -- 保留两位小数的余额

);
逻辑分析
  1. 首先,开始一个新的事务。

  2. 检查源账户的余额是否足够进行转账。

  3. 从源账户扣除转账金额。

  4. 向目的账户添加转账金额。

如果所有步骤都成功,则提交事务;如果有任何一步失败,则回滚事务以撤销所有更改。

编写脚本
-- 修改结束符号
DELIMITER //  
CREATE PROCEDURE TransferFunds()
BEGIN
START TRANSACTION;
-- 关闭自动提交
SET autocommit=0;
-- 源账户相关信息
SET @sourceAccountNumber = 1001;
SET @transferAmount = 101;
-- 目标账户相关信息
SET @destinationAccountNumber = 1002;
-- 检查源账户的余额是否充足
SELECT @sourceBalance := balance as 源账户余额 FROM accounts WHERE account_number = @sourceAccountNumber;
IF @sourceBalance < @transferAmount THEN
-- 余额不足,回滚事务
ROLLBACK;
-- 使用SIGNAL语句生成一个错误
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足';
-- 退出存储过程
else
-- 扣除源账户金额
UPDATE accounts SET balance = balance - @transferAmount WHERE account_number = @sourceAccountNumber;
-- 增加目的账户金额
UPDATE accounts SET balance = balance + @transferAmount WHERE account_number = @destinationAccountNumber;
select * from accounts where account_number = @sourceAccountNumber;
-- 提交事务
COMMIT;
END IF;
END //
-- 修改结束符号为;
DELIMITER ; 
调用事务
call  TransferFunds();

事务的隔离级别

  • 读未提交(READ UNCOMMITTED)

    • 这是最低的隔离级别,在该级别下,一个事务可以读取到另一个未提交事务的数据,可能会导致脏读、不可重复读和幻读等问题。脏读是指一个事务读取到了另一个未提交事务修改的数据,而这些数据可能在后续被回滚,从而导致读取的数据不准确。

    • 例如,事务 A 修改了一条数据但尚未提交,事务 B 在此时读取了该数据,之后事务 A 回滚了修改,那么事务 B 所读取的数据就是脏数据。

  • 读已提交(READ COMMITTED)

    • 在这个隔离级别下,一个事务只能读取到另一个已提交事务的数据,可以避免脏读问题,但仍可能出现不可重复读的情况。不可重复读是指在一个事务中,多次读取同一数据时,由于其他事务对该数据进行了修改并提交,导致每次读取的结果都不一样。

    • 比如,事务 A 在第一次读取某数据后,事务 B 对该数据进行了修改并提交,事务 A 再次读取该数据时,就会得到不同的结果。

  • 可重复读(REPEATABLE READ)

    • 这是 MySQL 8 默认的隔离级别,它确保在同一个事务中,对同一数据的多次读取结果是相同的,可以避免脏读和不可重复读问题,但可能会出现幻读现象。幻读是指在一个事务中,按照某个条件进行数据查询时,多次查询的结果可能不同,因为其他事务可能插入了满足该条件的新数据。

    • 例如,事务 A 按照某个条件查询数据时没有找到某条记录,而在事务 A 执行期间,事务 B 插入了一条满足该条件的新记录,当事务 A 再次按照相同条件查询时,就会发现多了一条之前没有的记录,就像出现了幻觉一样。

  • 串行化(SERIALIZABLE)

    • 这是最高的隔离级别,它通过强制事务串行执行,避免了脏读、不可重复读和幻读等所有并发问题,但会严重影响系统的性能,因为它限制了并发事务的执行。在这种隔离级别下,所有事务都按照顺序依次执行,就像排队一样,一个事务执行完了,下一个事务才能开始执行。

    • 比如,有多个事务需要访问同一个数据库资源,在串行化隔离级别下,这些事务会依次排队等待执行,而不会出现并发执行的情况。

事务的应用场景

  • 金融交易

    • 如银行转账、股票交易等场景,必须保证资金的转移和交易操作的原子性、一致性、隔离性和持久性,以确保金融数据的准确无误和交易的安全可靠。

    • 例如,在银行转账时,从一个账户转出资金和转入另一个账户的操作必须作为一个事务来处理,以防止出现资金丢失或错误转移的情况。

  • 订单处理

    • 在电商或其他涉及订单管理的系统中,订单的创建、商品库存的调整、客户支付等多个操作需要作为一个事务来统一处理,以保证订单数据的完整性和业务流程的正确性。

    • 比如,当用户提交订单时,系统需要同时处理订单记录的创建、库存数量的减少以及支付信息的处理等操作,如果其中任何一个环节出现问题,整个订单处理事务就应该回滚,以避免出现数据不一致的情况。

  • 数据更新与一致性维护

    • 当需要对多个相关联的数据表进行更新操作时,为了保证数据的一致性和完整性,需要使用事务。

    • 例如,在一个企业资源管理系统中,当修改员工所属部门时,不仅要更新员工表中的部门信息,还要同时更新与该部门相关的其他数据表,如部门预算表、部门人员统计表等,这些操作需要在一个事务中完成,以确保系统中各数据的一致性。

八、锁(Lock)(了解)

特性

在 MySQL 8 中,锁是用于控制多个并发事务对数据库资源访问的一种机制,它可以确保数据的一致性和完整性,防止数据被错误地修改或读取。

锁的类型

  • 按锁的粒度划分

    • 表锁(Table Lock)

      • 表锁是对整个数据表加锁,它锁住的是整个表资源。当一个事务对表加了表锁后,其他事务对该表的读写操作都将被阻塞,直到锁被释放。

      • 例如,在执行一些对整张表进行操作的语句,如 ALTER TABLEDROP TABLE 时,MySQL 会自动对表加上表锁。同时,也可以通过 LOCK TABLES 语句显式地对表加表锁,如 LOCK TABLES my_table READ; 表示对 my_table 表加读表锁,只允许对该表进行读操作;LOCK TABLES my_table WRITE; 表示对 my_table 表加写表锁,不允许其他事务对该表进行读写操作。

    • 行锁(Row Lock)

      • 行锁是对表中的某一行数据加锁,它的粒度更细,只锁住被操作的行数据。这样可以在并发访问时,允许不同事务对同一表中的不同行进行并发操作,提高了数据库的并发性能。

      • 例如,在使用 SELECT... FOR UPDATEUPDATEDELETE 等语句操作数据时,MySQL 会自动对涉及的行加上行锁。假设在一个员工信息表中,事务 A 要更新某一行员工的工资信息,那么它会对该行加上行锁,此时其他事务如果也要更新该行数据或者对该行进行与锁类型不兼容的操作,就需要等待事务 A 释放行锁。

    • 间隙锁(Gap Lock)

      • 间隙锁是一种特殊的锁,它锁定的是索引记录之间的间隙,而不是具体的某一行数据。间隙锁主要用于防止在并发操作时,其他事务在锁定的间隙中插入新的数据,从而导致幻读问题。

      • 例如,在一个有序的整数列索引的表中,如果事务 A 对某一区间的索引值加了间隙锁,那么其他事务就不能在这个区间内插入新的数据,即使这些新数据与已存在的数据行并不冲突,但由于它们处于被锁定的间隙中,也无法插入。

  • 按锁的性质划分

    • 共享锁(Shared Lock)

      • 共享锁又称为读锁,多个事务可以同时对同一数据资源加上共享锁,以实现对数据的并发读操作。当一个数据资源上有共享锁时,其他事务可以继续对其加共享锁,但不能加排它锁,直到所有的共享锁都被释放。

      • 例如,使用 SELECT... LOCK IN SHARE MODE 语句对数据进行查询时,会对查询的结果集加上共享锁。这样可以保证多个事务在同一时间都能读取到相同的数据,而不会因为其他事务的写操作而导致数据不一致。

    • 排它锁(Exclusive Lock)

      • 排它锁又称为写锁,当一个事务对数据资源加上排它锁后,其他事务既不能对其加共享锁,也不能加排它锁,直到该排它锁被释放。排它锁用于对数据进行写操作时,保证只有一个事务能对数据进行修改。

      • 例如,在执行 UPDATEDELETESELECT... FOR UPDATE 等语句时,会对涉及的数据加上排它锁。如果事务 A 对某一行数据加上了排它锁,那么事务 B 想要对同一行数据进行读或写操作时,就需要等待事务 A 释放排它锁。

锁的使用场景

  • 并发控制

    • 在多用户并发访问数据库的环境中,为了避免数据的不一致性和冲突,需要使用锁来控制并发访问。例如,在一个在线票务系统中,多个用户可能同时尝试预订同一航班的机票,通过对机票库存表的行锁,可以确保每个用户的预订操作是原子性的,不会出现超售的情况。

  • 数据完整性维护

    • 锁可以防止在对数据进行修改时,其他事务的干扰,从而保证数据的完整性。比如,在一个银行账户管理系统中,当对一个账户进行取款操作时,通过对该账户所在行加排它锁,可以确保在取款操作完成之前,其他事务不能对该账户进行修改,避免了数据错误和不一致的情况。

  • 事务隔离实现

    • 不同的事务隔离级别在一定程度上是通过锁来实现的。例如,在可重复读隔离级别下,MySQL 使用行锁和间隙锁的组合来防止幻读问题,确保在同一事务中对同一数据的多次读取结果是一致的。

锁的并发问题及解决方法

  • 死锁

    • 问题描述:死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。例如,事务 A 对资源 1 加了锁,同时需要获取资源 2;而事务 B 对资源 2 加了锁,同时需要获取资源 1,这样就导致了两个事务互相等待,形成死锁。

    • 解决方法:MySQL 本身有一些机制来检测和处理死锁,当检测到死锁时,会自动选择一个事务进行回滚,以打破死锁状态。此外,在编写事务时,尽量按照相同的顺序获取锁,避免交叉加锁,也可以减少死锁的发生概率。

  • 锁等待超时

    • 问题描述:当一个事务等待获取锁的时间超过了系统设定的超时时间,就会出现锁等待超时的问题,事务会被自动回滚。这可能是由于并发事务过多,导致某些事务长时间等待获取锁而无法执行。

    • 解决方法:可以通过调整系统参数来增加锁等待超时的时间,但这并不是根本的解决方法。更好的方式是优化事务的执行逻辑,尽量减少事务的执行时间和锁的持有时间,提高系统的并发性能。

九、数据库引擎

类型

  1. InnoDB:支持事务处理和行级锁定,适用于高并发和多表关联查询的应用。

  2. MyISAM:不支持事务处理,适用于读密集型应用。

  3. Memory:将数据存储在内存中,适用于需要快速读写的临时数据和缓存。

  4. NDB (Cluster):支持分布式存储和高可用性,适用于大规模的分布式系统。

  5. Archive:只支持插入和压缩,适用于存储大量历史数据。

  6. CSV:将数据存储为CSV文件,适用于导入导出数据。

  7. Blackhole:从写入的数据中丢弃数据,适用于日志传输。

常用引擎概述

InnoDB
  • 存储结构

    • InnoDB 采用表空间的形式来存储数据,它将数据存储在一个或多个表空间文件中。表空间可以包含多个表的数据和索引,这种存储方式有利于数据的管理和维护。

    • 例如,一个数据库中的多个表可以共享同一个表空间,也可以为每个表指定单独的表空间,方便对不同表的数据进行独立的存储和管理。

  • 事务支持

    • InnoDB 是 MySQL 8 中默认的事务型数据库引擎,它提供了完整的 ACID 事务支持。通过使用事务日志和锁机制,确保了数据的一致性和完整性。

    • 比如,在银行转账业务中,从一个账户转出资金和转入另一个账户的操作可以放在一个 InnoDB 事务中,如果其中一个操作失败,整个事务可以回滚,保证资金数据的准确性。

  • 并发控制

    • 支持行级锁,通过对每行数据加锁,允许多个并发事务同时访问不同行的数据,提高了数据库的并发性能。同时,它还使用了多版本并发控制(MVCC)技术,减少了锁冲突,进一步提升了并发处理能力。

    • 例如,在一个电商系统中,多个用户同时对不同商品进行下单操作,InnoDB 可以通过行级锁和 MVCC 技术,确保每个用户的操作都能并发执行,而不会相互干扰。

  • 索引类型

    • 支持多种索引类型,包括 B + 树索引、全文索引等。B + 树索引是最常用的索引类型,它具有高效的查找性能,适合用于各种查询场景。

    • 例如,在一个包含大量文章的博客系统中,使用 B + 树索引可以快速地根据文章标题、作者等字段查找文章,提高系统的查询速度。

MyISAM
  • 存储结构

    • MyISAM 把数据和索引分别存储在不同的文件中,数据文件的扩展名为 .MYD,索引文件的扩展名为 .MYI。这种存储结构使得数据和索引的管理相对简单,但在某些情况下可能会导致性能问题。

    • 比如,当数据量较大时,频繁的查询可能需要同时读取数据文件和索引文件,增加了磁盘 I/O 操作,从而影响查询速度。

  • 事务支持

    • MyISAM 不支持事务,它不能保证数据的一致性和完整性在复杂的业务场景下。如果在执行一系列操作时中途出现错误,无法像 InnoDB 那样进行回滚操作。

    • 例如,在一个订单处理系统中,如果使用 MyISAM 引擎,当订单创建和库存更新操作同时进行时,若库存更新失败,无法自动回滚订单创建操作,可能会导致数据不一致。

  • 并发控制

    • 只支持表级锁,当一个事务对表进行操作时,会锁住整个表,这在并发度较高的场景下可能会导致性能瓶颈,因为其他事务需要等待锁的释放才能对该表进行操作。

    • 例如,在一个多用户的论坛系统中,如果使用 MyISAM 引擎,当一个用户在修改帖子时,其他用户对该帖子所在的表进行的任何操作都需要等待,降低了系统的并发性能。

  • 索引类型

    • 同样支持 B + 树索引,能够快速地根据索引字段查找数据。此外,还支持全文索引,适合用于对文本数据进行全文搜索的场景。

    • 例如,在一个新闻网站中,使用全文索引可以快速地搜索到包含特定关键词的新闻文章,提高搜索效率。

Memory
  • 存储结构

    • Memory 引擎把数据存储在内存中,数据的处理速度非常快,但由于内存的易失性,一旦服务器关闭或出现故障,数据就会丢失。

    • 例如,在一些需要临时存储数据且对性能要求极高的场景中,如缓存系统,可以使用 Memory 引擎存储临时数据,以提高数据的访问速度。

  • 事务支持

    • 一般情况下不支持完整的事务处理,虽然可以通过一些特殊的设置和技巧实现有限的事务功能,但不具备 InnoDB 那样完善的事务支持。

    • 比如,在一个简单的计数器应用中,使用 Memory 引擎存储计数数据,若不要求严格的事务处理,它可以快速地进行计数操作,但无法保证在复杂情况下数据的绝对准确。

  • 并发控制

    • 支持表级锁,并发性能相对较差,但由于数据存储在内存中,整体的读写速度较快,在一定程度上可以弥补并发性能的不足。

    • 例如,在一个小型的在线游戏中,用于存储游戏中的一些临时状态数据,虽然使用表级锁,但由于数据量小且存储在内存中,对游戏的实时性影响不大。

  • 索引类型

    • 支持哈希索引和 B + 树索引,哈希索引在等值查询时具有非常高的效率,适合用于快速查找特定值的场景。

    • 例如,在一个用户登录系统中,使用哈希索引可以快速地根据用户名查找用户密码,提高登录验证的速度。

查看

查看支持的引擎

SHOW ENGINES;

查看某个具体表所使用的数据库引擎

SHOW CREATE TABLE mytable;

########################
SELECT TABLE_SCHEMA AS Database, 

TABLE_NAME AS Table,

ENGINE 

FROM INFORMATION_SCHEMA.TABLES 

WHERE TABLE_SCHEMA = 'bank';

配置

在MySQL配置文件中(通常是my.cnf或my.ini),可以通过设置以下参数来配置引擎:

配置项 作用
default-storage-engine 指定默认的存储引擎。
innodb_buffer_pool_size 设置InnoDB的缓冲池大小。
key_buffer_size 设置MyISAM的索引缓冲区大小。
innodb_file_per_table 每个InnoDB表创建一个独立的表空间文件。
innodb_flush_log_at_trx_commit 设置InnoDB事务提交时的日志刷新策略。

常见配置字段:

缓冲池相关

  • innodb_buffer_pool_size:缓冲池大小,用于缓存表数据和索引,默认值根据服务器内存自动调整,通常是 128M 左右。合理设置可减少磁盘 I/O,提升性能,如可设置为服务器内存的 70%-80%(需根据实际情况调整),示例:innodb_buffer_pool_size = 4G

  • innodb_buffer_pool_instances:缓冲池实例数量,默认值是 8。将缓冲池划分为多个实例可以减少并发时的争用,提高性能,如设置为 16,innodb_buffer_pool_instances = 16

日志相关

  • innodb_log_file_size:重做日志文件大小,默认值通常是 48M ,增大可减少日志切换频率,适合写入频繁的数据库,如设置为innodb_log_file_size = 512M

  • innodb_log_files_in_group:重做日志文件组中的文件数量,默认值是 2。

  • innodb_log_buffer_size:重做日志缓冲区大小,默认值是 16M,用于临时存储重做日志,可根据写入负载调整,如设置为innodb_log_buffer_size = 64M

  • innodb_flush_log_at_trx_commit:控制重做日志写入磁盘的时机,默认值为 1,表示每次事务提交时都将日志缓冲区写入磁盘并刷新日志文件,以确保数据安全;若设为 0,表示每秒将日志缓冲区写入磁盘一次并刷新日志文件;设为 2,表示每次事务提交时将日志缓冲区写入磁盘,但不立即刷新,而是每秒刷新一次,示例:innodb_flush_log_at_trx_commit = 2

并发控制相关

  • innodb_thread_concurrency:限制并发线程数量,默认值为 0,表示不限制。可根据服务器 CPU 核心数设置,如设为 CPU 核心数的 1.5 至 2 倍,假设服务器有 8 个核心,可设置为innodb_thread_concurrency = 12

  • innodb_read_io_threads:InnoDB 读 I/O 线程数量,默认值是 4,可根据磁盘 I/O 性能调整。

  • innodb_write_io_threads:InnoDB 写 I/O 线程数量,默认值是 4,可根据磁盘 I/O 性能调整。

自增长列相关

  • innodb_autoinc_lock_mode:控制自增长列的锁模式,默认值为 2。0 是传统模式,1 是连续模式,2 是交错模式,交错模式可提高并发插入性能。

数据存储相关

  • innodb_file_per_table:设为 ON 时,每个 InnoDB 表使用独立的表空间文件,便于管理和维护,默认值为 ON 。

  • innodb_data_file_path:定义 InnoDB 系统表空间的数据文件路径和大小,默认值是根据安装时的配置而定。

脏页控制相关

  • innodb_max_dirty_pages_pct:指定缓冲池中脏页的最大百分比,默认值为 75,达到该比例时,InnoDB 会将脏页写入磁盘。

  • innodb_max_dirty_pages_pct_lwm:脏页比例低水位线,默认值为 0,表示不启用低水位线设置。

其他

  • innodb_lock_wait_timeout:InnoDB 事务等待锁的超时时间,默认值是 50 秒,超时后事务会自动回滚。

  • innodb_rollback_on_timeout:设为 ON 时,事务超时会自动回滚,默认值为 ON 。

  • innodb_stats_on_metadata:设为 ON 时,访问表的元数据时会更新 InnoDB 统计信息,默认值为 ON ,可设为 OFF 以减少元数据访问时的性能开销。

  • innodb_fast_shutdown:控制 InnoDB 引擎关闭的速度,默认值是 1,表示正常关闭;若设为 0,表示缓慢关闭,执行完整的清理操作;设为 2,表示快速关闭,不执行完整的清理操作,下次启动时会进行恢复操作。

使用场景

  • 网站博客:使用InnoDB作为默认引擎来存储用户数据、文章内容和评论。通过合理配置缓冲池大小和索引,提高读写性能和并发性。

  • 电子商务平台:使用InnoDB存储产品信息、订单信息和用户数据。通过设置合适的事务隔离级别和优化查询,确保数据的一致性和可靠性。

  • 数据分析平台:使用MyISAM或Memory引擎存储日志和临时数据,以提高查询和分析性能。通过合理的索引和分区策略,加速大规模数据的处理。

  • 大规模分布式系统:使用NDB(Cluster)引擎作为存储引擎,以实现高可用性和容错性。通过水平分片和分布式查询,扩展数据库的存储和计算能力。

十、慢日志

慢日志是用于记录执行时间超过指定阈值的 SQL 语句的日志文件 ,它对于数据库性能优化非常有帮助。

慢日志的作用
  • 性能分析与优化:通过慢日志,可以轻松找出执行时间较长的 SQL 语句,进而分析语句的执行计划,找出性能瓶颈,采取针对性的优化措施,如添加索引、优化查询语句结构、调整数据库参数等,以提高数据库的整体性能。

  • 资源监控与调整:帮助数据库管理员了解哪些 SQL 语句消耗了过多的系统资源,如 CPU 时间、I/O 等待等,从而合理分配系统资源,优化数据库服务器的配置。

  • 问题排查与追踪:当数据库出现性能问题时,慢日志可以作为重要的排查工具,协助定位问题的根源,判断是由于个别 SQL 语句执行缓慢导致的,还是整体系统负载过高引起的。

慢日志的配置参数
  • slow_query_log:用于开启或关闭慢日志功能。取值为ON表示开启,OFF表示关闭,默认值为OFF。可以在 MySQL 配置文件my.cnfmy.ini中设置,也可以通过SET GLOBAL slow_query_log = ON;语句动态开启。

  • slow_query_log_file:指定慢日志文件的存储路径和文件名。默认值因操作系统而异,例如在 Linux 上可能是/var/lib/mysql/hostname-slow.log,在 Windows 上可能是MySQL安装目录\Data\hostname-slow.log。可以在配置文件中通过slow_query_log_file = /path/to/slow.log来设置具体路径。

  • long_query_time:定义了 SQL 语句执行时间的阈值,单位是秒。当 SQL 语句的执行时间超过该阈值时,就会被记录到慢日志中。默认值是10.000000,即 10 秒,可以根据实际需求在配置文件中进行调整,如long_query_time = 2,表示将阈值设置为 2 秒。

  • log_queries_not_using_indexes:取值为ON时,不仅会记录执行时间超过阈值的 SQL 语句,还会记录没有使用索引的 SQL 语句,无论其执行时间是否超过阈值。默认值为OFF,可在配置文件中设置开启。

  • min_examined_row_count:指定了 SQL 语句至少需要检查的行数,只有当检查的行数超过该值且执行时间超过long_query_time时,才会被记录到慢日志中。默认值为0,即不考虑检查行数。

查看
SHOW VARIABLES LIKE '%slow_query%';
配置案例

使用配置文件设置

# 开启慢日志功能
slow_query_log = ON

# 设置慢日志文件路径和名称
slow_query_log_file = /var/log/mysql/mysql-slow.log

# 设置慢查询时间阈值为2秒
long_query_time = 2

# 记录没有使用索引的SQL语句
log_queries_not_using_indexes = ON

# 设置至少需要检查的行数为1000
min_examined_row_count = 1000

使用SQL语句动态设置

动态设置方式在 MySQL 服务重启后会恢复为默认值。

-- 开启慢日志功能
SET GLOBAL slow_query_log = ON;

-- 设置慢日志文件路径和名称
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';

-- 设置慢查询时间阈值为2秒
SET GLOBAL long_query_time = 2;

-- 记录没有使用索引的SQL语句
SET GLOBAL log_queries_not_using_indexes = ON;

-- 设置至少需要检查的行数为1000
SET GLOBAL min_examined_row_count = 1000;
慢日志的查看与分析
  • 查看慢日志内容:可以直接打开慢日志文件查看其中记录的 SQL 语句。在 MySQL 命令行中,也可以使用SHOW SLOW LOGS;命令查看慢日志的相关信息,如文件位置、是否开启等。

  • 分析慢日志工具:MySQL 提供了一些工具来帮助分析慢日志,如mysqldumpslow工具。它可以对慢日志进行排序、统计等操作,方便找出执行最频繁、执行时间最长的 SQL 语句。例如,mysqldumpslow -s t /path/to/slow.log可以按照执行时间对慢日志中的 SQL 语句进行排序并显示。

十一、重做日志(了解)

在 MySQL 8 中,重做日志(Redo Log)是事务日志的一种重要类型,用于在数据库发生故障时确保数据的持久性和一致性。以下为你详细介绍重做日志及其相关案例:

重做日志的基本概念

  • 记录内容:重做日志记录了数据库中所有对数据页的修改操作,包括插入、更新、删除等操作。例如,当执行一条UPDATE语句修改了某条记录时,该操作的细节就会被记录到重做日志中。

  • 工作原理:在事务执行过程中,MySQL 会先将对数据的修改操作记录到重做日志缓冲区,当事务提交时,再将缓冲区中的重做日志写入到磁盘上的重做日志文件。在数据库发生故障需要恢复时,MySQL 会根据重做日志中的记录重新执行这些修改操作,从而将数据库恢复到故障前的状态。

  • 主要作用

    • 数据持久性保障:即使在数据库系统崩溃或意外关机的情况下,只要重做日志已经写入磁盘,就可以在重启后根据重做日志恢复未完成的事务,确保已提交事务的数据修改不会丢失。

    • 提高系统性能:通过将事务的修改操作先记录到重做日志缓冲区,然后批量写入磁盘,减少了磁盘 I/O 次数,提高了数据库的整体性能。

重做日志的配置参数

  • innodb_log_file_size:指定每个重做日志文件的大小。默认值是 48MB,取值范围是 4MB 到 512GB。合理设置该参数可以平衡重做日志文件的切换频率和恢复时间。例如,如果设置得较小,重做日志文件切换会比较频繁,但恢复时需要处理的日志量相对较少;反之,如果设置得较大,切换频率降低,但恢复时可能需要处理较多的日志数据。

  • innodb_log_files_in_group:表示重做日志文件组中文件的数量,默认值是 2 。MySQL 以循环方式使用这些文件,当一个文件写满后,会自动切换到下一个文件继续写入。

  • innodb_log_buffer_size:定义了重做日志缓冲区的大小,默认值是 16MB,取值范围是 1MB 到 4GB。较大的缓冲区可以减少写入磁盘的次数,但也会占用更多的内存。

相关案例

案例一:设置合适的重做日志文件大小

假设一个业务繁忙的电商数据库,每天有大量的订单数据插入和更新操作。如果innodb_log_file_size设置得过小,比如默认的 48MB,可能会导致重做日志文件频繁切换,增加系统的 I/O 负担和性能开销。

为了优化性能,可以根据数据库的写入负载情况适当增大innodb_log_file_size的值。经过评估,将其设置为 512MB。这样可以减少重做日志文件的切换频率,提高系统的整体性能。在 MySQL 配置文件my.cnf中进行如下设置:

[mysqld]
innodb_log_file_size = 512M

案例二:调整重做日志缓冲区大小

对于一个数据分析应用,需要频繁地对大量数据进行批量更新操作。在这种情况下,如果innodb_log_buffer_size设置得过小,会导致重做日志频繁写入磁盘,影响性能。

通过监控发现,在数据批量更新时,重做日志缓冲区经常被写满。于是将innodb_log_buffer_size从默认的 16MB 增大到 64MB。在my.cnf文件中添加如下配置:

[mysqld]
innodb_log_buffer_size = 64M

增大缓冲区大小后,减少了重做日志写入磁盘的次数,显著提高了批量更新操作的性能 。

十二、常见问题(了解)

在数据库中,特别是在多用户环境下,常常需要处理由于并发事务引起的各种问题

更新丢失(Lost Update)

更新丢失指的是当两个或多个事务读取同一条记录,并基于此独立修改它时,最后一个写操作可能覆盖其他事务所做的修改,导致一些更新效果丢失。

案例

假设事务1和事务2都读取了账户123的余额(比如100)。

事务1将余额更新为150。

事务2不知道事务1的更新,也将余额更新为120。

如果事务2在事务1提交后提交,则事务1所做的更新将会丢失。

脏读(Dirty Read)

脏读发生在一个事务读取了另一个事务尚未提交的数据。如果这个数据被回滚了,那么读取到的数据就是无效的。

案例

事务1从账户123中扣除了100。

事务2读取账户123的余额,包括了未提交的更改。

若事务1回滚更改,则事务2读到的数据是“脏”的,因为它包含了不应该读到的、未提交的数据。

不可重复读(Nonrepeatable Read)

不可重复读是指在一个事务内部,多次读取同一数据集合时,由于其他事务的提交,后续读取的结果可能与前次不一致,即数据集合中的一些行被其他事务更新了。

案例

事务1读取账户123的余额。

事务2更新账户123的余额并提交。

事务1再次读取同一个账户的余额,发现数据发生了变化。

幻读(Phantom Read)

幻读跟不可重复读类似,但它指的是在事务中执行相同的查询时,新增或删除的记录导致读取到不同的行。它通常发生在范围查询中,当其它事务插入或者删除了符合该范围的行。

案例

事务1根据条件查询出了一批订单记录。

事务2插入了一条新的符合事务1查询条件的订单记录,并提交。

当事务1再次执行相同的查询时,会发现一个“幻影”记录——就是新插入的那条订单记录。

解决方法

为了处理这些并发问题,My提供了不同的事务隔离级别

  1. READ UNCOMMITTED(读未提交): 允许脏读,也就是可以读取尚未提交的事务数据。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  1. READ COMMITTED(读已提交): 保证一个事务不可以读取其他未提交事务的数据,从而防止脏读。但是不保证不可重复读。

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. REPEATABLE READ(可重复读): 防止脏读和不可重复读现象的发生,但可能出现幻读。这是MySQL的默认事务隔离级别。

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. SERIALIZABLE(可串行化): 最严格的隔离级别,事务依次顺序执行,可以看作没有并发。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

你可能感兴趣的:(oracle,数据库,sql)