MySQL~高级应用 + 优化。

MySQL 高级。


文章目录

    • MySQL 高级。
      • Linux 安装 MySQL。
          • MySQL 启动。
          • MySQL 密码。
          • 启动。
          • 修改密码。
          • 开启权限。
      • 索引。
          • 索引结构。
            • B-Tree。
            • HASH。
            • R-tree。
            • Full-Text。
          • B-tree。
          • B+ Tree。
            • MySQL 中的 B+ Tree。
          • 索引分类。
          • 索引语法。
            • 创建索引。
            • 查看索引。
            • 删除索引。
          • ALTER 命令。
          • 索引设计原则。
      • 视图。
          • 视图~what。
          • 创建、修改视图。
          • 查看视图。
          • 删除视图。
      • 存储过程和函数。
          • what。
          • 创建存储过程。
          • 调用存储过程~call。
          • 查看存储过程。
          • 删除存储过程。
          • 语法。
            • 变量。
            • + declare。
            • + set~为变量赋值。
            • 也可以通过 select ... into 方式赋值。
            • if 条件判断。
            • 传递参数。
            • + in~输入。
            • + out~输出。
            • case 结构。
            • while 循环。
            • repeat 循环。
            • loop 循环 & leave 语句。
          • 游标、光标。
          • 案例。
          • 循环。
          • 存储函数。
      • 触发器。
          • 简介。
          • 创建。
          • eg.
          • 删除。
          • 查看。
      • MySQL 体系结构。
      • 存储引擎。
          • 对比。
          • InnoDB。
            • 事务。
            • 外键。
          • MyISAM。
          • MEMORY。
          • MERGE。
          • MyISAM & MERGE。
          • 存储引擎的选择。
      • 优化 SQL 步骤。
          • 查看 SQL 执行频率。
          • 定位低效率的 SQL 语句。
            • 慢查询日志。
          • explain 分析执行计划。
            • id。
            • select_type。
            • table。
            • type。
          • key。
          • rows。
          • extra。
          • show profile 分析 sql。
          • trace 分析优化器执行计划。
      • 索引使用。
      • SQL 优化。
      • 应用优化。
      • 查询缓存优化。
      • 内存管理优化。
      • MySQL 锁问题。
      • 常用 SQL 技巧。
      • MySQL 常用工具。
      • MySQL 日志。
      • MySQL 主从复制。
      • 综合案例。


Linux 安装 MySQL。

  • 卸载旧版本。

查询现有 MySQL。

[root@192 ~]# rpm -qa | grep -i mysql

rpm -e --nodeps ...
  • 下载。

https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar

SecureCRT 使用 put + 文件全路径上传。

  • 解压。

tar -xvf mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar

mysql-community-client-5.7.29-1.el7.x86_64.rpm
mysql-community-common-5.7.29-1.el7.x86_64.rpm
mysql-community-devel-5.7.29-1.el7.x86_64.rpm
mysql-community-embedded-5.7.29-1.el7.x86_64.rpm
mysql-community-embedded-compat-5.7.29-1.el7.x86_64.rpm
mysql-community-embedded-devel-5.7.29-1.el7.x86_64.rpm
mysql-community-libs-5.7.29-1.el7.x86_64.rpm
mysql-community-libs-compat-5.7.29-1.el7.x86_64.rpm
mysql-community-server-5.7.29-1.el7.x86_64.rpm
mysql-community-test-5.7.29-1.el7.x86_64.rpm
  • 先安装依赖。
[root@192 mysql_geek]# rpm -ivh mysql-community-server-5.7.29-1.el7.x86_64.rpm 
warning: mysql-community-server-5.7.29-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
error: Failed dependencies:
	mysql-community-client(x86-64) >= 5.7.9 is needed by mysql-community-server-5.7.29-1.el7.x86_64
	mysql-community-common(x86-64) = 5.7.29-1.el7 is needed by mysql-community-server-5.7.29-1.el7.x86_64
	net-tools is needed by mysql-community-server-5.7.29-1.el7.x86_64
[root@192 mysql_geek]# rpm -ivh mysql-community-client-5.7.29-1.el7.x86_64.rpm 
warning: mysql-community-client-5.7.29-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
error: Failed dependencies:
	mysql-community-libs(x86-64) >= 5.7.9 is needed by mysql-community-client-5.7.29-1.el7.x86_64

安装 libs 时出现

[root@192 mysql_geek]# sudo rpm -ivh mysql-community-libs-5.7.29-1.el7.x86_64.rpm 
warning: mysql-community-libs-5.7.29-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
error: Failed dependencies:
	mysql-community-common(x86-64) >= 5.7.9 is needed by mysql-community-libs-5.7.29-1.el7.x86_64
	mariadb-libs is obsoleted by mysql-community-libs-5.7.29-1.el7.x86_64

yum remove mysql-libs

清除之前的依赖。

  • 安装 mysql-community-common。

  • 安装 mysql-community-libs。

server 又依赖 net-tools。

[root@192 mysql_geek]# rpm -ivh mysql-community-server-5.7.29-1.el7.x86_64.rpm 
warning: mysql-community-server-5.7.29-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
error: Failed dependencies:
	net-tools is needed by mysql-community-server-5.7.29-1.el7.x86_64

[root@192 mysql_geek]# yum install net-tools

综上。依赖顺序。

# yum remove mysql-libs
# rpm -ivh mysql-community-common-5.7.29-1.el7.x86_64.rpm 
# rpm -ivh mysql-community-libs-5.7.29-1.el7.x86_64.rpm 
# rpm -ivh mysql-community-client-5.7.29-1.el7.x86_64.rpm 
# rpm -ivh mysql-community-server-5.7.29-1.el7.x86_64.rpm 

MySQL 启动。

先查看 MySQL 状态。

CentOS 7。

[root@192 mysql_geek]# systemctl status mysqld

CentOS 6。

service mysql status

启动。

[root@192 ~]# service mysqld start
Redirecting to /bin/systemctl start mysqld.service


MySQL 密码。

一般安装时会打印安装日志信息。提示临时密码位置。

cat /root/.mysql_secret

如果没有就手动查看 MySQL 日志。

[root@192 ~]# cat /var/log/mysqld.log | grep password
2020-04-05T17:41:24.133433Z 1 [Note] A temporary password is generated for root@localhost: #we,eqv_=7PG
[root@192 ~]# cat /var/log/mysqld.log 
2020-04-05T17:41:23.298775Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2020-04-05T17:41:23.472998Z 0 [Warning] InnoDB: New log files created, LSN=45790
2020-04-05T17:41:23.497672Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2020-04-05T17:41:23.556554Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: aae18f2c-7764-11ea-a862-000c29a7c564.
2020-04-05T17:41:23.560085Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2020-04-05T17:41:23.959210Z 0 [Warning] CA certificate ca.pem is self signed.
2020-04-05T17:41:24.133433Z 1 [Note] A temporary password is generated for root@localhost: #we,eqv_=7PG
2020-04-05T17:41:26.405529Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2020-04-05T17:41:26.406978Z 0 [Note] /usr/sbin/mysqld (mysqld 5.7.29) starting as process 27976 ...
2020-04-05T17:41:26.410224Z 0 [Note] InnoDB: PUNCH HOLE support available
2020-04-05T17:41:26.410246Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2020-04-05T17:41:26.410249Z 0 [Note] InnoDB: Uses event mutexes
2020-04-05T17:41:26.410251Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used for memory barrier
2020-04-05T17:41:26.410254Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.11
2020-04-05T17:41:26.410258Z 0 [Note] InnoDB: Using Linux native AIO
2020-04-05T17:41:26.410574Z 0 [Note] InnoDB: Number of pools: 1
2020-04-05T17:41:26.410722Z 0 [Note] InnoDB: Using CPU crc32 instructions
2020-04-05T17:41:26.414264Z 0 [Note] InnoDB: Initializing buffer pool, total size = 128M, instances = 1, chunk size = 128M
2020-04-05T17:41:26.422756Z 0 [Note] InnoDB: Completed initialization of buffer pool
2020-04-05T17:41:26.432926Z 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread priority can be changed. See the man page of setpriority().
2020-04-05T17:41:26.445753Z 0 [Note] InnoDB: Highest supported file format is Barracuda.
2020-04-05T17:41:26.459487Z 0 [Note] InnoDB: Creating shared tablespace for temporary tables
2020-04-05T17:41:26.459586Z 0 [Note] InnoDB: Setting file './ibtmp1' size to 12 MB. Physically writing the file full; Please wait ...
2020-04-05T17:41:26.473366Z 0 [Note] InnoDB: File './ibtmp1' size is now 12 MB.
2020-04-05T17:41:26.474088Z 0 [Note] InnoDB: 96 redo rollback segment(s) found. 96 redo rollback segment(s) are active.
2020-04-05T17:41:26.474100Z 0 [Note] InnoDB: 32 non-redo rollback segment(s) are active.
2020-04-05T17:41:26.474266Z 0 [Note] InnoDB: Waiting for purge to start
2020-04-05T17:41:26.526026Z 0 [Note] InnoDB: 5.7.29 started; log sequence number 2630069
2020-04-05T17:41:26.526273Z 0 [Note] Plugin 'FEDERATED' is disabled.
2020-04-05T17:41:26.537792Z 0 [Note] Found ca.pem, server-cert.pem and server-key.pem in data directory. Trying to enable SSL support using them.
2020-04-05T17:41:26.537811Z 0 [Note] Skipping generation of SSL certificates as certificate files are present in data directory.
2020-04-05T17:41:26.538380Z 0 [Warning] CA certificate ca.pem is self signed.
2020-04-05T17:41:26.538422Z 0 [Note] Skipping generation of RSA key pair as key files are present in data directory.
2020-04-05T17:41:26.540445Z 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
2020-04-05T17:41:26.543385Z 0 [Note] InnoDB: Buffer pool(s) load completed at 200406  1:41:26
2020-04-05T17:41:26.544551Z 0 [Note] Server hostname (bind-address): '*'; port: 3306
2020-04-05T17:41:26.544792Z 0 [Note] IPv6 is available.
2020-04-05T17:41:26.544805Z 0 [Note]   - '::' resolves to '::';
2020-04-05T17:41:26.544881Z 0 [Note] Server socket created on IP: '::'.
2020-04-05T17:41:26.551461Z 0 [Note] Event Scheduler: Loaded 0 events
2020-04-05T17:41:26.551564Z 0 [Note] /usr/sbin/mysqld: ready for connections.
Version: '5.7.29'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MySQL Community Server (GPL)
2020-04-05T17:48:11.163779Z 2 [Note] Access denied for user 'root'@'localhost' (using password: NO)

