MySQL 学习笔记(二)

  • MySQL 学习笔记(一)
  • MySQL 学习笔记(二)
  • MySQL - 索引
  • MySQL 学习笔记(三)

别名

当表名或字段名比较长且需要重复使用时,可以为它们取一个别名,简化调用。其中:

  • 为表取别名:其语法如下:
    表名 [AS] 别名
    
  • 为字段取别名:其语法如下:
    字段名 [AS] 别名
    

数据操作

创建完数据表后,就可以往表里面添加具体的实体数据。下面介绍对实体数据的一些基本操作。

  • :为数据表插入数据,使用的是INSERT命令。其语法如下所示:

    INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
        [INTO] tbl_name
        [PARTITION (partition_name [, partition_name] ...)]
        [(col_name [, col_name] ...)]
        { {VALUES | VALUE} (value_list) [, (value_list)] ...
          |
          VALUES row_constructor_list
        }
        [AS row_alias[(col_alias [, col_alias] ...)]]
        [ON DUPLICATE KEY UPDATE assignment_list]
    
    INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
        [INTO] tbl_name
        [PARTITION (partition_name [, partition_name] ...)]
        [AS row_alias[(col_alias [, col_alias] ...)]]
        SET assignment_list
        [ON DUPLICATE KEY UPDATE assignment_list]
    
    INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
        [INTO] tbl_name
        [PARTITION (partition_name [, partition_name] ...)]
        [(col_name [, col_name] ...)]
        [AS row_alias[(col_alias [, col_alias] ...)]]
        {SELECT ... | TABLE table_name}
        [ON DUPLICATE KEY UPDATE assignment_list]
    
    value:
        {expr | DEFAULT}
    
    value_list:
        value [, value] ...
    
    row_constructor_list:
        ROW(value_list)[, ROW(value_list)][, ...]
    
    assignment:
        col_name = [row_alias.]value
    
    assignment_list:
        assignment [, assignment] ...
    

    INSERT命令涉及的选项非常多,但是其最常用的格式却简单许多,如下所示:

    INSERT [INTO] table_name (column_list) VALUES (value_list);
    

    其中:

    • table_name:表示要插入数据的表名。
    • column_list:表示要插入数据的列,多个列之间使用逗号(,)进行分隔。
    • value_list:表示要插入的具体数据。

    举个例子:创建一张表tmp,并为其添加新数据:

    mysql> CREATE TABLE tmp (                                           # 建表
        -> id INT PRIMARY KEY AUTO_INCREMENT,
        -> data VARCHAR(20)
        -> );
    Query OK, 0 rows affected (1.10 sec)
    
    mysql> INSERT INTO tmp VALUES(10,'first data');                     # 全量插入(不指定列)
    Query OK, 1 row affected (0.19 sec)
    
    mysql> INSERT INTO tmp(data) VALUES ('second data');                # 局部插入(指定列)
    Query OK, 1 row affected (0.27 sec)
    
    mysql> INSERT INTO tmp(data) VALUES ('third data'),('fourth data'); # 同时插入多条数据
    Query OK, 2 rows affected (0.25 sec)
    Records: 2  Duplicates: 0  Warnings: 0
    
    mysql> SELECT * FROM tmp;                                           # 查表
    +----+-------------+
    | id | data        |
    +----+-------------+
    | 10 | first data  |
    | 11 | second data |
    | 12 | third data  |
    | 13 | fourth data |
    +----+-------------+
    4 rows in set (0.00 sec)
    

    :使用INSERT命令时可以忽略指定要插入数据的列名,但这要求数据必须按照表结构定义时列的顺序依次进行插入(全量插入)。
    而如果指定要插入数据的列名,则可实现只对某些列进行插入(局部插入),同时不必严格遵循列顺序,并且当表结构被改动时,指定插入的 SQL 语句不受影响。

    :使用单条INSERT语句插入多个记录效果等同于多次执行单行插入INSERT语句,但是效率会更高。

    INSERT语句还可以将对一个表的查询结果插入到另一个表中。其语法格式如下所示:

    INSERT [INTO] table_name1 (column_list) 
    SELECT (column_list2) FROM table_name2 WHERE (condition)
    

    举个例子:比如我们创建一个新表tmp1,其只有一个字段data,然后我们将表tmp中的data字段复制给新表tmp1

    mysql> CREATE TABLE tmp1 ( data VARCHAR(20) );
    Query OK, 0 rows affected (1.35 sec)
    
    mysql> INSERT INTO tmp1
        -> SELECT data FROM tmp;
    Query OK, 4 rows affected (0.34 sec)
    Records: 4  Duplicates: 0  Warnings: 0
    
    mysql> SELECT * FROM tmp1;
    +-------------+
    | data        |
    +-------------+
    | first data  |
    | second data |
    | third data  |
    | fourth data |
    +-------------+
    4 rows in set (0.00 sec)
    

    :借助INSERT语句提供的查询结果插入功能,我们可以很方便实现对数据表的备份功能。
    比如,现在假设我们要备份数据表tmp,具体操作如下所示:

    mysql> CREATE TABLE clone_tmp LIKE tmp; # 复制表结构
    [0/2853]Query OK, 0 rows affected (1.38 sec) 
    
    mysql> SHOW CREATE TABLE clone_tmp\G    # 查看表结构
    *************************** 1. row ***************************
           Table: clone_tmp
    Create Table: CREATE TABLE `clone_tmp` (
      `id` int NOT NULL AUTO_INCREMENT,
      `data` varchar(20) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
    1 row in set (0.10 sec)
    
    mysql> SELECT * FROM clone_tmp;         # 查看表数据
    Empty set (0.00 sec)
    
    mysql> INSERT INTO clone_tmp            # 复制表数据
        -> SELECT * FROM tmp;
    Query OK, 4 rows affected (0.24 sec)
    Records: 4  Duplicates: 0  Warnings: 0
    
    mysql> SELECT * FROM clone_tmp;         # 查看表数据
    +----+-------------+
    | id | data        |
    +----+-------------+
    | 10 | first data  |
    | 11 | second data |
    | 12 | third data  |
    | 13 | fourth data |
    +----+-------------+
    4 rows in set (0.00 sec)
    
  • :从数据表中删除数据使用的是DELETE语句。其语法如下所示:

    DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias]
        [PARTITION (partition_name [, partition_name] ...)]
        [WHERE where_condition]
        [ORDER BY ...]
        [LIMIT row_count]
    

    DELETE语句如果未指定WHERE子句限定删除范围,则将进行全表删除,即删除表中所有的记录。

    举个例子:删除表tmpid1012之间的记录:

    mysql> SELECT * FROM tmp;                          # 原始数据
    +----+-------------+
    | id | data        |
    +----+-------------+
    | 10 | first data  |
    | 11 | second data |
    | 12 | third data  |
    | 13 | fourth data |
    +----+-------------+
    4 rows in set (0.00 sec)
    
    mysql> DELETE FROM tmp WHERE id BETWEEN 10 AND 12; # 删除数据
    Query OK, 3 rows affected (0.64 sec)
    
    mysql> SELECT * FROM tmp;                          # 最新数据
    +----+-------------+
    | id | data        |
    +----+-------------+
    | 13 | fourth data |
    +----+-------------+
    1 row in set (0.00 sec)
    

    :如果想删除表中所有记录,一个高效的方法是使用TRUNCATE语句。其语法如下所示:

    TRUNCATE [TABLE] tbl_name
    

    TRUNCATE语句实际上是将表进行删除,然后再重新创建一个相同结构的新表。也因为TRUNCATE是直接删除表而不是逐行删除记录,因此它的执行速度比DELETE快。

  • :对数据进行修改,使用的是UPDATE语句。其语法如下所示:

    # 格式一:Single-table syntax
    UPDATE [LOW_PRIORITY] [IGNORE] table_reference
        SET assignment_list
        [WHERE where_condition]
        [ORDER BY ...]
        [LIMIT row_count]
    
    value:
        {expr | DEFAULT}
    
    assignment:
        col_name = value
    
    assignment_list:
        assignment [, assignment] ...
    
    # 格式二:Multiple-table syntax
    UPDATE [LOW_PRIORITY] [IGNORE] table_references
        SET assignment_list
        [WHERE where_condition]
    

    UPDATE语句最常用的语法结构如下:

    UPDATE table_name 
    SET column_name1 = value1, column_name2 = value2,..., column_namen = valuen
    WHERE (condition);
    

    举个例子:找到表tmpid10的记录,将且data字段的值更改为ten data

    mysql> SELECT * FROM tmp WHERE id = 10;            # 原始数据
    +----+------------+
    | id | data       |
    +----+------------+
    | 10 | first data |
    +----+------------+
    1 row in set (0.02 sec)
    
    mysql> UPDATE tmp SET data='ten data' WHERE id=10; # 修改数据
    Query OK, 1 row affected (0.21 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> SELECT * FROM tmp WHERE id=10;              # 最新数据
    +----+----------+
    | id | data     |
    +----+----------+
    | 10 | ten data |
    +----+----------+
    1 row in set (0.00 sec)
    

    :请确保UPDATE语句始终带有WHERE子句,从而限定修改记录范围。如果忽略WHERE子句,MySQL 将会执行全表更新,即表中所有行都会被更新。

  • :对数据表进行查询,使用的语句为select。其语法如下所示:

    SELECT
        [ALL | DISTINCT | DISTINCTROW ]
        [HIGH_PRIORITY]
        [STRAIGHT_JOIN]
        [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
        [SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
        select_expr [, select_expr] ...
        [into_option]
        [FROM table_references
          [PARTITION partition_list]]
        [WHERE where_condition]
        [GROUP BY {col_name | expr | position}, ... [WITH ROLLUP]]
        [HAVING where_condition]
        [WINDOW window_name AS (window_spec)
            [, window_name AS (window_spec)] ...]
        [ORDER BY {col_name | expr | position}
          [ASC | DESC], ... [WITH ROLLUP]]
        [LIMIT {[offset,] row_count | row_count OFFSET offset}]
        [into_option]
        [FOR {UPDATE | SHARE}
            [OF tbl_name [, tbl_name] ...]
            [NOWAIT | SKIP LOCKED]
          | LOCK IN SHARE MODE]
        [into_option]
    
    into_option: {
        INTO OUTFILE 'file_name'
            [CHARACTER SET charset_name]
            export_options
      | INTO DUMPFILE 'file_name'
      | INTO var_name [, var_name] ...
    }
    

    对数据的查询操作包含很多细分内容,以下列举常用的查询操作:

    • 基本查询:基本查询就是对数据表字段直接进行查询。其语法如下所示:

      SELECT { * |  } FROM ,...
      

      其中:

      • *:表示选中数据表所有字段。
      • column_list:表示字段列表,即数据表一个或多个字段的组合,多个字段间使用逗号,进行分隔。

      举个例子:创建一张用户表user,为其添加一些数据,然后查询下看添加是否成功:

      mysql> CREATE TABLE user (                                   # 建表
          -> id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
          -> name VARCHAR(30) NOT NULL DEFAULT 'Anonymous',
          -> gender ENUM('male','female') NOT NULL DEFAULT 'male',
          -> email VARCHAR(30)
          -> );
      Query OK, 0 rows affected (4.50 sec)
      
      mysql> INSERT INTO user (name, gender, email) VALUES         # 插入数据
          -> ('zhangsan', 'male', '[email protected]'),
          -> ('lisi', 'male', '[email protected]'),
          -> ('wangwu', 'female', '[email protected]');
      Query OK, 3 rows affected (0.21 sec)
      Records: 3  Duplicates: 0  Warnings: 0
      
      mysql> SELECT * FROM user;                                   # 全量查询(全字段查询)
      +----+----------+--------+------------------------+
      | id | name     | gender | email                  |
      +----+----------+--------+------------------------+
      |  1 | zhangsan | male   | [email protected]       |
      |  2 | lisi     | male   | [email protected]    |
      |  3 | wangwu   | female | [email protected] |
      +----+----------+--------+------------------------+
      3 rows in set (0.00 sec)
      
      mysql> SELECT name, gender, email FROM user;                 # 局部字段查询
      +----------+--------+------------------------+
      | name     | gender | email                  |
      +----------+--------+------------------------+
      | zhangsan | male   | [email protected]       |
      | lisi     | male   | [email protected]    |
      | wangwu   | female | [email protected] |
      +----------+--------+------------------------+
      3 rows in set (0.00 sec)
      
    • 数据过滤:对查询得到结果进行过滤,使用的是WHERE子句结合其他过滤操作符,常用的数据过滤操作有如下几种:

      • 范围过滤:范围过滤是对SELECT得到的结果集中的某些字段在数值范围上进行限定,大概有如下三种操作:

        • 比较运算符:即基于对结果集字段的数值大小进行比较操作。
          MySQL 中常用的比较操作符如下表所示:

          符号 描述
          = 等于
          <>, != 不等于
          > 大于
          >= 大于或等于
          < 小于
          <= 小于或等于
          <=> 比较两个NULL值是否相等

          其中:<=>操作符作用是安全的NULL相等比较,当比较的两个操作码均为NULL时,返回1。而当只有一个操作码为NULL时,返回0。如下所示:

          mysql> SELECT NULL <=> NULL;
          +---------------+
          | NULL <=> NULL |
          +---------------+
          |             1 |
          +---------------+
          1 row in set (0.01 sec)
          
          mysql> SELECT 1 <=> NULL;
          +------------+
          | 1 <=> NULL |
          +------------+
          |          0 |
          +------------+
          1 row in set (0.05 sec)
          

          更多比较操作符详情,请查看:Comparison Operators

          举个例子:查询表user中性别为male的记录:

          mysql> SELECT * FROM user WHERE gender = 'male';
          +----+----------+--------+---------------------+
          | id | name     | gender | email               |
          +----+----------+--------+---------------------+
          | 1 | zhangsan | male | [email protected]    |
          | 2 | lisi     | male | [email protected] |
          +----+----------+--------+---------------------+
          2 rows in set (0.00 sec)
          

          :比较操作符如果用于对字符串的比较时,字符串需要使用单引号'进行限定。

        • IN:该操作符可以是多个数据的集合,当结果集匹配集合中任一数据时,则将其返回。
          其语法如下所示:

          expr [NOT] IN (value,...)
          

          其中:当expr等于任一value值时,返回1true),否则,返回0false)。

          举个例子:查询表user中姓名为lisiwangwu的记录:

          mysql> SELECT * FROM user WHERE name IN ('lisi', 'wangwu');
          +----+--------+--------+------------------------+
          | id | name   | gender | email                  |
          +----+--------+--------+------------------------+
          |  2 | lisi   | male   | [email protected]    |
          |  3 | wangwu | female | [email protected] |
          +----+--------+--------+------------------------+
          2 rows in set (0.00 sec)
          
        • BETWEEN AND:该操作符用于限定一个区间范围,当结果集落在该区间范围内时,则将其返回。
          其语法如下所示:

          expr [NOT] BETWEEN min AND max
          

          其中:当expr值大于等于min并且小于等于max时,返回1,否则返回0

          举个例子:查询表userid13中的所有记录:

          mysql> SELECT * FROM user WHERE id BETWEEN 1 AND 3;
          +----+----------+--------+------------------------+
          | id | name     | gender | email                  |
          +----+----------+--------+------------------------+
          |  1 | zhangsan | male   | [email protected]       |
          |  2 | lisi     | male   | [email protected]    |
          |  3 | wangwu   | female | [email protected] |
          +----+----------+--------+------------------------+
          3 rows in set (0.06 sec)
          

        IN操作符是数据集合,BETWEEN AND操作符是区间集合,两者区别示例如下:

        • IN (1, 3):表示匹配的数据为13
        • BETWEEN 1 AND 3:表示匹配的数据为123
      • 空值过滤:如果需要检查字段是否为NULL,可以使用IS NULL操作符。
        其语法如下所示:

        IS [NOT] NULL
        

        :空值与字段为0,空字符串和包含空格不同。如下例子所示:

        mysql> SELECT 1 IS NULL, 0 IS NULL, '' IS NULL, ' ' IS NULL, NULL IS NULL;
        +-----------+-----------+------------+-------------+--------------+
        | 1 IS NULL | 0 IS NULL | '' IS NULL | ' ' IS NULL | NULL IS NULL |
        +-----------+-----------+------------+-------------+--------------+
        |         0 |         0 |          0 |           0 |            1 |
        +-----------+-----------+------------+-------------+--------------+
        1 row in set (0.00 sec)
        
      • 去重:如果查询结果集存在重复条目,可使用DISTINCT关键字去除重复条目。
        其语法如下所示:

        SELECT DISTINCT 字段名 FROM 表名;
        

        举个例子:为表user插入一个namewangwu的新记录,这样user表中字段name就存在重复条目,然后分别查询在不使用DISTINCT和使用DISTINCT关键字的结果集:

        mysql> INSERT INTO user(name) VALUES ('wangwu');             # 插入重复数据
        Query OK, 1 row affected (0.20 sec)
        
        mysql> SELECT name FROM user WHERE name = 'wangwu';          # 查表
        +--------+
        | name   |
        +--------+
        | wangwu | # 结果集存在重复
        | wangwu |
        +--------+
        2 rows in set (0.00 sec)
        
        mysql> SELECT DISTINCT name FROM user WHERE name = 'wangwu'; # 查表(去重)
        +--------+
        | name   |
        +--------+
        | wangwu | # 去重成功
        +--------+
        1 row in set (0.00 sec)
        
      • 逻辑过滤:表达式可以结合逻辑运算符来进行范围限定。MySQL 中常用的逻辑运算符如下表所示:

        运算符号 描述
        AND
        OR
        NOT!
        XOR 异或

        其中:如果表达式为真,则返回1,否则返回0

        :逻辑或OR操作符作用效果与IN操作符是一样的,但是IN操作符更加简洁明了,且效率更高,最重要的是IN操作符支持更加复杂的嵌套查询。

        OR可以和AND一起使用,在默认情况下,AND的优先级大于OR,对于复杂的查询操作,建议手动添加括号()以保证运算顺序符合预期。

        举个例子:使用逻辑运算符查询用户表user

        # 逻辑与:选择 name='wangwu' 且 gender='female' 的记录
        mysql> SELECT * FROM user WHERE name = 'wangwu' AND gender = 'female'; 
        +----+--------+--------+------------------------+
        | id | name   | gender | email                  |
        +----+--------+--------+------------------------+
        |  3 | wangwu | female | [email protected] |
        +----+--------+--------+------------------------+
        1 row in set (0.00 sec)
        
        # 逻辑或:选择 name='lisi' 或 email 为空的记录 
        mysql> SELECT * FROM user WHERE name = 'lisi' OR email IS NULL;
        +----+--------+--------+---------------------+
        | id | name | gender | email |
        +----+--------+--------+---------------------+
        |  2 | lisi   | male   | [email protected] |
        |  4 | wangwu | male   | NULL                |
        +----+--------+--------+---------------------+
        2 rows in set (0.00 sec)
        
        # 逻辑非:选择 name 不为 wnagwu 的记录
        mysql> SELECT * FROM user WHERE NOT name = 'wangwu';
        +----+----------+--------+---------------------+
        | id | name | gender | email |
        +----+----------+--------+---------------------+
        |  1 | zhangsan | male   | [email protected]    |
        |  2 | lisi     | male   | [email protected] |
        +----+----------+--------+---------------------+
        2 rows in set (0.00 sec)
        
      • 匹配过滤:MySQL 支持模糊匹配功能,具体可分为如下两种模式:

        • 通配符匹配:MySQL 中LIKE关键字支持使用通配符进行匹配查找。其语法如下所示:

          expr [NOT] LIKE pattern [ESCAPE 'escape_char']
          

          :更多字符串匹配操作,请参考:String Comparison Functions

          MySQL 支持多种通配符,常用的通配符有如下两种:

          通配符 描述
          % 匹配任意长度字符串
          _ 匹配任意单个字符串

          :通配符%无法匹配空值NULL

          举个例子:查找表user中名称包含an的记录,查找表user中名称长度为4,且以字符i结尾的记录:

          mysql> SELECT * FROM user WHERE name LIKE '%an%';
          +----+----------+--------+------------------------+
          | id | name | gender | email |
          +----+----------+--------+------------------------+
          |  1 | zhangsan | male   | [email protected]       |
          |  3 | wangwu   | female | [email protected] |
          |  4 | wangwu   | male   | NULL                   |
          +----+----------+--------+------------------------+
          3 rows in set (0.00 sec)
          
          mysql> SELECT * FROM user WHERE name LIKE '___i';
          +----+------+--------+---------------------+
          | id | name | gender | email               |
          +----+------+--------+---------------------+
          |  2 | lisi | male   | [email protected] |
          +----+------+--------+---------------------+
          1 row in set (0.00 sec)
          
        • 正则匹配:MySQL 中可通过关键字REGEXP进行正则表达式查询。其语法如下所示:

          expr [NOT] REGEXP pattern
          

          :更多正则匹配内容,请查看:Regular Expressions

          举个例子:查找表user中带有纯数字邮箱的记录:

          mysql> SELECT * FROM user WHERE email REGEXP '^\\\d{1,}@.+\\.com$';
          +----+----------+--------+---------------------+
          | id | name     | gender | email               |
          +----+----------+--------+---------------------+
          |  1 | zhangsan | male   | [email protected]    |
          |  2 | lisi     | male   | [email protected] |
          +----+----------+--------+---------------------+
          2 rows in set (0.00 sec)
          

          :MySQL 中,为了匹配特殊字符,需要使用\\前缀进行转义。比如,\\-表示查找-\\.表示查找.

    • 条目限制:MySQL 中可以使用LIMIT关键字限制查询结果的数量。其语法如下所示:

      # 格式一
      LIMIT [偏移量,] 行数
      
      # 格式二
      LIMIT 行数 OFFSET 偏移量
      

      其中:

      • 偏移量:指从结果集第几行开始选取,其默认值为0,即从结果集第一条记录开始显示。
      • 行数:指返回的记录条目数。

      :MySQL 8.0 中也可以使用LIMIT 行数 OFFSET 偏移量的用法,比如:LIMIT 3 OFFSET 4LIMIT 4, 3效果一样,都表示从结果集第 5 条记录开始选取后面的 3 条记录。

      举个例子:查询表user第一条记录:

      mysql> SELECT * FROM user LIMIT 1; # 相当于 limit 0, 1
      +----+----------+--------+------------------+
      | id | name     | gender | email            |
      +----+----------+--------+------------------+
      |  1 | zhangsan | male   | [email protected] |
      +----+----------+--------+------------------+
      1 row in set (0.00 sec)
      
    • 数据排序:如果想对结果集进行排序,可使用ORDER BY子句。其语法如下所示:

      ORDER BY {col_name | expr | position} [ASC | DESC]
      

      其中:ORDER BY子句默认采用升序ASC排序,若需要降序排序,则需显示使用DESC

      ORDER BY支持多字段排序,只需使用逗号,分隔多字段即可。多字段排序只有在当前面字段值相同时,才会对这些相同记录采用后续字段进行排序。

      举个例子:查询表user所有记录,先按照字段name进行升序排序,当name字段相同的记录,再按照id进行降序排列:

      mysql> SELECT * FROM user ORDER BY name;            # 按 name 升序排列
      +----+----------+--------+------------------------+
      | id | name     | gender | email                  |
      +----+----------+--------+------------------------+
      |  2 | lisi     | male   | [email protected]    |
      |  3 | wangwu   | female | [email protected] |
      |  4 | wangwu   | male   | NULL                   |
      |  1 | zhangsan | male   | [email protected]       |
      +----+----------+--------+------------------------+
      4 rows in set (0.00 sec)
      
      mysql> SELECT * FROM user ORDER BY name, id DESC;   # 先按 name 升序排列,name 相同时再按 id 降序排列
      +----+----------+--------+------------------------+
      | id | name     | gender | email                  |
      +----+----------+--------+------------------------+
      |  2 | lisi     | male   | [email protected]    |
      |  4 | wangwu   | male   | NULL                   |
      |  3 | wangwu   | female | [email protected] |
      |  1 | zhangsan | male   | [email protected]       |
      +----+----------+--------+------------------------+
      4 rows in set (0.00 sec)
      

      ORDER BY通常都是使用结果集中的字段进行排序,但是也可以使用非检索的列字段(即原表字段)进行排序。

    • 数据汇总:对数据进行查询时,很多时候并不是要对数据进行检索,而是直接对查询出来的数据进行汇总。为了方便对数据进行汇总计算,MySQL 内置了一些常用的聚集函数,方便我们进行数据汇总。

      聚集函数(aggregate function):指的是运行在行组上,计算和返回单个值的函数。

      MySQL 中最常使用到的聚集函数如下表所示:

      函数 描述
      AGV() 返回某列的平均值
      COUNT() 返回某列的行数
      MAX() 返回某列的最大值
      MIN() 返回某列的最小值
      SUM() 返回某列之和

      :更多聚集函数内容,请查看:Aggregate Function Descriptions

      举个例子:查询表user的字段id,输出结果集的总行数,并分别求取该字段的最小值,最大值,平均值和总和值:

      mysql> SELECT COUNT(id), # 总行数
          -> MIN(id),          # 最小值
          -> MAX(id),          # 最大值
          -> AVG(id),          # 平均值
          -> SUM(id)           # 总和
          -> FROM user;
      +-----------+---------+---------+---------+---------+
      | count(id) | min(id) | max(id) | avg(id) | sum(id) |
      +-----------+---------+---------+---------+---------+
      |         4 |       1 |       4 |  2.5000 |      10 |
      +-----------+---------+---------+---------+---------+
      1 row in set (0.00 sec)
      
    • 分组查询:分组查询是对记录按照某一个或多个字段进行分组,其实质是允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
      MySQL 中使用关键字GROUP BY对数据进行分组,其语法格式如下所示:

      [GROUP BY 字段] [HAVING <条件表达式>]
      

      其中:

      • 字段:表示要按照该字段名进行分组。
      • HAVING:表示对分组进行过滤,满足条件表达式的分组将被显示。

      GROUP BY子句的执行机制为:首先GROUP BY会将指定字段进行排序,然后将值相同的字段整合为一个组,这样由于字段值的不同,就产生了多个组。然后分别对这些组进行聚集计算。

      一个需要牢记的点是:GROUP BY子句指示 MySQL 分组数据,然后对每个组而不是整个结果集进行聚集。

      以下列举一些常见的分组操作:

      • 创建分组:创建分组直接使用GROUP BY子句即可。GROUP BY通常和聚集函数一起使用,以便能汇总总表内容的子集。

        :如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。

        举个例子:对数据表user按照字段name进行分组,并统计分组数量:

        mysql> SELECT * FROM user;                            # 原始数据
        +----+----------+--------+------------------------+
        | id | name     | gender | email                  |
        +----+----------+--------+------------------------+
        |  1 | zhangsan | male   | [email protected]       |
        |  2 | lisi     | male   | [email protected]    |
        |  3 | wangwu   | female | [email protected] |
        |  4 | wangwu   | male   | NULL                   |
        +----+----------+--------+------------------------+
        4 rows in set (0.00 sec)
        
        mysql> SELECT name, COUNT(*) FROM user GROUP BY name; # 按 name 进行分组
        +----------+----------+
        | name     | count(*) |
        +----------+----------+
        | zhangsan |        1 |
        | lisi     |        1 |
        | wangwu   |        2 |
        +----------+----------+
        3 rows in set (0.00 sec)
        

        如上所示,我们将表user按照名称name进行分组,并统计各个分组数量。可以看到,数据表user中名称为zhangsanlisi的记录都各自只有一个,而名称为wangwu的记录总共有两条。

      • 分组详情:可以通过函数GROUP_CONCAT()将每个分组中的各个字段具体值显示出来。

        举个例子:将表user按性别gender进行分组,统计并显示各性别组的人的名称。

        mysql> SELECT gender, COUNT(*) as total, GROUP_CONCAT(name) AS names FROM user GROUP BY gender;
        +--------+-------+----------------------+
        | gender | total | names                |
        +--------+-------+----------------------+
        | male   |     3 | zhangsan,lisi,wangwu |
        | female |     1 | wangwu               |
        +--------+-------+----------------------+
        2 rows in set (0.00 sec)
        
      • 分组汇总:可以使用关键子WITH ROLLUP来为所有分组记录添加一条汇总记录,该汇总记录会自动计算分组数据总和。

        举个例子:将表user按性别gender进行分组,并汇总所有分组记录:

        mysql> SELECT gender, COUNT(*) AS total FROM user GROUP BY gender WITH ROLLUP;
        +--------+-------+
        | gender | total |
        +--------+-------+
        | male   |     3 |
        | female |     1 |
        | NULL   |     4 | # 汇总记录
        +--------+-------+
        3 rows in set (0.00 sec)
        
      • 多字段分组GROUP BY子句支持多字段分组,字段之间使用逗号,进行分隔。

        GROUP BY进行多字段分组时,其执行机制为:首先按照第一个字段进行分组,当第一个字段存在相同记录时,再将这些相同的记录以第二个字段进行分组,依次类推。

        举个例子:对表user按照性别gender进行分组,当性别相同时,再按照姓名进行分组:

        mysql> SELECT name,gender FROM user GROUP BY gender,name;
        +----------+--------+
        | name     | gender |
        +----------+--------+
        | zhangsan | male   |
        | lisi     | male   |
        | wangwu   | female |
        | wangwu   | male   |
        +----------+--------+
        4 rows in set (0.00 sec)
        
      • 过滤分组GROUP BY子句可结合HAVING关键字来对分组进行过滤,只有在满足限定条件下,才显示分组内容。

        HAVING非常类似于WHERE,它们之间的区别在于:WHERE用于行过滤,而HAVING用于分组过滤。或者说,WHERE是在数据分组前进行过滤,而HAVING是在数据分组后进行过滤

        举个例子:将表user按性别gender进行分组,然后过滤出性别数量大于 1 的分组:

        mysql> SELECT gender, GROUP_CONCAT(name) AS names FROM user GROUP BY gender HAVING COUNT(gender) > 1;
        +--------+----------------------+
        | gender | names                |
        +--------+----------------------+
        | male   | zhangsan,lisi,wangwu |
        +--------+----------------------+
        1 row in set (0.01 sec)
        
      • 分组排序GROUP BY用于分组,ORDER BY用于记录排序,两者结合起来,就可以实现对分组进行排序。

        举个例子:对表user按性别gender进行分组,并统计性别人数,按性别人数从小到大进行排序:

        mysql> SELECT gender, COUNT(*) AS total FROM user GROUP BY gender;                # 未排序
        +--------+-------+
        | gender | total |
        +--------+-------+
        | male   |     3 |
        | female |     1 |
        +--------+-------+
        2 rows in set (0.00 sec)
        
        mysql> SELECT gender, COUNT(*) AS total FROM user GROUP BY gender ORDER BY total; # 按 total 排序
        +--------+-------+
        | gender | total |
        +--------+-------+
        | female |     1 |
        | male   |     3 |
        +--------+-------+
        2 rows in set (0.00 sec)
        
    • 多表查询:在关系型数据库中,表与表之间可以存在关联关系,因此可以同时针对多个表进行查询,然后连接这些数据到同一结果集中。
      多表查询常涉及的操作有如下几种:

      • 组合查询:组合查询可以将多条SELECT语句组合到一个结果集中,MySQL 中借助UNION关键字可以实现组合查询。
        其语法如下所示:

        SELECT column,... FROM table_name1
        UNION [ALL | DISTINCT]
        SELECT column,... FROM table_name2
        

        其中:UNION合并生成的结果集会自动删除重复条目(即默认使用UNION DISTINCT),返回的行都是唯一的。如果期望保留重复条目,则需使用UNION ALL

        :联合查询只要求查询的列数一样,数据类型可不相同,结果集的字段由第一条查询语句决定。

        UNION也可以对同一个表进行组合查询,这种情况下效果与使用逻辑运算符OR的效果是一样的。

        举个例子:对表user进行组合查询,要求获取性别gender为女的用户和箱email不为空的用户:

        mysql> SELECT name FROM user WHERE gender = 'female'; # 性别为女
        +--------+
        | name   |
        +--------+
        | wangwu |
        +--------+
        1 row in set (0.00 sec)
        
        mysql> SELECT name FROM user WHERE email IS NOT NULL; # 邮箱不为空
        +----------+
        | name     |
        +----------+
        | zhangsan |
        | lisi     |
        | wangwu   |
        +----------+
        3 rows in set (0.00 sec)
        
        mysql> SELECT name FROM user WHERE gender = 'female'
            -> UNION                                          # 组合查询(去重)
            -> SELECT name FROM user WHERE email IS NOT NULL;
        +----------+
        | name     |
        +----------+
        | wangwu   |
        | zhangsan |
        | lisi     |
        +----------+
        3 rows in set (0.00 sec)
        
        mysql> SELECT name FROM user WHERE gender = 'female'
            -> UNION ALL                                      # 组合查询(保留重复条目)
            -> SELECT name FROM user WHERE email IS NOT NULL;
        +----------+
        | name     |
        +----------+
        | wangwu   |
        | zhangsan |
        | lisi     |
        | wangwu   |
        +----------+
        4 rows in set (0.00 sec)
        
      • 子查询(subquery):子查询指的是嵌套在其他查询语句中的查询语句。

        子查询的执行机制为:子查询总是由内向外处理,即在SELECT子句中,最内部的子查询会最先进行计算,然后将该子查询结果作为临近外层另一个查询的过滤条件,查询可以基于一个表或者多个表。

        举个例子:查询表user,找到id13的记录:

        mysql> SELECT * FROM user WHERE id IN (1, 3);       # 直接查询
        +----+----------+--------+------------------------+
        | id | name     | gender | email                  |
        +----+----------+--------+------------------------+
        |  1 | zhangsan | male   | [email protected]       |
        |  3 | wangwu   | female | [email protected] |
        +----+----------+--------+------------------------+
        2 rows in set (0.00 sec)
        
        mysql> SELECT * FROM user WHERE id IN (
            -> SELECT id FROM user WHERE id = 1 OR id = 3   # 使用子查询
            -> );
        +----+----------+--------+------------------------+
        | id | name     | gender | email                  |
        +----+----------+--------+------------------------+
        |  1 | zhangsan | male   | [email protected]       |
        |  3 | wangwu   | female | [email protected] |
        +----+----------+--------+------------------------+
        2 rows in set (0.00 sec)
        

        上述例子其实就是使用子查询SELECT id FROM user WHERE id = 1 OR id = 3得到结果集1,3,然后该结果集会被外层SELECT语句使用,相当于外层语句为:SELECT * FROM user WHERE id IN (1, 3)

        另外,子查询中经常会使用一些操作符,增强查询功能。常用的子查询操作符如下表所示:

        操作符 描述
        ANY 表示只要满足子查询任一比较条件,就返回
        SOME ANY
        ALL 表示外层查询必须同时满足子查询的所有条件,才返回
        EXISTS 用来判断子查询是否返回行,如果子查询至少返回一行,则EXISTS返回true,此时会触发外层查询

        举个例子:创建两个表tmp1tmp2,分别使用子查询操作符进行比较:

        mysql> CREATE TABLE tmp1 ( num1 INT);                  # 建表
        Query OK, 0 rows affected (2.41 sec)
        
        mysql> CREATE TABLE tmp2 ( num2 INT);                  # 建表
        Query OK, 0 rows affected (1.84 sec)
        
        mysql> INSERT INTO tmp1 VALUES (1), (10), (55), (123); # 插入值
        Query OK, 4 rows affected (0.31 sec)
        Records: 4  Duplicates: 0  Warnings: 0
        
        mysql> INSERT INTO tmp2 VALUES (5), (35), (382);       # 插入值
        Query OK, 4 rows affected (0.17 sec)
        Records: 4  Duplicates: 0  Warnings: 0
        
        # 子查询返回集合 [5, 35, 382],num1 > ANY:表示 num1 大于 [5, 35, 382] 中任一一个值即可返回
        mysql> SELECT * FROM tmp1 WHERE num1 > ANY( SELECT num2 FROM tmp2);
        +------+
        | num1 |
        +------+
        |   10 |
        |   55 |
        |  123 |
        +------+
        3 rows in set (0.00 sec)
        
        # 子查询返回集合 [5, 35, 382],num1 < ALL:表示 num1 必须小于 [5, 35, 382] 中所有值才能返回
        mysql> SELECT * FROM tmp1 WHERE num1 < ALL( SELECT num2 FROM tmp2);
        +------+
        | num1 |
        +------+
        |    1 |
        +------+
        1 row in set (0.01 sec)
        
        # 当子查询返回结果集不为空时,触发外层查询
        mysql> SELECT * FROM tmp1 WHERE EXISTS (SELECT * FROM tmp2);
        +------+
        | num1 |
        +------+
        |    1 |
        |   10 |
        |   55 |
        |  123 |
        +------+
        4 rows in set (0.01 sec)
        
        # 当子查询返回结果集为空时,外层查询不进行查询
        mysql> SELECT * FROM tmp1 WHERE EXISTS (SELECT * FROM tmp2 WHERE num2 > 1000);
        Empty set (0.00 sec)
        

        更多子查询相关内容,请查看:Subqueries

      • 连接查询:就是将两张表依据某个条件(连接条件)进行数据拼接的过程。

        :连接实质上就是将两张表的字段拼接成一张表,然后以一定的条件过滤该表,得到结果集。

        连接是关系数据库模型的主要特点,连接的过程就是将不同实体信息整合的过程。在具体介绍连接查询前,先创建两张示例表,如下所示:

        mysql> CREATE TABLE article (                                # 文章表
            -> id INT PRIMARY KEY AUTO_INCREMENT,
            -> title VARCHAR(50) NOT NULL COMMENT 'title of article',
            -> content TEXT COMMENT 'content of article',
            -> pubTime TIMESTAMP COMMENT 'publish date'
            -> );
        Query OK, 0 rows affected (2.29 sec)
        
        mysql> CREATE TABLE comment (                                # 评论表
            -> id INT PRIMARY KEY AUTO_INCREMENT,
            -> content TINYTEXT COMMENT 'content of comment',
            -> article_id INT NOT NULL,
            -> FOREIGN KEY(article_id) REFERENCES article(id)        # 外键约束
            -> );
        Query OK, 0 rows affected (4.16 sec)
        
        mysql> INSERT INTO article (title, content) VALUES           # 插入值
            -> ('first article','content one'),
            -> ('second article', 'content two'),
            -> ('third article','content three');
        Query OK, 3 row affected (0.24 sec)
        Records: 3  Duplicates: 0  Warnings: 0
        
        mysql> INSERT INTO comment (content, article_id) VALUES      # 插入值
            -> ('comment 1',1),
            -> ('comment 11',1),
            -> ('comment 3',3);
        Query OK, 3 rows affected (0.22 sec)
        Records: 3  Duplicates: 0  Warnings: 0
        
        mysql> SELECT * FROM article;                                # 查表
        +----+----------------+---------------+---------+
        | id | title          | content       | pubTime |
        +----+----------------+---------------+---------+
        |  1 | first article  | content one   | NULL    |
        |  2 | second article | content two   | NULL    |
        |  3 | third article  | content three | NULL    |
        +----+----------------+---------------+---------+
        3 rows in set (0.00 sec)
        
        mysql> SELECT * FROM comment;                                # 查表
        +----+------------+------------+
        | id | content    | article_id |
        +----+------------+------------+
        |  1 | comment 1  |          1 |
        |  2 | comment 11 |          1 |
        |  3 | comment 3  |          3 |
        +----+------------+------------+
        3 rows in set (0.00 sec)
        

        上面我们创建了两张表:文章表article和评论表comment,并为各自填充了一些数据,每篇文章对应一条或多条评论。

        连接查询是关系数据库中最主要的查询,具体可细分为如下三种连接查询:

        • 交叉连接(Cross Join):指不需要连接条件的多表连接。其语法如下所示:

          # 格式一:隐式交叉连接
          SELECT column1[,...,columnn] from table_name1,table_name2[,...table_namen]
          
          # 格式二:显示交叉连接
          left_table [CROSS] JOIN right_table
          

          举个例子:交叉查询表articlecomment

          mysql> SELECT * FROM article AS a, COMMENT AS c ORDER BY a.id, c.id;             # 隐式写法
          +----+----------------+---------------+---------+----+------------+------------+
          | id | title          | content       | pubTime | id | content    | article_id |
          +----+----------------+---------------+---------+----+------------+------------+
          |  1 | first article  | content one   | NULL    |  1 | comment 1  |          1 |
          |  1 | first article  | content one   | NULL    |  2 | comment 11 |          1 |
          |  1 | first article  | content one   | NULL    |  3 | comment 3  |          3 |
          |  2 | second article | content two   | NULL    |  1 | comment 1  |          1 |
          |  2 | second article | content two   | NULL    |  2 | comment 11 |          1 |
          |  2 | second article | content two   | NULL    |  3 | comment 3  |          3 |
          |  3 | third article  | content three | NULL    |  1 | comment 1  |          1 |
          |  3 | third article  | content three | NULL    |  2 | comment 11 |          1 |
          |  3 | third article  | content three | NULL    |  3 | comment 3  |          3 |
          +----+----------------+---------------+---------+----+------------+------------+
          9 rows in set (0.00 sec)
          
          mysql> SELECT * FROM article AS a CROSS JOIN comment AS c ORDER BY a.id, c.id;   # 显示写法
          +----+----------------+---------------+---------+----+------------+------------+
          | id | title          | content       | pubTime | id | content    | article_id |
          +----+----------------+---------------+---------+----+------------+------------+
          |  1 | first article  | content one   | NULL    |  1 | comment 1  |          1 |
          |  1 | first article  | content one   | NULL    |  2 | comment 11 |          1 |
          |  1 | first article  | content one   | NULL    |  3 | comment 3  |          3 |
          |  2 | second article | content two   | NULL    |  1 | comment 1  |          1 |
          |  2 | second article | content two   | NULL    |  2 | comment 11 |          1 |
          |  2 | second article | content two   | NULL    |  3 | comment 3  |          3 |
          |  3 | third article  | content three | NULL    |  1 | comment 1  |          1 |
          |  3 | third article  | content three | NULL    |  2 | comment 11 |          1 |
          |  3 | third article  | content three | NULL    |  3 | comment 3  |          3 |
          +----+----------------+---------------+---------+----+------------+------------+
          9 rows in set (0.00 sec)
          

          可以看到,交叉连接产生的结果是笛卡尔积(即两张表记录乘积),这种结果不具备实际数据价值,因此通常不会使用到交叉查询。

        • 内连接(Inner Join):指将两张表依据一定的条件连接起来。其语法如下:

          # 格式一:WHERE 子句
          SELECT column1[,...,columnn] from table_name1,table_name2[,...table_namen] WHERE where_condition
          
          # 格式二:使用 ON 子句(建议)
          left_table [INNER] JOIN right_table ON search_condition 
          
          # 格式三:使用 using 子句
          left_table [INNER] JOIN right_table USING(column_name1 [,...,column_namen])
          

          :使用内连接查询通常而言两张表内具备相同的字段(通常为外键),可以依据该字段连接两张表,且只有两张表内同时存在的值才会被提取出来。

          :使用WHERE子句的内连接定义连接条件比较简单明了,但INNER JOIN语法是标准的 ANSI SQL 标准规范,且查询效率更高,推荐使用ON子句进行内连接查询。

          举个例子:表comment的外键字段article_id指向表article的主键id,因此这两个字段是同一字段,现在通过该字段内连两个表,查看下结果(其实就是查询拥有评论的文章列表):

          # 传统查询
          mysql> SELECT title, c.content as comment FROM article AS a, comment AS c WHERE c.article_id = a.id;
          +---------------+------------+
          | title         | comment    |
          +---------------+------------+
          | first article | comment 1  |
          | first article | comment 11 |
          | third article | comment 3  |
          +---------------+------------+
          3 rows in set (0.00 sec)
          
          # 使用 ON 子句查询
          mysql> SELECT title, c.content comment FROM article a INNER JOIN comment c ON c.article_id = a.id;
          +---------------+------------+
          | title         | comment    |
          +---------------+------------+
          | first article | comment 1  |
          | first article | comment 11 |
          | third article | comment 3  |
          +---------------+------------+
          3 rows in set (0.00 sec)
          
          # 使用 USING 子句查询
          mysql> SELECT c.article_id AS id, title, c.content comment FROM article INNER JOIN comment c USING(id);
          +----+----------------+------------+
          | id | title          | comment    |
          +----+----------------+------------+
          |  1 | first article  | comment 1  |
          |  1 | second article | comment 11 |
          |  3 | third article  | comment 3  |
          +----+----------------+------------+
          3 rows in set (0.00 sec)
          

          USING关键字要求连接查询必须使用同名字段(即using(id) == left_table.id = right_table.id),不过通过借助为字段取别名可以绕过这一限制。
          :由于存在多个表,当表名称比较长时,可以为表取一个别名,简化调用。

        • 外连接:有时候需要包含没有关联的行数据,则此时可以使用外连接。

          内连接查询时,只会返回符合查询条件和连接条件的行数据。而有时候可能需要返回与另一张表中没有关联的数据,即查询结果集中不仅包含符合条件的行数据,还包含左表(左外连接)、右表(右外连接)或两个边表(全外连接)中的所有数据行。

          在 MySQL 中,只支持左外连接和右外连接,具体如下:
          :要在 MySQL 中实现全外连接,可以借助使用组合查询(UNION)即可。

          • 左(外)连接(Left Join):即不仅返回符合查询条件的数据行,同时返回左表所有数据行(对于不符合连接条件的记录,将其对应的右表字段内容设置NULL)。
            其语法如下所示:

            left_table LEFT [OUTER] JOIN right_table
            

            举个例子:比如想查看表article的所有内容,包含标题,正文和评论,那么就可以使用左外连接:

            mysql> SELECT title, a.content, c.content comment FROM article a LEFT OUTER JOIN comment c ON a.id = c.article_id;
            +----------------+---------------+------------+
            | title          | content       | comment    |
            +----------------+---------------+------------+
            | first article  | content one   | comment 1  |
            | first article  | content one   | comment 11 |
            | second article | content two   | NULL       | # 第二篇文章没有评论
            | third article  | content three | comment 3  |
            +----------------+---------------+------------+
            4 rows in set (0.00 sec)
            
          • 右(外)连接(Right Join):即不仅返回符合查询条件的数据行,同时返回右表所有的数据行。其语法如下所示:

            left_table RIGHT [OUTER] JOIN right_table
            

            举个例子:比如想查看评论对应的文章(包含没有评论的文章)内容,那么就可以使用右外连接:

            mysql> SELECT title, a.content, c.content comment FROM comment c RIGHT OUTER JOIN article a ON c.article_id = a.id;
            +----------------+---------------+------------+
            | title          | content       | comment    |
            +----------------+---------------+------------+
            | first article  | content one   | comment 1  |
            | first article  | content one   | comment 11 |
            | second article | content two   | NULL       |
            | third article  | content three | comment 3  |
            +----------------+---------------+------------+
            4 rows in set (0.00 sec)
            

            :该例子其实与上述左外连接例子相同,只是调换了左表和右表角色。

最后,一个SELECT语句中,其子句结构具备严格的固定顺序,如下所示:

SELECT DISTINCT 
FROM   JOIN 
ON 
WHERE                           # 行级过滤
GROUP BY                          # 分组
WITH { CUBE | ROLLUP }                           # 分组汇总
HAVING                          # 分组过滤
ORDER BY 
LIMIT 

存储引擎

数据库对数据进行增删改查底层是通过存储引擎进行的,不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能。

MYSQL 支持多种不同的存储引擎,可以通过命令SHOW ENGINES查看系统支持的所有存储引擎:

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

可以看到,MySQL 支持的存储引擎有:FEDERATEDMEMORYInnoDBPERFORMANCE_SCHEMAMyISAMMRG_MYISAMBLACKHOLECSVARCHIVE。其中:各种存储引擎最明显的区别是是否支持事务,MySQL 8.0 中默认的存储引擎为InnoDB,且其是 MySQL 唯一一个支持事务的内置存储引擎。

下面介绍下 MySQL 中最常用的两种存储引擎:

  • InnoDBInnoDB支持事务操作,支持行锁定和外键约束,是事务型数据库的首选存储引擎。其主要特性如下所示:

    • 支持事务机制,具备提交、回滚和奔溃恢复能力。
    • 支持大数据量存储,性能十分高效,其 CPU 效率基本优于其余所有基于磁盘的关系数据库引擎。
    • 支持外键完整性约束(FOREIGN KEY)。
    • 存储数据时,每张表的存储都按照主键顺序存放。如果定义表时没有显示指定主键,则InnoDB会自动为每一行生成一个6BROWID,并以此作为主键。
  • MyISAMMyISAM是基于ISAM的扩展实现,其拥有较高的插入、查询速度,但不支持事务。其主要特性如下所示:

    • 支持在大文件系统上进行使用。
    • BLOBTEXT列可以被索引。
    • 索引支持NULL值,该值占用 0~1 个字节空间。
    • 所有数字键值以高字节优先被存储,以允许一个更高的索引压缩。
    • 每个字符列可以使用不同的字符集。
    • CHARVARCHAR类型最大值可达 64KB。

其他存储引擎也拥有各自的特性,比如MEMORY引擎支持将表中数据存储到内存中,使得其具备高效快速的查询能力...
不同的存储引擎有不同的特点,实际使用中应结合业务场景,选择使用合适的存储引擎。

以下是几种常用存储引擎的横向比较,方便查看各引擎的特点:

特性 InnoDB MyISAM MEMORY MERGE BerkeleyDB
存储限制 64TB
事务支持 支持
锁机制 行锁 表锁 表锁 表锁 行锁
B树索引 支持 支持 支持 支持 支持
哈希索引 支持
全文索引 支持 支持
集群索引 支持
数据缓存 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据压缩 支持
空间使用 N/A
内存使用 中等
批量插入速度
外键支持 支持

事务

关系型数据库中,可以使用 事务(transaction) 来维护数据库完整性,它能保证事务范围内的成批操作要么全部执行,要么全部不执行(除非明确指示)。

简单来说,事务的作用就是使得多条 SQL 语句的操作具备原子性。
在事务的语义下,执行多条 SQL 语句就相当于执行一条 SQL 语句。而当事务中的某条 SQL 语句操作失败时,它会回滚之前成功的操作,使得数据库与未执行该事务前的状态保持一致,维护了数据的完整性。

保证事务可靠性的理论基础是事务具备的四大特性:ACID,具体内容如下:

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。

  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏,即数据库总是从一个一致性的状态转换到另一个一致性的状态。
    :一致性保证了当事务中间环节出错时,前面执行的语句也不会生效(回滚)。

  • Isolation(隔离性):数据库支持多个并发事务同时对数据进行读写和修改,一个事务所做的修改在最终提交之前,对其他事务是不可见的。
    :由于并发读写可能导致数据的不一致性,为了在不同程度上尽量避免这个问题,数据库提供了几种隔离级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

  • Durability(持久性):事务完成提交后,对数据的修改就是永久的,即便系统故障也不会丢失。

下面介绍下数据库的事务隔离级别,在介绍之前,先创建一个示例数据表account,表示一个银行账户:

mysql> CREATE TABLE account (
    -> id BIGINT PRIMARY KEY AUTO_INCREMENT,
    -> name VARCHAR(20) NOT NULL DEFAULT 'anonymous',
    -> money DECIMAL(10,5) NOT NULL DEFAULT 0
    -> )ENGINE=InnoDB;
Query OK, 0 rows affected (2.18 sec)

:MySQL 中只有InnoDB支持事务。

当多个事务同时操作同一条记录时,由于并发操作可能会造成数据的不一致性,包括脏读、不可重复读、幻读等。也因此,为了在最大可能上保证数据的一致性,数据库提供了以下几种隔离级别:

  • 未提交读(Read Uncommitted):在该隔离级别下,可能会遇到脏读(Dirty Read)问题,即当前隔离级别的事务可以读取到其他(任意隔离级别)事务更新后但未提交的数据,如果此时其他事务进行回滚操作,那么当前事务再次读取获得的值就可能与上一次不一致。
    Read Uncommitted是隔离级别最低的一种事务级别。

    举个例子:如下表顺序所示,首先为表添加一条数据,然后分别开启两个 MySQL 客户端,依下表account顺序同时执行事务1和事务2:

    时序 事务1 事务2
    1 INSERT INTO account(name, money) VALUES('lisi', 100);
    2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    3 BEGIN; BEGIN;
    4 UPDATE account SET MONEY = 200 WHERE name = 'lisi';
    5 SELECT * FROM ACCOUNT WHERE name = 'lisi'; # 200
    6 ROLLBACK;
    7 SELECT * FROM account WHERE name = 'lisi'; # 100
    8 COMMIT;
  • 已提交读(Read Committed):在该隔离级别下,可能会遇到不可重复读(Non Repeatable Read)问题,即当前事务多次读取同一数据,当第二次读取时,此时恰好另一个事务修改了这个数据并进行提交,则当前事务读取的数据就可能与第一次的数据不一致。如下示例所示:

    时序 事务1 事务2
    1 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    2 BEGIN; BEGIN;
    3 SELECT * FROM account WHERE name = 'lisi'; # 100
    4 UPDATE account SET money = 200 WHERE name = 'lisi';
    5 COMMIT;
    6 SELECT * FROM account WHERE name = 'lisi'; # 200
    7 COMMIT;

    将事务隔离级别设置为READ COMMITTED后,只有在其他(任意级别)事务进行提交后,修改的数据才会被当前事务读取到,这样就避免了脏读。

  • 可重复读(Repeatable Read):在该种隔离级别下,可能会遇到幻读(Phantom Read)问题,即在当前隔离级别事务下,第一次查询某条记录,发现不存在,此时如果有其他事务提交该记录,则再一次进行查询,仍然查询不到,但是对这条“不存在”的记录进行修改时,却能修改成功,并且再次进行查询,就可以查看到修改过的这条记录。如下所示:

    时序 事务1 事务2
    1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    2 BEGIN BEGIN
    3 SELECT * FROM account WHERE name = 'zhangsan'; # empty set
    4 INSERT INTO account(name, money) VALUES('zhangsan', 100);
    5 COMMIT;
    6 SELECT * FROM account WHERE name = 'zhangsan'; # empty set
    7 UPDATE account SET money = 200 WHERE name = 'zhangsan';
    8 SELECT * FROM account WHERE name = 'zhangsan'; # zhangsan 200
    9 COMMIT;

    将事务隔离级别设置为REPEATABLE READ后,此时即使其他事务提交修改已存在的数据或添加新数据,当前事务都无法感知得到,REPEATABLE READ在同一个事务内的查询结果都与事务开始时刻一致,这种隔离级别解决了不可重复读问题(同一事务多次读取数据可能不一致)。

    REPEATABLE READ隔离级别相当于在开始事务时,对表进行了一个缓存操作,后续在该事务内进行的操作都基于缓存数据,因此实际表添加或修改的内容不会影响到缓存数据。

    :MySQL 默认的数据库事务隔离级别为REPEATABLE READ,具体可通过如下命令进行查询:

    mysql> SELECT @@GLOBAL.transaction_isolation as global, @@SESSION.transaction_isolation as session;
    +-----------------+-----------------+
    | global          | session         |
    +-----------------+-----------------+
    | REPEATABLE-READ | REPEATABLE-READ |
    +-----------------+-----------------+
    1 row in set (0.00 sec)
    
  • 串行化(Serializable):串行化是数据库中最严格的隔离级别,在该种级别下,所有的事务依次进行执行,因此不会出现脏读、不可重复读 和 幻读现象,但是由于事务是串行执行的,因此效率非常低。

总结一下,数据库隔离级别之间的关系如下表所示:

隔离级别 读数据一致性 脏读 不可重复读 幻读
Read Uncommitted 最低级别
Read Committed 语句级
Repeatable Read 事务级
Serializable 最高级别,事务级

数据库中,对事务的操作主要包含如下几方面:

  • 开始事务:开启一个事务可以使用START TRANSACTIONBEGIN语句,其语法具体如下所示:

    START TRANSACTION
        [transaction_characteristic [, transaction_characteristic] ...]
    
    transaction_characteristic: {
        WITH CONSISTENT SNAPSHOT
      | READ WRITE
      | READ ONLY
    }
    
    BEGIN [WORK]
    
  • 提交事务:指将事务执行结果写入到数据表中,其使用的命令为COMMIT,具体语法如下所示:

    COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
    

    对于单条 SQL 语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。
    而如果使用START TRANSACTIONBEGIN开启且手动使用COMMIT进行提交的事务,则称为显示事务。
    :默认情况下,MySQL 会自动提交所有更改,即隐式事务会自动提交。可以通过设置autocommit标志来更改提交行为:

    SET autocommit = {0 | 1}
    

    其中:0表示禁止自动提交,1表示使能自动提交(默认为1)。
    :当禁止自动提交时,对表进行的修改不会自动进行持久化(但是可以查询得到),必须使用COMMIT来将数据持久化到磁盘上或ROLLBACK撤销修改。如下例子所示:

    # 禁止自动提交
    mysql> SET autocommit = 0;
    Query OK, 0 rows affected (0.00 sec)
    
    # 插入新数据
    mysql> INSERT INTO account(name, money) VALUES('wangwu',100);
    Query OK, 1 row affected (0.00 sec)
    
    # 查询(可以获取到新数据)
    mysql> SELECT * FROM account WHERE name = 'wangwu';
    +----+--------+-----------+
    | id | name   | money     |
    +----+--------+-----------+
    |  6 | wangwu | 100.00000 |
    +----+--------+-----------+
    1 row in set (0.00 sec)
    
    # 回滚
    mysql> ROLLBACK;
    Query OK, 0 rows affected (0.09 sec)
    
    # 插入的新数据已被撤销
    mysql> SELECT * FROM account WHERE name = 'wangwu';
    Empty set (0.00 sec)
    
    # 恢复自动提交
    mysql> set AUTOCOMMIT = 1;
    Query OK, 0 rows affected (0.00 sec)
    
  • 回滚事务:当事务中某环节出错时,可进行回滚操作,撤销之前执行成功的操作。
    回滚事务使用的命令为ROLLBACK,其具体语法如下所示:

    ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
    

    ROLLBACK语句默认回滚START TRANSACTIONBEGIN语句之间的操作,可以用来管理INSERTUPDATEDELETE操作,但是不能回退SELECT操作(因为这没有意义),也不能回退CREATEDROP操作。

  • 占位符:为了更加细粒度的控制事务回滚过程,可以通过在事务处理块中适当位置插入一个占位符,然后使用ROLLBACK TO回退到指定占位符中。
    占位符的相关操作命令为SAVEPOINT,其语法如下所示:

    # 创建占位符
    SAVEPOINT identifier
    # 回退到占位符处
    ROLLBACK [WORK] TO [SAVEPOINT] identifier
    # 释放占位符
    RELEASE SAVEPOINT identifier
    

    使用占位符的一个好处就是可以更加细粒度的控制事务处理过程,对于一些非关键位置的错误,完全可以通过占位符进行捕获回退,避免事务退出。

举个例子:现在假设表account中用户lisi转账 100 元给zhangsan,由于银行转账过程涉及不同用户间金额计算,这个操作过程要求具备原子性,因此使用事务进行处理,确保数据的一致性:

# 初始数据
mysql> SELECT * FROM account;
+----+----------+-----------+
| id | name     | money     | 
+----+----------+-----------+
| 1  | lisi     | 200.00000 |
| 5  | zhangsan | 200.00000 |
+----+----------+-----------+
2 rows in set (0.00 sec)

# 开启事务
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

# lisi - 100
mysql> UPDATE account SET money = money - 100 WHERE name = 'lisi';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# zhangsan + 100
mysql> UPDATE account SET money = money + 100 WHERE name = 'zhangsan';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# 提交事务
mysql> COMMIT;
Query OK, 0 rows affected (0.14 sec)

# 查看事务执行结果
mysql> SELECT * FROM account;
+----+----------+-----------+
| id | name     | money     |
+----+----------+-----------+
|  1 | lisi     | 100.00000 |
|  5 | zhangsan | 300.00000 |
+----+----------+-----------+
2 rows in set (0.01 sec)

你可能感兴趣的:(MySQL 学习笔记(二))