启动。
[root@192 ~]# mysql -p #we,eqv_=7PG
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.29

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

修改密码。

第一次执行 sql,MySQL 会提示要先修改密码。

mysql> show databases;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> set password = password('root');
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
  • 此时想要远程登录(使用 MySQL Workbench 或 SQLyog),会失败。是因为 MySQL Server 没有开启远程权限。

开启权限。

'root' 处为你设置的账户名。
'密码' 处为你设置的密码。

mysql> grant all privileges on *.* to 'root'@'%' identified by '密码';
Query OK, 0 rows affected, 1 warning (0.00 sec)

使用 flush privileges;,应用设置。

mysql> flush privileges;
Query OK, 0 rows affected (0.02 sec)


索引。

在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的 SQL 语句执行得更快,可快速访问数据库表中的特定信息。
当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘 I/O 操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的 ROWID(相当于页码)快速找到表中对应的记录。
~百度百科。

索引(Index)是帮助 MySQL 高效获取数据的数据结构(有序)。在数据之外,数据库还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。

  • 优势。
  • 类似于书籍的目录索引,提高数据检索效率,降低数据库的 IO 成本。
  • 通索引对列数据进行排序,降低数据排序成本,降低 CPU 的消耗。
  • 劣势。

实际上索引也是一张表,该表中保存了主键与索引字段,并指向实体类在记录,所以索引列也是要占用空间的。
虽然索引大大提高了查询效率,同时却也降低了更新表的速度。eg. 对表进行 INSERT、DELETE、UPDATE。因为更新表时,MySQL 不仅要保存数据,还要保存索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

MySQL~高级应用 + 优化。_第1张图片

索引结构。

MySQL 常见 4 种索引。

B-Tree。

最常见的索引。大部分索引都支持 B 树索引。

HASH。

只有 Memory 引擎支持,使用场景简单。

R-tree。

空间索引是 MyISAM 引擎的一个特殊索引类型。主要用于地理空间数据类型,通常使用较少,不做特别介绍。

Full-Text。

全文索引也是 MyISAM 的一个特殊索引类型,主要用于全文检索,InnoDB 从 MySQL 5.6 版本开始支持全文索引。

索引 InnoDB 引擎 MyISAM 引擎 MEMORY 引擎
B-Tree
HASH × ×
R-tree × ×
Full-Text 5.6 版本以后支持。 ×

我们平常所说的索引,如果没有特别指明,都是指 B+ 树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引、复合索引、前缀索引、唯一索引默认都是使用 B+Tree 索引,统称为索引。


B-tree。

B-tree(多路搜索树,并不是二叉的)。

一棵 m 叉的 B-tree 特征如下。

  • 每个结点至多可以拥有 m 个子结点。
  • 各结点的关键字和可以拥有的子结点数都有限制。
    规定 m 阶 B-tree 中,根结点至少有 2 个子结点,除非根结点为叶子节点。
  • 所有的叶子节点都在同一层。
  • 每个非叶子节点由 n 个 key 与 n + 1 个指针组成,其中 [m/2]-1 <= n <= m-1。

以 5 叉 B-tree 为例。

m = 5。
2 <= n <= 4。
当 n > 4 时,中间节点分裂到父节点,两边节点分裂。

插入 C N G A H E K Q M F W L T Z D P R X Y S。演变过程如下。

MySQL~高级应用 + 优化。_第2张图片

和二叉树相比,B-tree 深度更小 ——> 效率高。


B+ Tree。

B+ Tree 是 B-Tree 的变种。

  • 区别。
    n 叉 B+ Tree 最多含有 n 个 key,而 B-Tree 最多含有 n-1 个 key。
    B+ Tree的叶子节点保存所有 key 信息,依 key 大小顺序排列。
    所有的非叶子节点都可以看作是 key 的索引部分。

MySQL~高级应用 + 优化。_第3张图片
由于 B+ Tree 只有叶子节点保存 key 信息,查询任何 key 都要从 root 走到叶子。所以 B+ Tree 的查询效率更稳定。


MySQL 中的 B+ Tree。

MySQL 索引数据结构对经典的 B+ Tree 进行了优化。在原 B+ Tree 的基础上,增加一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的 B+ Tree,提高区间访问的性能。MySQL~高级应用 + 优化。_第4张图片

索引分类。
  • 单值索引。

即一个索引只包含单个列,一个表可以有多个单值索引。

  • 唯一索引。

索引列的值必须唯一,但允许有空值。

  • 复合索引。

即一个索引包含多个列。


索引语法。

索引可以在创建表的时候创建,也可以随时增加新的索引。

  • 表准备。
CREATE SCHEMA `demo_01` DEFAULT CHARACTER SET utf8mb4 ;

use demo_01;

CREATE TABLE `demo_01`.`city` (
  `city_id` INT NOT NULL AUTO_INCREMENT,
  `city_name` VARCHAR(45) NOT NULL,
  `country_id` INT NULL,
  PRIMARY KEY (`city_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE `demo_01`.`country` (
  `country_id` INT NOT NULL AUTO_INCREMENT,
  `country_name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`country_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

INSERT INTO `demo_01`.`city` (`city_name`, `country_id`) VALUES ('西安', '1');
INSERT INTO `demo_01`.`city` (`city_name`, `country_id`) VALUES ('NewYork', '2');
INSERT INTO `demo_01`.`city` (`city_name`, `country_id`) VALUES ('北京', '1');
INSERT INTO `demo_01`.`city` (`city_name`, `country_id`) VALUES ('上海', '1');

INSERT INTO `demo_01`.`country` (`country_id`, `country_name`) VALUES ('1', 'China');
INSERT INTO `demo_01`.`country` (`country_id`, `country_name`) VALUES ('2', 'America');
INSERT INTO `demo_01`.`country` (`country_id`, `country_name`) VALUES ('3', 'Japan');
INSERT INTO `demo_01`.`country` (`country_id`, `country_name`) VALUES ('4', 'UK');


创建索引。
CREATE [UNIQUE | FULLTEXT | SPATIAL] index 'index_name' 
[USING index_type] 
ON table_name (index_col_name, ...)

eg. 为 city 表中的 city_name 字段创建索引。

ALTER TABLE `demo_01`.`city` 
ADD INDEX `idx_city_name` (`city_name` ASC);
;

create index idx_city_name on city(city_name);

查看索引。
show index from city;
mysql> show index from city;
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city  |          0 | PRIMARY       |            1 | city_id     | A         |           4 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | idx_city_name |            1 | city_name   | A         |           4 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)


show index from city \G;
mysql> show index from city\G;
*************************** 1. row ***************************
        Table: city
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: city_id
    Collation: A
  Cardinality: 4
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
*************************** 2. row ***************************
        Table: city
   Non_unique: 1
     Key_name: idx_city_name
 Seq_in_index: 1
  Column_name: city_name
    Collation: A
  Cardinality: 4
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
2 rows in set (0.00 sec)

ERROR: 
No query specified


删除索引。
ALTER TABLE `demo_01`.`city` 
DROP INDEX `idx_citi_name` ;
;

drop index idx_city_name on city;

ALTER 命令。
alter table -tb_name- add primary key (column_list);
-- 添加一个主键。索引值必须是唯一的,且不能为 NULL。

alter table -tb_name- add unique -index_name- (-column_list-);
-- 索引的值必须是唯一的(除 NULL 外。NULL 可能出现多次)。

alter table -tb_name- add index -index_name- (-column_list-);
-- 添加普通索引。索引值可出现多次。

alter table -tb_name- add fulltext -index_name- (column_list);
-- 指定索引为 FULLTEXT,用于全文检索。

索引设计原则。
  • 对查询频次较高,且数据量比较大的表建立索引。

  • 索引字段的选择。最佳获选列应当从 where 子句的条件中提取。如果 where 子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。

  • 尽量使用唯一索引,区分度越高,使用索引的效率越高。

  • 索引可以有效的提升查询数据的效率,但索引数量不是多多益善。索引越多,维护索引的代价自然也就水涨船高。对于插入、更新、删除等 DML 操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低 DML 操作的效率,增加相应的时间消耗。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的可以有效提升 MySQL 访问索引的 I/O 效率。

  • 利用最左前缀,N 个列组合而成的组合索引,那么相当于是创建了 N 个索引,如果查询时 where 子句中使用了组成该索引的前几个字段,那么这条查询 SQL 可以利用组合索引来提升查询效率。

创建复合索引。
CREATE INDEX idx_name_email_status ON tb_seller (name, email, status);
相当于
对 name 创建了索引。
对 name,email 创建了索引。
对 name,email,status 创建了索引。

最左索引:只要查询时包含了第一个字段,查询就会使用索引。


视图。

视图~what。

视图(View)是一种虚拟存在的表。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图就是一条 SELECT 语句执行后返回的结果集。所以我们在创建视图时,主要的工作就落在这条 SQL 查询语句上。

视图 VS 普通的表。

  • 简单。

使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件, 对用户来说已经是过滤好的复合条件的结果集。

  • 安全。

使用视图的用户只能访问他们被允许查询的结果集, 对表的权限管理并不能限制到某个行某个列, 但是通过视图就可以简单的实现。

  • 数据独立。

一旦视图的结构确定了, 可以屏蔽表结构变化对用户的影响, 源表增加列对视图没有影响;源表修改列名, 则可以过修改视图来解决, 不会造成对访问者的影响。


创建、修改视图。
CREATE
    [OR REPLACE]
    [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
    [DEFINER = user]
    [SQL SECURITY { DEFINER | INVOKER }]
    VIEW view_name [(column_list)]
    AS select_statement
    [WITH [CASCADED | LOCAL] CHECK OPTION]
SELECT 
    c.*, t.country_name
FROM
    city c,
    country t
WHERE
    c.country_id = t.country_id

麻烦 ——> 使用视图。

  • 创建视图。
CREATE VIEW `view_city_country` AS
    SELECT 
        c.*, t.country_name
    FROM
        city c,
        country t
    WHERE
        c.country_id = t.country_id;


  • 使用(虚拟的表)。
SELECT 
    *
FROM
    view_city_country;
  • 修改视图。
update view_city_country set city_name = '西安市' where city_id = 1;

更新的是视图中封装的基表数据。

ALTER [algorithm = {UNDEFINED | MERGE | TEMPLATE}]
VIEW -view_name- [(column_list)]
AS select_statement
[with| CASCADED | LOCAL] CHECK OPTION]

选项。

WITH [CASCADED | LOCAL] CHECK OPTION 决定了是否允许更新数据使记录不再满足视图的条件。

LOCAL:只要满足本视图的条件就可以更新。
CASCADED:必须满足所有针对该视图的所有视图条件才可以更新。默认。

-- 更新的是原表数据。
UPDATE view_city_country 
SET 
    city_name = '西安市'
WHERE
    city_id = 1;

视图是用来简化查询操作的,不建议更新。


查看视图。

从 MySQL 5.1 开始,使用 SHOW TABLES 命令的时候不仅显示表的名称,同时也会显示视图的名字,而不存在单独显示视图 SHOW VIEWS 命令。

mysql> show tables;
+-------------------+
| Tables_in_demo_01 |
+-------------------+
| city              |
| country           |
| view_city_country |
+-------------------+
3 rows in set (0.00 sec)

mysql> show views;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'views' at line 1
mysql> 

同样,在使用 SHOW TABLE STATUS 命令的时候,不但可以显示表的信息,同时也可以显示视图的信息。

mysql> show table status \G;
*************************** 1. row ***************************
           Name: city
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 4
 Avg_row_length: 4096
    Data_length: 16384
Max_data_length: 0
   Index_length: 32768
      Data_free: 0
 Auto_increment: 5
    Create_time: 2020-04-06 09:51:14
    Update_time: 2020-04-06 12:38:03
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 
*************************** 2. row ***************************
           Name: country
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 4
 Avg_row_length: 4096
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 5
    Create_time: 2020-04-06 09:01:28
    Update_time: 2020-04-06 09:03:31
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 
*************************** 3. row ***************************
           Name: view_city_country
         Engine: NULL
        Version: NULL
     Row_format: NULL
           Rows: NULL
 Avg_row_length: NULL
    Data_length: NULL
Max_data_length: NULL
   Index_length: NULL
      Data_free: NULL
 Auto_increment: NULL
    Create_time: NULL
    Update_time: NULL
     Check_time: NULL
      Collation: NULL
       Checksum: NULL
 Create_options: NULL
        Comment: VIEW
3 rows in set (0.00 sec)

ERROR: 
No query specified

mysql> show create view view_city_country;
+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+
| View              | Create View                                                                                                                                                                                                                                                                                                            | character_set_client | collation_connection |
+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+
| view_city_country | CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `view_city_country` AS select `c`.`city_id` AS `city_id`,`c`.`city_name` AS `city_name`,`c`.`country_id` AS `country_id`,`t`.`country_name` AS `country_name` from (`city` `c` join `country` `t`) where (`c`.`country_id` = `t`.`country_id`) | utf8mb4              | utf8mb4_general_ci   |
+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+----------------------+
1 row in set (0.00 sec)


删除视图。
DROP VIEW [IF EXISTS] view_name [, view_name] ... [RESTRICT | CASCADE]
DROP VIEW city_country_view;

存储过程和函数。

what。

存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的 SQL 语句集,它存储在数据库中,一次编译后永久有效,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。在数据量特别庞大的情况下利用存储过程能达到倍速的效率提升。
~ 百科。

存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,较少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程和函数的区别在于函数必须有返回值,而存储过程没有。

  • 函数:是一个有返回值的过程。
  • 过程:是一个没有返回值的函数。

创建存储过程。
CREATE PROCEDURE procedure_name ([proc_parameter[, ...]])
begin
	-- SQL 语句。
end;
mysql> delimiter //

mysql> CREATE PROCEDURE citycount (IN country CHAR(3), OUT cities INT)
       BEGIN
         SELECT COUNT(*) INTO cities FROM world.city
         WHERE CountryCode = country;
       END//
Query OK, 0 rows affected (0.01 sec)

eg.

mysql> create procedure pro_test01()
    -> begin
    -> select "hello mysql";
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 3

还未写完就报错,是因为 MySQL 中,; 是分隔符,表示语句写完了。
要先将分隔符设置为非 ;

delimiter $

mysql> create procedure pro_test01() 
    -> begin
    -> select 'hello mysql';
    -> end$
Query OK, 0 rows affected (0.00 sec)

The example uses the mysql client delimiter command to change the statement delimiter from ; to // while the procedure is being defined. This enables the ; delimiter used in the procedure body to be passed through to the server rather than being interpreted by mysql itself. See Section 24.1, “Defining Stored Programs”.


调用存储过程~call。
mysql> call pro_test01();
    -> $
+-------------+
| hello mysql |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)


查看存储过程。
-- 查询 db_name 数据库中所有的存储过程。
mysql> select name from mysql.proc where db='demo_01'$
+------------+
| name       |
+------------+
| pro_test01 |
+------------+
1 row in set (0.00 sec)
-- 查询存储过程的状态信息。
show procedure status;
-- 查询某个存储过程的定义。
mysql> show create procedure pro_test01 \G;
*************************** 1. row ***************************
           Procedure: pro_test01
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `pro_test01`()
begin
select 'hello mysql';
end
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8mb4_general_ci
1 row in set (0.00 sec)


删除存储过程。
DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name

语法。
变量。
  • + declare。

通过 declare 可以定义一个局部变量,该变量的作用范围只能在 BEGIN…END 块中。

DECLARE var_name [, var_name] … type [DEFAULT value]

eg.

delimiter $

create procedure pro_test2()
begin
declare num int default 5;
SELECT CONCAT('num 的值为:', num);
end$

delimiter ;


call pro_test2()
  • + set~为变量赋值。
delimiter $

create procedure pro_test3()
begin
declare num int default 0;
set num = num + 10;
SELECT CONCAT('num 的值为:', num);
end$

delimiter ;

call pro_test3()

~

num 的值为:10
也可以通过 select … into 方式赋值。
delimiter $

create procedure pro_test4()
begin
declare countnum int;
SELECT 
    COUNT(*)
INTO countnum FROM
    city;
SELECT CONCAT('city 表中的记录数为:', countnum);
end$

delimiter ;

call pro_test4()

if 条件判断。
IF search_condition THEN statement_list
    [ELSEIF search_condition THEN statement_list] ...
    [ELSE statement_list]
END IF

需求。

根据定义的身高变量,判定当前身高的所属的身材类型。

180 及以上 ~~ 身材高挑。
170 ~ 180 ~~ 标准身材。
170 以下 ~~ 一般身材。

delimiter $

create procedure testIF()
begin

declare height int default 175;
declare description varchar(50) default '';

if height > 180 then set description = "身材高挑";

elseif height >= 170 then set description = "标准身材";

else set description = '一般身材';

end if;

SELECT 
    CONCAT('身高',
            height,
            '对应的身材类型为:',
            description);

end$

delimiter ;

call testIF();

传递参数。
create procedure procedure_name ([in / out / inout] 参数名 参数类型)
  • in:输入。需要调用方传入值。默认。
  • out:输出。该参数作为返回值。
  • inout:既可以作为输入参数,又可以作为输出参数。
+ in~输入。

需求。

根据定义的身高变量,判定当前身高的所属的身材类型。

delimiter $

create procedure testIN(IN height INT)
begin
 
declare description varchar(50) default '';

if height > 180 then set description = "身材高挑";

elseif height >= 170 then set description = "标准身材";

else set description = '一般身材';

end if;

SELECT 
    CONCAT('身高',
            height,
            '对应的身材类型为:',
            description);

end$

delimiter ;

call testIN(188);

+ out~输出。

需求。

根据定义的身高变量,获取当前身高的所属的身材类型(返回值)。

delimiter $

create procedure testOUT(IN height INT, out description varchar(10))
begin
 
if height > 180 then set description = "身材高挑";

elseif height >= 170 then set description = "标准身材";

else set description = '一般身材';

end if;

SELECT 
    CONCAT('身高',
            height,
            ' ,对应的身材类型为:',
            description);

end$

delimiter ;

call testOUT(188, @description);-- @ ——> 用户会话变量。

SELECT @description;

@ ——> 用户会话变量。

@@——> 系统变量。

mysql> set name = 'geek';
ERROR 1193 (HY000): Unknown system variable 'name'
mysql> set @name = 'geek';
Query OK, 0 rows affected (0.00 sec)

mysql> select @name;
+-------+
| @name |
+-------+
| geek  |
+-------+
1 row in set (0.00 sec)

mysql> 


case 结构。
CASE case_value
    WHEN when_value THEN statement_list
    [WHEN when_value THEN statement_list] ...
    [ELSE statement_list]
END CASE
CASE
    WHEN search_condition THEN statement_list
    [WHEN search_condition THEN statement_list] ...
    [ELSE statement_list]
END CASE
delimiter $

-- IN 默认,可以不写。
-- create procedure testCASE(IN mon INT)
create procedure testCASE(mon INT)
begin

declare result varchar(10);

case
	when mon >= 1 and mon <= 3 then
		set result = '第一季度';
    when mon >= 4 and mon <= 6 then 
		set result = '第二季度';
    when mon >= 7 and mon <= 9 then 
		set result = '第三季度';
    when mon >= 10 and mon <= 12 then 
		set result = '第四季度';
end case;
SELECT 
    CONCAT('传递的月份为:',
            mon,
            ',计算出的结果为:',
            result) AS content;
end$

delimiter ;

call testCASE(3);


while 循环。
[begin_label:] WHILE search_condition DO
    statement_list
END WHILE [end_label]
delimiter $

create procedure pro_testWHILE(n INT)
begin

declare total int default 0;
declare num int default 1;
while num <= n do
	set total = total + num;
    set num = num + 1;
end while;
SELECT total;
end$

delimiter ;

call pro_testWHILE(100);


repeat 循环。
[begin_label:] REPEAT
    statement_list
UNTIL search_condition
END REPEAT [end_label]
delimiter $

create procedure pro_testREPEAT(n INT)
begin

declare total int default 0;

repeat
	set total = total + n;
    set n = n - 1;
    until n = 0
end repeat;
SELECT total;
end$

delimiter ;

call pro_testREPEAT(100);


loop 循环 & leave 语句。
[begin_label:] LOOP
    statement_list
END LOOP [end_label]
LEAVE label
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN
      ITERATE label1;
    END IF;
    LEAVE label1;
  END LOOP label1;
  SET @x = p1;
END;
delimiter $

create procedure pro_testLOOP_LEAVE(n INT)
begin

declare total int default 0;

c: loop

	set total = total + n;
    set n = n - 1;
    if n <= 0 then
		leave c;
    end if;

end loop c;

SELECT total;

end$

delimiter ;

call pro_testLOOP_LEAVE(100);


游标、光标。

游标是用来存储查询结果集的数据类型,在存储过程和函数中可以使用光标对结果集进行循环处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE。

  • 声明。
DECLARE cursor_name CURSOR FOR select_statement
  • OPEN。
OPEN cursor_name
  • FETCH。
FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] ...
  • CLOSE。
CLOSE cursor_name

案例。
CREATE TABLE `demo_01`.`emp` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL COMMENT '姓名。',
  `age` INT(11) NULL COMMENT '年龄。',
  `salary` INT(11) NULL COMMENT '薪水。',
  PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

INSERT INTO `demo_01`.`emp` (`name`, `age`, `salary`) VALUES ('金毛狮王', '55', '3800');
INSERT INTO `demo_01`.`emp` (`name`, `age`, `salary`) VALUES ('白眉鹰王', '60', '4000');
INSERT INTO `demo_01`.`emp` (`name`, `age`, `salary`) VALUES ('青翼蝠王', '38', '2800');
INSERT INTO `demo_01`.`emp` (`name`, `age`, `salary`) VALUES ('紫衫龙王', '42', '1800');

USE `demo_01`;
DROP procedure IF EXISTS `new_procedure`;

DELIMITER $$
USE `demo_01`$$
CREATE PROCEDURE `new_procedure` ()
BEGIN
	declare e_id int(11);
    declare e_name varchar(50);
    declare e_age int(11);
    declare e_salary int(11);
	declare emp_result cursor for select * from emp;
    
    open emp_result;
    
    fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);

    fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);
    
    close emp_result;
    
END$$

DELIMITER ;


可能出现的问题。

Error Code: 1329 No data - zero rows fetched, selected, or processed

↓ ↓ ↓


循环。
CREATE DEFINER=`root`@`%` PROCEDURE `new_procedure`()
BEGIN
	declare e_id int(11);
    declare e_name varchar(50);
    declare e_age int(11);
    declare e_salary int(11);
    declare has_data int default 1;

	declare emp_result cursor for select * from emp;
    declare exit handler for not found set has_data = 0;

    open emp_result;

    fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);

    fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);
                fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);
                fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);
                fetch emp_result into e_id, e_name, e_age, e_salary;
SELECT 
    CONCAT('id = ',
            e_id,
            ', name = ',
            e_name,
            ', age = ',
            e_age,
            ', 薪资为:',
            e_salary);
    
    close emp_result;
    
END

存储函数。

存储函数有返回值。

存储过程没有返回值。但可以通过 IN OUT 输出结果。

CREATE [AGGREGATE] FUNCTION function_name
    RETURNS {STRING|INTEGER|REAL|DECIMAL}
    SONAME shared_library_name


CREATE FUNCTION `new_function` ()
RETURNS INTEGER
BEGIN

RETURN 1;
END

USE `demo_01`;
DROP function IF EXISTS `new_function`;

DELIMITER $$
USE `demo_01`$$
CREATE FUNCTION `new_function` (countryId INT)
RETURNS INT
BEGIN

	DECLARE cnum INT;
    
SELECT 
    COUNT(*)
INTO cnum FROM
    city
WHERE
    country_id = countryId;

RETURN cnum;
END$$

DELIMITER ;


  • 使用 select 调用。
select demo_01.new_function(1);

触发器。

简介。

触发器是与表有关的数据库对象,指在 insert / update / delete 之前或之后,触发并执行触发器中定义的 SQL 语句集合。触发器的这种特性可以协助应用在数据库端确保数庭的完整性,日志记录,数据校验等作。

使用别名 OLDNEW 来引用触发器中发生变的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

触发器类型 NEW 和 OLD 的使用
INSERT 型触发器 NEW 表示将要或者已经新增的数据
UPDATE 型触发器 OLD 表示修改之前的数据,NEW 表示将要或已经修改后的数据
DELETE 型触发器 OLD 表示将要或者已经删除的数据

创建。
CREATE
    [DEFINER = user]
    TRIGGER trigger_name
    trigger_time trigger_event
    ON tbl_name FOR EACH ROW
    [trigger_order]
    trigger_body

trigger_time: { BEFORE | AFTER }

trigger_event: { INSERT | UPDATE | DELETE }

trigger_order: { FOLLOWS | PRECEDES } other_trigger_name

eg.
CREATE TABLE `demo_01`.`emp_logs` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `operation` VARCHAR(20) NOT NULL COMMENT '操作类型。insert/update/delete。',
  `operation_time` DATETIME NOT NULL COMMENT '操作时间。',
  `operation_id` INT(11) NOT NULL COMMENT '操作表的 id。',
  `operation_params` VARCHAR(500) NULL COMMENT '操作参数。',
  PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

  • 创建 insert 类型的触发器,完成插入数据时的日志记录。
DROP TRIGGER IF EXISTS `demo_01`.`emp_AFTER_INSERT`;

DELIMITER $$
USE `demo_01`$$
CREATE DEFINER=`root`@`%` TRIGGER `demo_01`.`emp_AFTER_INSERT` AFTER INSERT ON `emp` FOR EACH ROW
BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'insert', now(), new.id, concat('插入后(id: ', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary, ')'));
END$$
DELIMITER ;

INSERT INTO `demo_01`.`emp` (`id`, `name`, `age`, `salary`) VALUES ('5', '光明左使', '30', '3500');

mysql> select * from emp_logs;
+----+-----------+---------------------+--------------+-----------------------------------------------------------+
| id | operation | operation_time      | operation_id | operation_params                                          |
+----+-----------+---------------------+--------------+-----------------------------------------------------------+
|  1 | insert    | 2020-06-27 13:14:32 |            5 | 插入后(id: 5name: 光明左使, age: 30, salary: 3500)        |
+----+-----------+---------------------+--------------+-----------------------------------------------------------+
1 row in set (0.00 sec)

  • 创建 update 类型的触发器,完成修改数据时的日志记录。
DROP TRIGGER IF EXISTS `demo_01`.`emp_AFTER_UPDATE`;

DELIMITER $$
USE `demo_01`$$
CREATE DEFINER = CURRENT_USER TRIGGER `demo_01`.`emp_AFTER_UPDATE` AFTER UPDATE ON `emp` FOR EACH ROW
BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'update', now(), new.id, concat('修改前(id: ', old.id, 'name: ', old.name, ', age: ', old.age, ', salary: ', old.salary, '),修改后(', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary));
END$$
DELIMITER ;

UPDATE `demo_01`.`emp` SET `age` = '39' WHERE (`id` = '3');
mysql> select * from emp_logs;
+----+-----------+---------------------+--------------+------------------------------------------------------------------------------------------------------------------------+
| id | operation | operation_time      | operation_id | operation_params                                                                                                       |
+----+-----------+---------------------+--------------+------------------------------------------------------------------------------------------------------------------------+
|  1 | insert    | 2020-06-27 13:14:32 |            5 | 插入后(id: 5name: 光明左使, age: 30, salary: 3500)                                                                     |
|  2 | update    | 2020-06-27 13:24:34 |            3 | 修改前(id: 3name: 青翼蝠王, age: 38, salary: 2800),修改后(3name: 青翼蝠王, age: 39, salary: 2800                   |
+----+-----------+---------------------+--------------+------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)


删除。
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

查看。
SHOW TRIGGERS
    [{FROM | IN} db_name]
    [LIKE 'pattern' | WHERE expr]
mysql> show triggers;
+------------------+--------+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+---------+----------------------+----------------------+--------------------+
| Trigger          | Event  | Table | Statement                                                                                                                                                                                                                                                                                                                                    | Timing | Created                | sql_mode                                                                                                                                  | Definer | character_set_client | collation_connection | Database Collation |
+------------------+--------+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+---------+----------------------+----------------------+--------------------+
| emp_AFTER_INSERT | INSERT | emp   | BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'insert', now(), new.id, concat('插入后(id: ', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary, ')'));
END                                                                                                 | AFTER  | 2020-06-27 13:14:05.60 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@%  | utf8mb4              | utf8mb4_general_ci   | utf8mb4_general_ci |
| emp_AFTER_UPDATE | UPDATE | emp   | BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'update', now(), new.id, concat('修改前(id: ', old.id, 'name: ', old.name, ', age: ', old.age, ', salary: ', old.salary, '),修改后(', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary));
END           | AFTER  | 2020-06-27 13:23:46.47 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@%  | utf8mb4              | utf8mb4_general_ci   | utf8mb4_general_ci |
+------------------+--------+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+---------+----------------------+----------------------+--------------------+
2 rows in set (0.00 sec)

mysql> 

mysql> show triggers\G;
*************************** 1. row ***************************
             Trigger: emp_AFTER_INSERT
               Event: INSERT
               Table: emp
           Statement: BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'insert', now(), new.id, concat('插入后(id: ', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary, ')'));
END
              Timing: AFTER
             Created: 2020-06-27 13:14:05.60
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
  Database Collation: utf8mb4_general_ci
*************************** 2. row ***************************
             Trigger: emp_AFTER_UPDATE
               Event: UPDATE
               Table: emp
           Statement: BEGIN
INSERT INTO emp_logs (id, operation, operation_time, operation_id, operation_params) values (NULL, 'update', now(), new.id, concat('修改前(id: ', old.id, 'name: ', old.name, ', age: ', old.age, ', salary: ', old.salary, '),修改后(', new.id, 'name: ', new.name, ', age: ', new.age, ', salary: ', new.salary));
END
              Timing: AFTER
             Created: 2020-06-27 13:23:46.47
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
             Definer: root@%
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
  Database Collation: utf8mb4_general_ci
2 rows in set (0.00 sec)

ERROR: 
No query specified

mysql> 


MySQL 体系结构。


存储引擎。

和大多数的数据库不同, MySQL 中有一个存储引擎的概念,针对不同的存储需求可以选择最优的存储引擎。

存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式。存储引擎是基于表的,而不是基于厍的。所以存储引擎也可被称为表类型。

Oracle、sqlServer 等数据库只有一种存储引。MySQL 提供了插件式的存储引擎架构。所以 MySQL 存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎。

MySQL 5.0 支持的存储引擎包含:lnnoDB、MylSAM、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED 等,其中 InnoDB 和 BDB 提供事务安全表,其他存储引擎是非事务安全表。

可以通过指定 show engines,来查询当前数据库支持的存储引擎。

mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

mysql> 


mysql> show variables like '%engine%';
+----------------------------------+--------+
| Variable_name                    | Value  |
+----------------------------------+--------+
| default_storage_engine           | InnoDB |
| default_tmp_storage_engine       | InnoDB |
| disabled_storage_engines         |        |
| internal_tmp_disk_storage_engine | InnoDB |
+----------------------------------+--------+
4 rows in set (0.02 sec)


对比。
特点 InnoDB MyISAM MEMORY MERGE NDB
存储限制 64 TB 没有
事务安全 支持
锁机制 支持(适合高并发) 表锁 表锁 表锁 行锁
B 树索引 支持 支持 支持 支持 支持
哈希索引 支持
全文索引 支持(5.6 版本以后) 支持
集群索引 支持
数据索引 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据可压缩 支持
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持

InnoDB。
事务。
CREATE SCHEMA `demo02` DEFAULT CHARACTER SET utf8 ;

CREATE TABLE `demo02`.`goods_innodb` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(20) NOT NULL,
  PRIMARY KEY (`id`));

INSERT INTO `demo02`.`goods_innodb` (`name`) VALUES ('Mate20');

默认自动提交。

如果显式 start transaction;

则必须手动提交 commit


外键。
CREATE TABLE `demo02`.`country_innodb` (
  `country_id` INT NOT NULL AUTO_INCREMENT,
  `country_name` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`country_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE `demo02`.`city_innodb` (
  `city_id` INT NOT NULL AUTO_INCREMENT,
  `city_name` VARCHAR(50) NOT NULL,
  `country_id` INT NOT NULL,
  PRIMARY KEY (`city_id`),
  INDEX `idx_fk_country_id` (`country_id` ASC),
  CONSTRAINT `fk_city_country`
    FOREIGN KEY (`country_id`)
    REFERENCES `demo02`.`country_innodb` (`country_id`)
    ON DELETE RESTRICT
    ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

mysql> desc country_innodb;
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| country_id   | int(11)      | NO   | PRI | NULL    | auto_increment |
| country_name | varchar(100) | NO   |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+
2 rows in set (0.02 sec)

mysql> desc city_innodb;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| city_id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| city_name  | varchar(50) | NO   |     | NULL    |                |
| country_id | int(11)     | NO   | MUL | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

INSERT INTO `demo02`.`country_innodb` (`country_name`) VALUES ('China');
INSERT INTO `demo02`.`country_innodb` (`country_name`) VALUES ('America');
INSERT INTO `demo02`.`country_innodb` (`country_name`) VALUES ('Japan');

INSERT INTO `demo02`.`city_innodb` (`city_name`, `country_id`) VALUES ('Xian', '1');
INSERT INTO `demo02`.`city_innodb` (`city_name`, `country_id`) VALUES ('NewYork', '2');
INSERT INTO `demo02`.`city_innodb` (`city_name`, `country_id`) VALUES ('BeiJing', '1');

  • ON DELETE RESTRICT
    删除主表数据时,如果有关联记录,不删除。
    ON UPDATE CASCADE
    更新主表时,如果子表有关联记录,更新子表记录。
Executing:
DELETE FROM `demo02`.`country_innodb` WHERE (`country_id` = '2');

ERROR 1451: 1451: Cannot delete or update a parent row: a foreign key constraint fails (`demo02`.`city_innodb`, CONSTRAINT `fk_city_country` FOREIGN KEY (`country_id`) REFERENCES `country_innodb` (`country_id`) ON UPDATE CASCADE)
SQL Statement:
DELETE FROM `demo02`.`country_innodb` WHERE (`country_id` = '2')


Operation failed: There was an error while applying the SQL script to the database.

UPDATE `demo02`.`country_innodb` SET `country_id` = '100' WHERE (`country_id` = '1');

MySQL~高级应用 + 优化。_第5张图片

[geek@192 ~]$ sudo ls /var/lib/mysql/
192-slow.log	 demo_01	 ibtmp1		     public_key.pem
auto.cnf	 demo02		 mysql		     sakila
ca-key.pem	 ib_buffer_pool  mysql.sock	     server-cert.pem
ca.pem		 ibdata1	 mysql.sock.lock     server-key.pem
client-cert.pem  ib_logfile0	 performance_schema  sys
client-key.pem	 ib_logfile1	 private_key.pem     testdb
[geek@192 ~]$ sudo ls -l /var/lib/mysql/demo02
total 344
-rw-r-----. 1 mysql mysql   8648 Jun 27 11:48 city_innodb.frm
-rw-r-----. 1 mysql mysql 114688 Jun 27 12:03 city_innodb.ibd
-rw-r-----. 1 mysql mysql   8618 Jun 27 11:43 country_innodb.frm
-rw-r-----. 1 mysql mysql  98304 Jun 27 12:03 country_innodb.ibd
-rw-r-----. 1 mysql mysql     61 Jun 27 11:38 db.opt
-rw-r-----. 1 mysql mysql   8586 Jun 27 11:39 goods_innodb.frm
-rw-r-----. 1 mysql mysql  98304 Jun 27 11:40 goods_innodb.ibd
[geek@192 ~]$ 

  • .frm ~ 表结构。
  • .ibd ~ 数据和索引。

MyISAM。
CREATE TABLE `demo02`.`goods_myisam` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(20) NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8;

start transaction; 后不提交也可以查询到。(没有事务)。

[geek@192 ~]$ sudo ls -l /var/lib/mysql/demo02
[sudo] password for geek: 
total 360
-rw-r-----. 1 mysql mysql   8648 Jun 27 11:48 city_innodb.frm
-rw-r-----. 1 mysql mysql 114688 Jun 27 12:03 city_innodb.ibd
-rw-r-----. 1 mysql mysql   8618 Jun 27 11:43 country_innodb.frm
-rw-r-----. 1 mysql mysql  98304 Jun 27 12:03 country_innodb.ibd
-rw-r-----. 1 mysql mysql     61 Jun 27 11:38 db.opt
-rw-r-----. 1 mysql mysql   8586 Jun 27 11:39 goods_innodb.frm
-rw-r-----. 1 mysql mysql  98304 Jun 27 11:40 goods_innodb.ibd
-rw-r-----. 1 mysql mysql   8586 Jun 27 12:18 goods_myisam.frm
-rw-r-----. 1 mysql mysql      0 Jun 27 12:18 goods_myisam.MYD
-rw-r-----. 1 mysql mysql   1024 Jun 27 12:18 goods_myisam.MYI
  • .MYD(MYData) ~ 表数据。
  • .MYI(MYIndex) ~ 索引。

MEMORY。

Memory 存储引擎将表的数据存放在内存中。每个 MEMORY 表实际对应一个磁盘文件,格式 .frm ,该文件中只存储表的结构,而其数据文件,都是存在内存中,这样有利于数据的快速处理,提高整个表的效率。MEMORY 类型的表访问非常地快,因为 ta 的数据是存放在内存中的,并且默认使用 HASH 索引。但是服务一旦关闭,表中的数据就会丢失。


MERGE。

MERGE 存储引擎是一组 MyISAM 表的组合,这些 MyISAM 表必须结构完全相同,MERGE 表本身并没有存储数据,对 MERGE 类型的表可以进行查询、更新、删除操作,这些操作实际上是对内部的 MylSAM 表进行的。

对于 MERGE 类型表的插入操作,是通过 INSERT-METHOD 子句定义插入的表,可以有3 个不同的值,使用 FIRST 或 LAST 值使得插入操作被相应地作用在第一或者最后一个表上,不定义这个子句或者定义为 NO,表示不能对这个 MERGE 表执行适入操作。

可以对 MERGE 表进行 DROP 操作,但是这个操作只是删除 MERGE 表的定义, 对内部的表是没有任何影响的。


MyISAM & MERGE。

创建三个测试表,前两个是 myisam 引擎,payment_2020, payment_2021。payment_all 是前两个表的 MERGE 表。

CREATE TABLE `demo_01`.`order_2020` (
  `order_id` INT NOT NULL,
  `order_money` DOUBLE(10,2) NULL,
  `order_address` VARCHAR(50) NULL,
  PRIMARY KEY (`order_id`))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8;

CREATE TABLE `demo_01`.`order_2021` (
  `order_id` INT NOT NULL,
  `order_money` DOUBLE(10,2) NULL,
  `order_address` VARCHAR(50) NULL,
  PRIMARY KEY (`order_id`))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8;

CREATE TABLE `demo_01`.`order_all` (
    `order_id` INT NOT NULL,
    `order_money` DOUBLE(10 , 2 ),
    `order_address` VARCHAR(50),
    PRIMARY KEY (`order_id`)
)  ENGINE=MERGE DEFAULT CHARACTER SET=UTF8 INSERT_METHOD=LAST UNION=( order_2020 , order_2021 );


存储引擎的选择。

在选择存储引擎时,应该根据应系统的特点选择台适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。以下是几种常用的存储引擎的使用环境。

  • lnnoDB:是 MySQL 的默认存储引擎,用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包含很多的更新、删除操作。 那么 InnoDB 存储引擎是比较台适的选择。 InnoDB 存储引擎除了有效的降低由于删除和更新导致的锁定,还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB 是最合适的选择。
  • MylSAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常台适的。
  • MEMORY:将所有数据保存在 RAM 中,在要快速定位记录和其他类似数据环境下,可以提供极快的访问。MEMORY 的缺陷就是对表的大小有限制。太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常停止后表中的数据是可以恢复的。MEMORY 表通常用于更新不太频繁的小表,用以快速得到访问结果。
  • MERGE:用于将一系列等同的 MylSAM 以逻辑方式组合在一起,并作为一个对象引用他们。MERGE 表的优点在于可以突破对单个从MyISAM 表的大小限制,并且通过将不同的表分布在多个磁盘上。可以有效的改善 MERGE 表的访问效率。这对于存储诸如数据仓储等 VLDB 环境十分合适。

优化 SQL 步骤。

在应用的的开发过程中,由于初期数据量小,开友人员写 SQL 语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多 SQL 语句开始逐渐显露出性能问题,对生产的影晌也越采越大。此时这些有问题的 SQL 语句就成为整个系统性能的瓶颈,因此我们必须要对 ta 们进行优化。

当面对一个有 SQL 性能问题的数据库时,我们应该从何处入手来进行系统的分析,使得能够尽快定位问题 SQL 并尽快解决问题。


查看 SQL 执行频率。

MySQL 客户端连接成功后,通过 SHOW [GLOBAL | SESSION] STATUS 命令可以提供务器状态信息。SHOW [GLOBAL | SESSION] STATUS 可以根需要加上参数 “session” 或者 “global” 来显示 session 级(当前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果。如果不写,默认使用的参数是 “session”。

下面的命令显示了当前 session 中所有统计参数的值。

mysql> show status like 'Com_______';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog    | 0     |
| Com_commit    | 0     |
| Com_delete    | 0     |
| Com_insert    | 0     |
| Com_repair    | 0     |
| Com_revoke    | 0     |
| Com_select    | 5     |
| Com_signal    | 0     |
| Com_update    | 0     |
| Com_xa_end    | 0     |
+---------------+-------+
10 rows in set (0.01 sec)

mysql> show global status like 'Com_______';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog    | 0     |
| Com_commit    | 10    |
| Com_delete    | 7     |
| Com_insert    | 26    |
| Com_repair    | 0     |
| Com_revoke    | 0     |
| Com_select    | 137   |
| Com_signal    | 0     |
| Com_update    | 12    |
| Com_xa_end    | 0     |
+---------------+-------+
10 rows in set (0.00 sec)

mysql> show global status like 'Innodb_rows_%';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Innodb_rows_deleted  | 4     |
| Innodb_rows_inserted | 1750  |
| Innodb_rows_read     | 1858  |
| Innodb_rows_updated  | 14    |
+----------------------+-------+
4 rows in set (0.00 sec)


定位低效率的 SQL 语句。
慢查询日志。

可以通过以下两种方式定位执行效率较低的 SQL 语句。

  • 慢查询日志:通过慢查询日志定位那些执行效率较低的 SQL 语句,用 --log-slow-queries[=file-name] 选项启动时,mysqld 写一个包含所有执行事件超过 long_query_time 秒的 SQL 语句的日志文件。
  • show processlist。慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用 show processlist 命令查看当前 MySQL 在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化。
mysql> show processlist;
+----+------+---------------------+---------+---------+------+----------+------------------+
| Id | User | Host                | db      | Command | Time | State    | Info             |
+----+------+---------------------+---------+---------+------+----------+------------------+
|  7 | root | localhost           | demo_01 | Query   |    0 | starting | show processlist |
| 10 | root | 192.168.142.1:49414 | demo_01 | Sleep   |  463 |          | NULL             |
| 11 | root | 192.168.142.1:49415 | demo_01 | Sleep   |  463 |          | NULL             |
+----+------+---------------------+---------+---------+------+----------+------------------+
3 rows in set (0.00 sec)

如果是慢查询,State 是 Sending data。


explain 分析执行计划。

通过以上步骤查词到效率低的 SQL 语句后,可以通过 EXPLAIN 或者 DES 命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

mysql> explain SELECT * FROM demo_01.emp;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | emp   | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    4 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

  • id。
    select 查询的序列号。是一组数字,表示的是查询中执行 select 子句或者是操作表的顺序。
  • select_type。
    表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查间,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等。
  • table。
    输出结果集的表。
  • type。
    表示表的连接类型,性能由好到差的连接类型为 system -> const -> eq_ref -> ref -> ref_or_null -> index_merge -> index_subquery -> range -> index -> all。
  • possible keys。
    查询时可能会使用的索引。
  • key。
    实际使用的索引。
  • key_len。
    索引字段的长度。
  • rows。
    扫描行的数量。
  • extra。
    执行情况的说明和描述。
  • table
    这一行的数据是关于哪张表的。
  • type
    这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为 const、eq-reg、ref、range、index 和 ALL。
  • posible_keys
    可能应用在这张表中的索引。如果为空,没有可能的索引。
  • key
    实际使用的索引。如果为 NULL,则没有使用索引。
  • key_len
    使用的索引的长度。在不损失精确性的情况下,长度越短越好。
  • ref
    显示索引的哪一列被使用了,如果可能的话,是一个常数。
  • rows
    MySql 认为必须检查的用来返回请求数据的行数。
CREATE TABLE `demo_01`.`t_role` (
    `id` VARCHAR(32) NOT NULL,
    `role_name` VARCHAR(255) DEFAULT NULL,
    `role_code` VARCHAR(255) DEFAULT NULL,
    `description` VARCHAR(255) DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `unique_role_name` (`role_name` ASC)
)  ENGINE=INNODB DEFAULT CHARACTER SET=UTF8;

CREATE TABLE `t_user` (
  `id` varchar(32) NOT NULL,
  `username` varchar(45) NOT NULL,
  `password` varchar(96) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `demo_01`.`user_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `user_id` VARCHAR(32) NULL DEFAULT NULL,
  `role_id` VARCHAR(32) NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_ur_role_id_idx` (`role_id` ASC),
  INDEX `fk_ur_user_id_idx` (`user_id` ASC),
  CONSTRAINT `fk_ur_role_id`
    FOREIGN KEY (`role_id`)
    REFERENCES `demo_01`.`t_role` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_ur_user_id`
    FOREIGN KEY (`user_id`)
    REFERENCES `demo_01`.`t_user` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('1', 'super', 'super', '超级管理员');
INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('2', 'admin', 'admin', '系统管理员');
INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('3', 'geek', 'geek', 'test');
INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('4', 'stu1', 'stu1', '学生1');
INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('5', 'stu2', 'stu2', '学生2');
INSERT INTO `demo_01`.`t_user` (`id`, `username`, `password`, `name`) VALUES ('6', 't1', 't1', '老师1');

INSERT INTO `demo_01`.`t_role` (`id`, `role_name`, `role_code`, `description`) VALUES ('5', '学生', 'student', '学生');
INSERT INTO `demo_01`.`t_role` (`id`, `role_name`, `role_code`, `description`) VALUES ('7', '老师', 'teacher', '老师');
INSERT INTO `demo_01`.`t_role` (`id`, `role_name`, `role_code`, `description`) VALUES ('8', '教学管理员', 'teachmanager', '教学管理员');
INSERT INTO `demo_01`.`t_role` (`id`, `role_name`, `role_code`, `description`) VALUES ('9', '管理员', 'admin', '管理员');
INSERT INTO `demo_01`.`t_role` (`id`, `role_name`, `role_code`, `description`) VALUES ('10', '超级管理员', 'super', '超级管理员');

INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('1', '5');
INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('1', '7');
INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('2', '8');
INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('3', '9');
INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('4', '8');
INSERT INTO `demo_01`.`user_role` (`user_id`, `role_id`) VALUES ('5', '10');

mysql> select * from t_user;
+----+----------+----------+-----------------+
| id | username | password | name            |
+----+----------+----------+-----------------+
| 1  | super    | super    | 超级管理员      |
| 2  | admin    | admin    | 系统管理员      |
| 3  | geek     | geek     | test            |
| 4  | stu1     | stu1     | 学生1           |
| 5  | stu2     | stu2     | 学生2           |
| 6  | t1       | t1       | 老师1           |
+----+----------+----------+-----------------+
6 rows in set (0.00 sec)

mysql> select * from t_role;
+----+-----------------+--------------+-----------------+
| id | role_name       | role_code    | description     |
+----+-----------------+--------------+-----------------+
| 10 | 超级管理员      | super        | 超级管理员      |
| 5  | 学生            | student      | 学生            |
| 7  | 老师            | teacher      | 老师            |
| 8  | 教学管理员      | teachmanager | 教学管理员      |
| 9  | 管理员          | admin        | 管理员          |
+----+-----------------+--------------+-----------------+
5 rows in set (0.00 sec)

mysql> select * from user_role;
+----+---------+---------+
| id | user_id | role_id |
+----+---------+---------+
|  1 | 1       | 5       |
|  2 | 1       | 7       |
|  3 | 2       | 8       |
|  4 | 3       | 9       |
|  5 | 4       | 8       |
|  6 | 5       | 10      |
+----+---------+---------+
6 rows in set (0.00 sec)

mysql> 



id。

id 字段是 select 查询的序列号。是一组数字,表示的是查询中执行 select 子句或者是操作表的顺序。

  1. id 相同表示加载表的顺序是从上到下。
mysql> explain select * from t_role r, t_user u, user_role ur where r.id = ur.role_id and u.id = ur.user_id;
+----+-------------+-------+------------+--------+-------------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                       | key     | key_len | ref                | rows | filtered | Extra                                              |
+----+-------------+-------+------------+--------+-------------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | r     | NULL       | ALL    | PRIMARY                             | NULL    | NULL    | NULL               |    5 |   100.00 | NULL                                               |
|  1 | SIMPLE      | ur    | NULL       | ALL    | fk_ur_role_id_idx,fk_ur_user_id_idx | NULL    | NULL    | NULL               |    6 |    20.00 | Using where; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | u     | NULL       | eq_ref | PRIMARY                             | PRIMARY | 98      | demo_01.ur.user_id |    1 |   100.00 | NULL                                               |
+----+-------------+-------+------------+--------+-------------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

  1. id 不同。id 值越大,优先级越高,越先被执行。
mysql> explain select * from t_role where id = (select role_id from user_role where user_id = (select id from t_user where username = 'stu1'));
+----+-------------+-----------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
| id | select_type | table     | partitions | type  | possible_keys        | key                  | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-----------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
|  1 | PRIMARY     | t_role    | NULL       | const | PRIMARY              | PRIMARY              | 98      | const |    1 |   100.00 | NULL        |
|  2 | SUBQUERY    | user_role | NULL       | ref   | fk_ur_user_id_idx    | fk_ur_user_id_idx    | 99      | const |    1 |   100.00 | Using where |
|  3 | SUBQUERY    | t_user    | NULL       | const | unique_user_username | unique_user_username | 137     | const |    1 |   100.00 | Using index |
+----+-------------+-----------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)

  1. id 有相同,也有不同,同时存在。id 相同的可以认为是一组,从上往下顺序执行。在所有的组中,id 的值越大,优先级越高,越先执行。
mysql> EXPLAIN SELECT * FROM t_role r, (SELECT * FROM user_role ur WHERE ur.user_id = '2') a WHERE r.id = a.role_id;
+----+-------------+-------+------------+--------+-------------------------------------+-------------------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys                       | key               | key_len | ref                | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+-------------------------------------+-------------------+---------+--------------------+------+----------+-------------+
|  1 | SIMPLE      | ur    | NULL       | ref    | fk_ur_role_id_idx,fk_ur_user_id_idx | fk_ur_user_id_idx | 99      | const              |    1 |   100.00 | Using where |
|  1 | SIMPLE      | r     | NULL       | eq_ref | PRIMARY                             | PRIMARY           | 98      | demo_01.ur.role_id |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+--------+-------------------------------------+-------------------+---------+--------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)


select_type。
  • simple。
    简单的 select 查询,查询中不包含子查询或 UNION。
  • primary。
    查询中若包含任何复杂的子查询,最外层查询标记为该标识。
  • subquery。
    在 select 或 where 列表中包含了子查询。
  • derived。
    在 from 列表中包含的子查询,被标记为 DERIVED。MySQL 会递归执行这些子查询,把结果放在临时表中。
  • union。
    若第二个 select 出现在 union 之后,则标记为 union。若 union 包含在 from 子句的子查询中,外层 select 将被标记为 derived。
  • union result。
    从 union 表获取结果的 select。
mysql> explain select * from t_user where id = (select id from user_role where role_id = 9);
+----+-------------+-----------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
| id | select_type | table     | partitions | type  | possible_keys     | key               | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-----------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
|  1 | PRIMARY     | t_user    | NULL       | ALL   | PRIMARY           | NULL              | NULL    | NULL |    6 |    16.67 | Using where              |
|  2 | SUBQUERY    | user_role | NULL       | index | fk_ur_role_id_idx | fk_ur_role_id_idx | 99      | NULL |    6 |    16.67 | Using where; Using index |
+----+-------------+-----------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
2 rows in set, 5 warnings (0.00 sec)

mysql> explain select a.* from (select * from t_user where id in ('1', '2'));
ERROR 1248 (42000): Every derived table must have its own alias
mysql> explain select a.* from (select * from t_user where id in ('1', '2')) a;
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | range | PRIMARY       | PRIMARY | 98      | NULL |    2 |   100.00 | Using where |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t_user where id = '1' union select * from t_user where id = '2';
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type  | table      | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra           |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
|  1 | PRIMARY      | t_user     | NULL       | const | PRIMARY       | PRIMARY | 98      | const |    1 |   100.00 | NULL            |
|  2 | UNION        | t_user     | NULL       | const | PRIMARY       | PRIMARY | 98      | const |    1 |   100.00 | NULL            |
| NULL | UNION RESULT | <union1,2> | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  | NULL |     NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)

table。

展示这一行数据是关于哪一张表的。


type。

type 显示的是访问类型,是较为重要的一个指标。

type 含义
NULL MySQL 不访问任何表、索引,直接返回结果。
system 表只有一行记录(等于系统表),这是 const 类型的特例。一般不会出现。
const 表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。const 于将“主键”或“唯一”索引的所有部分与常量进行比较。
eq-ref 类似 ref,区别在于使用的是唯一索引,使用主踺的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描。
ref 非唯一索引扫描。返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个)。
range 只检索给定返回的行,便用一个索引来选择行。where 之后出现 between,<,>,in 等操作。
index 与 ALL 的区别为 index 类型只是遍历了索引树,通常比 ALL 快,ALL 是遍历数据文件。
all 将遍历全表以找到匹配的行。

key。
  • possible_keys。
    显示可能应用在这张表的索引,一个或多个。

  • key。
    实际使用的索引,如果为 NULL,则没有用索引。

  • key_len。
    表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精度的前提下,长度越短越好。


rows。

扫描行的数量。

如果使用了索引,值扫描一行。否则全部行扫描。


extra。

其他额外的执行计划信息。在该列展示。

extra 含义
using filesort MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,称为“文件排序”,效率低。
using temporary 使用了临时表保存中间结果。MySQL 在对查间结果排序时使用临时表。常见于 order by 和 group by,效率低。
using index 表示相应的 select 操作使用了覆盖索引,避免访问表的数据行,效率不错。
mysql> explain select * from t_user order by username;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using filesort |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select username from t_user order by username;
+----+-------------+--------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type  | possible_keys | key                  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | index | NULL          | unique_user_username | 137     | NULL |    6 |   100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)


show profile 分析 sql。
  • 查看是否支持。
mysql> select @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES              |
+------------------+
1 row in set, 1 warning (0.00 sec)

  • 开启。1 开启。0 关闭。
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> set profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           1 |
+-------------+
1 row in set, 1 warning (0.00 sec)

查看 sql 耗时。

mysql> show profiles;
+----------+------------+----------------------------------+
| Query_ID | Duration   | Query                            |
+----------+------------+----------------------------------+
|        1 | 0.00024250 | select @@profiling               |
|        2 | 0.00014950 | SELECT DATABASE()                |
|        3 | 0.00023050 | show databases                   |
|        4 | 0.00010450 | show tables                      |
|        5 | 0.00025275 | select * from t_user             |
|        6 | 0.00016300 | select count(*) from t_user      |
|        7 | 0.00023950 | show tables                      |
|        8 | 0.00028950 | select count(*) from city_innodb |
+----------+------------+----------------------------------+
8 rows in set, 1 warning (0.00 sec)

  • 进一步详细耗时。
mysql> show profile for query 8;
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000055 |
| checking permissions | 0.000005 |
| Opening tables       | 0.000016 |
| init                 | 0.000044 |
| System lock          | 0.000009 |
| optimizing           | 0.000004 |
| statistics           | 0.000011 |
| preparing            | 0.000009 |
| executing            | 0.000002 |
| Sending data         | 0.000101 |
| end                  | 0.000006 |
| query end            | 0.000006 |
| closing tables       | 0.000005 |
| freeing items        | 0.000009 |
| cleaning up          | 0.000010 |
+----------------------+----------+
15 rows in set, 1 warning (0.00 sec)
mysql> show profile all for query 8;
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
| Status               | Duration | CPU_user | CPU_system | Context_voluntary | Context_involuntary | Block_ops_in | Block_ops_out | Messages_sent | Messages_received | Page_faults_major | Page_faults_minor | Swaps | Source_function       | Source_file          | Source_line |
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
| starting             | 0.000055 | 0.000015 |   0.000031 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | NULL                  | NULL                 |        NULL |
| checking permissions | 0.000005 | 0.000001 |   0.000003 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | check_access          | sql_authorization.cc |         809 |
| Opening tables       | 0.000016 | 0.000005 |   0.000010 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | open_tables           | sql_base.cc          |        5781 |
| init                 | 0.000044 | 0.000014 |   0.000027 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | handle_query          | sql_select.cc        |         128 |
| System lock          | 0.000009 | 0.000002 |   0.000005 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_lock_tables     | lock.cc              |         330 |
| optimizing           | 0.000004 | 0.000001 |   0.000002 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         158 |
| statistics           | 0.000011 | 0.000004 |   0.000007 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         374 |
| preparing            | 0.000009 | 0.000003 |   0.000005 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         482 |
| executing            | 0.000002 | 0.000000 |   0.000001 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         126 |
| Sending data         | 0.000101 | 0.000032 |   0.000063 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         202 |
| end                  | 0.000006 | 0.000001 |   0.000003 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | handle_query          | sql_select.cc        |         206 |
| query end            | 0.000006 | 0.000002 |   0.000003 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc         |        4956 |
| closing tables       | 0.000005 | 0.000001 |   0.000003 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc         |        5009 |
| freeing items        | 0.000009 | 0.000003 |   0.000006 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_parse           | sql_parse.cc         |        5622 |
| cleaning up          | 0.000010 | 0.000003 |   0.000006 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | dispatch_command      | sql_parse.cc         |        1931 |
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
15 rows in set, 1 warning (0.00 sec)



trace 分析优化器执行计划。

MySQL 5.6 提供了对 SQL 的跟踪 trace,通过 trace 文件能够进一步了解为什么优化器选择 A 计划,而不是选择 B 计划。

打开 trace 设置格式为 JSON,并设置 trace 最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

SET optimizer_trace = “enabled=on”, end_markers_in_json = on;
SET optimizer_trace_max_mem_size=1000000;

mysql> SET optimizer_trace = "enabled=on", end_markers_in_json = on;
Query OK, 0 rows affected (0.00 sec)

mysql> SET optimizer_trace_max_mem_size=1000000;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from city_innodb;
+---------+-----------+------------+
| city_id | city_name | country_id |
+---------+-----------+------------+
|       1 | Xian      |        100 |
|       2 | NewYork   |          2 |
|       3 | BeiJing   |        100 |
+---------+-----------+------------+
3 rows in set (0.00 sec)

mysql> select * from information_schema.optimizer_trace\G;
*************************** 1. row ***************************
                            QUERY: select * from city_innodb
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `city_innodb`.`city_id` AS `city_id`,`city_innodb`.`city_name` AS `city_name`,`city_innodb`.`country_id` AS `country_id` from `city_innodb`"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "table_dependencies": [
              {
                "table": "`city_innodb`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "rows_estimation": [
              {
                "table": "`city_innodb`",
                "table_scan": {
                  "rows": 3,
                  "cost": 1
                } /* table_scan */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`city_innodb`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 3,
                      "access_type": "scan",
                      "resulting_rows": 3,
                      "cost": 1.6,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 3,
                "cost_for_plan": 1.6,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": null,
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`city_innodb`",
                  "attached": null
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "refine_plan": [
              {
                "table": "`city_innodb`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.01 sec)

ERROR: 
No query specified

mysql> 



索引使用。


SQL 优化。


应用优化。


查询缓存优化。


内存管理优化。


MySQL 锁问题。


常用 SQL 技巧。


MySQL 常用工具。


MySQL 日志。


MySQL 主从复制。


综合案例。

你可能感兴趣的:(MySQL,MySQL)