Mysql

一、 数据库范式

  • 第一范式(原子性):1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
  • 第二范式(完全依赖):2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性,有个主键
  • 第三范式(没有传递依赖):3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。

二、 存储过程

一些 SQL 语句的集合,中间加了点逻辑控制语句。

存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。

存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。

特性

1.有输入输出参数,可以声明变量,有if/else/case/while等控制语句,通过编写存储过程,可以实现复杂的逻辑功能

2.函数的普通特性:模块化,封装,代码复用

3.速度快,只有首次执行需要经过编译和优化步骤,后续被调用可以直接执行,省去以上步骤

-- 创建测试数据表
DROP TABLE IF EXISTS test1;
CREATE TABLE `test1` (
    `id` int(11) NOT NULL,
    `num1` int(11) NOT NULL DEFAULT '0',
    `num2` varchar(11) NOT NULL DEFAULT '',
    `type1` int(4) NOT NULL DEFAULT '0',
    `type2` int(4) NOT NULL DEFAULT '0',
    `str1` varchar(100) NOT NULL DEFAULT '',
    `str2` varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `num1` (`num1`),
    KEY `num2` (`num2`),
    KEY `type1` (`type1`),
    KEY `str1` (`str1`),
    KEY `str2` (`str2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 创建存储过程
DROP PROCEDURE IF EXISTS pre_test1;
DELIMITER 
CREATE PROCEDURE `pre_test1`()
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    WHILE i < 10000000 DO
        SET i = i + 1;
        SET @str1 = SUBSTRING(MD5(RAND()),1,20);
        -- 每100条数据str2产生一个null值
        IF i % 100 = 0 THEN
            SET @str2 = NULL;
        ELSE
            SET @str2 = @str1;
        END IF;
        INSERT INTO test1 (`id`, `num1`, `num2`,
        `type1`, `type2`, `str1`, `str2`)
        VALUES (CONCAT('', i), CONCAT('', i),
        CONCAT('', i), i%5, i%5, @str1, @str2);
        -- 事务优化,每一万条数据提交一次事务
        IF i % 10000 = 0 THEN
            COMMIT;
        END IF;
    END WHILE;
END;
// DELIMITER ;
-- 执行存储过程
CALL pre_test1();

三、 drop、delete 与 truncate

  • drop(丢弃数据): drop table 表名 ,直接将表都删除掉,在删除表的时候使用。
  • truncate (重置,清空数据) : truncate table 表名 ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
  • delete(删除数据) : delete from 表名 where 列名=值,删除某一行的数据,如果不加 where 子句和truncate table 表名作用类似。

drop 语句,此表的结构也会删除

truncate 和 drop 属于 DDL(Data Definition Language 数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发触发器( trigger)。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。

四、 数据库设计

  1. 需求分析 : 分析用户的需求,包括数据、功能和性能需求。
  2. 概念结构设计 : 主要采用 E-R 模型进行设计,画 E-R 图。
  3. 逻辑结构设计 : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
  4. 物理结构设计 : 主要是为所设计的数据库选择合适的存储结构和存取路径。
  5. 数据库实施 : 包括编程、测试和试运行
  6. 数据库的运行和维护 : 系统的运行与数据库的日常维护。

五、 UTF-8 和 UTF-8mb4

  • utf8utf8编码只支持1-3个字节 。 在 utf8 编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。
  • utf8mb4 : UTF-8 的完整实现。最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。

乱码的本质: 编码和解码时用了不同或者不兼容的字符集

六、 Mysql基础架构

img

  • 连接器:身份验证和权限相关

  • 查询缓存:执行查询语句的时候会先查询缓存(mysql8.0之后移除)

  • 分析器:sql语句分析器,检查语法正确

  • 优化器:按照mysql认为最优的方式执行

IO 成本: 即从磁盘把数据加载到内存的成本

CPU 成本:将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的成本

  • 执行器:执行语句,从存储引擎返回数据。会判断权限,没有会报错

db-mysql-sql-8

  • 插件式存储引擎:负责数据的存储和读取。

每次在执行 SQL 的时候都会将其数据加载到内存中,这块内存就是 InnoDB 中一个非常重要的组件:

缓冲池 Buffer Pool

db-mysql-sql-9

SQL 执行步骤:

  • innodb 存储引擎会在缓冲池中查找 id=1 的这条数据是否存在
  • 发现不存在,那么就会去磁盘中加载,并将其存放在缓冲池中
  • 该条记录会被加上一个独占锁(修改)

undo log文件:记录数据被修改前的样子

redo log buffer(先放在内存中去完成)

redo 日志文件:记录数据被修改后的样子

执行器调用存储引擎是怎么将一条 SQL 加载到缓冲池和记录哪些日志的,流程如下:

  • 准备更新一条 SQL 语句

  • MySQL(innodb)会先去缓冲池(BufferPool)中去查找这条数据,没找到就会去磁盘中查找,如果查找到就会将这条数据加载到缓冲池(BufferPool)中

  • 在加载到 Buffer Pool 的同时,会将这条数据的原始记录保存到 undo 日志文件中

  • innodb 会在 Buffer Pool 中执行更新操作

  • 更新后的数据会记录在 redo log buffer 中

  • MySQL 提交事务的时候,会将 redo log buffer 中的数据写入到 redo 日志文件中 刷磁盘可以通过 innodb_flush_log_at_trx_commit 参数来设置

    • 值为 0 表示不刷入磁盘
    • 值为 1 表示立即刷入磁盘
    • 值为 2 表示先刷到 os cache
  • myslq 重启的时候会将 redo 日志恢复到缓冲池中

bin log 日志文件:记录整个操作过程(mysql级别日志)

性质 redo Log bin Log
文件大小 redo log 的大小是固定的(配置中也可以设置,一般默认的就足够了) bin log 可通过配置参数max_bin log_size设置每个bin log文件的大小(但是一般不建议修改)。
实现方式 redo log是InnoDB引擎层实现的 bin log是 MySQL 层实现的,所有引擎都可以使用 bin log日志
记录方式 redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。(顺序读写) bin log 通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上
使用场景 redo log用于崩溃恢复(crash-safe) bin log 适用于主从复制和数据恢复

bin log 的刷盘策略

sync_bin log来修改,默认为 0,表示先写入 os cache,提交事务的时候,数据不会直接到磁盘中

建议将sync_bin log设置为 1 表示直接将数据写入到磁盘文件中。

  • STATMENT

基于 SQL 语句的复制。每一条会修改数据的 SQL 语句会记录到 bin log 中

  • ROW

基于行的复制。不记录每条SQL语句的上下文信息,仅需记录哪条数据被修改了

  • MIXED

基于 STATMENT 和 ROW 两种模式的混合复制。先使用 STATEMENT 模式保存 bin log ,对于 STATEMENT 模式无法复制的操作使用 ROW 模式

会将 redo log buffer 中的数据写入到redo log 文件中,同时也会将本次修改的数据记录到 bin log文件中,同时会将本次修改的bin log文件名和修改的内容在bin log中的位置记录到redo log中,最后还会在redo log最后写入 commit 标记,这样就表示本次事务被成功的提交了。

db-mysql-sql-13

MySQL 有一个后台线程,它会在某个时机将我们Buffer Pool中的脏数据刷到 MySQL 数据库中,这样就将内存和数据库的数据保持统一了。

存储引擎是基于表的,而不是数据库。

七、MyISAM 和 InnoDB 的区别

1.支持行级锁

2.支持事务

3.支持外键

4.支持数据库异常崩溃后的安全恢复

5.支持 MVCC

6.索引实现不一样。

7.支持真正的在线热备份

B+Tree 作为索引结构。

具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。

与红黑树的比较,B+ Tree 具有更少的查找次数,利用计算机预读特性。

InnoDB 引擎中,其数据文件本身就是索引文件。(聚簇索引)

MyISAM 支持压缩表和空间数据索引

八、MySQL 查询缓存

任何两个查询条件不同,查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。

缓存建立之后,表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。

升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁

对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存

select sql_no_cache count(*) from usr;

九、ACID

  1. 原子性Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;undolog保证
  2. 一致性Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
  3. 隔离性Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;MVCC保证
  4. 持久性Durabilily): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。redolog保证

🌈 只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的

十、 并发事务问题

  • 脏读(Dirty read): A事务对数据进行修改,却未提交到数据库,B事务使用了这个数据。B事务对数据的操作就是不正确的
  • 丢失修改(Lost to modify): A事务和B事务同时对一个数据进行修改,那么有一个修改就会丢失
  • 不可重复读(Unrepeatable read): A事务中多次读同一数据。B事务在中间有修改操作。A事务读取结果不一致
  • 幻读(Phantom read): A事务读取数据,同时B事务插入数据。A事务就会发现多了一些数据

不可重复读和幻读区别:不可重复读的重点是修改,幻读的重点在于新增或者删除

十一、 SQL隔离级别

  • READ-UNCOMMITTED(读取未提交) : 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交) : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读) : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(mysql默认级别)
  • SERIALIZABLE(可串行化) : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别 脏读 不可重复读 幻读
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。

MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提高性能

十二、锁分类

InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁

当我们执行 UPDATEDELETE 语句时,如果 WHERE条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有记录进行加锁

不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:

  • 共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
  • 排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
# 共享锁
SELECT ... LOCK IN SHARE MODE;
# 排他锁
SELECT ... FOR UPDATE;

意向锁:快速判断是否可以对某个表使用表锁。

  • 意向共享锁(Intention Shared Lock,IS 锁):事务有意向对表中的某些加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
  • 意向排他锁(Intention Exclusive Lock,IX 锁):事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的 IX 锁。

意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。

意向锁之间是互相兼容的。

意向锁和共享锁和排它锁互斥(这里指的是表级别的共享锁和排他锁,意向锁不会与行级的共享锁和排他锁互斥)。

InnoDB 有哪几类行锁?

MySQL InnoDB 支持三种行锁定方式:

  • 记录锁(Record Lock) :也被称为记录锁,属于单个行记录上的锁。
  • 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。
  • 临键锁(Next-key Lock) :Record Lock+Gap Lock,锁定一个范围,包含记录本身。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。

InnoDB 的默认隔离级别 REPEATABLE-READ(可重读)是可以解决幻读问题发生的,主要有下面两种情况:

  • 快照读 :由 MVCC 机制来保证不出现幻读。
  • 当前读 : 使用 Next-Key Lock 进行加锁来保证不出现幻读。

十三、数据类型

1. 数值类型
a. 整型 ----------
    类型         字节     范围(有符号位)
    tinyint     1字节    -128 ~ 127      无符号位:0 ~ 255
    smallint    2字节    -32768 ~ 32767
    mediumint   3字节    -8388608 ~ 8388607
    int         4字节
    bigint      8字节
    int(M)  M表示总位数
b. 浮点型 ----------
    类型             字节     范围
    float(单精度)     4字节
    double(双精度)    8字节
    浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。
        不同于整型,前后均会补填0.
    定义浮点型时,需指定总位数和小数位数。
        float(M, D)     double(M, D)
        M表示总位数,D表示小数位数。
c. 定点数 ----------
    decimal -- 可变长度
    decimal(M, D)   M也表示总位数,D表示小数位数。
    保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。
    将浮点数转换为字符串来保存,每9位数字保存为4个字节。
2. 字符串类型
-- a. char, varchar ----------
    char    定长字符串,速度快,但浪费空间
    varchar 变长字符串,速度慢,但节省空间
    M表示能存储的最大长度,此长度是字符数,非字节数。
    不同的编码,所占用的空间不同。
    char,最多255个字符,与编码无关。
    varchar,最多65535字符,与编码有关。
b. blob, text ----------
    blob 二进制字符串(字节字符串)
        tinyblob, blob, mediumblob, longblob
    text 非二进制字符串(字符字符串)
        tinytext, text, mediumtext, longtext
    text 在定义时,不需要定义长度,也不会计算总长度。
    text 类型在定义时,不可给default值
-- c. binary, varbinary ----------
    类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。
    char, varchar, text 对应 binary, varbinary, blob.
3. 日期时间类型
    一般用整型保存时间戳,因为PHP可以很方便的将时间戳进行格式化。
    datetime    8字节    日期及时间    1000-01-01 00:00:00 到 9999-12-31 23:59:59
    date        3字节    日期         1000-01-01 到 9999-12-31
    timestamp   4字节    时间戳       19700101000000 到 2038-01-19 03:14:07
    time        3字节    时间         -838:59:59 到 838:59:59
    year        1字节    年份         1901 - 2155
4. 枚举和集合
-- 枚举(enum) ----------
enum(val1, val2, val3...)
    在已知的值中进行单选。最大数量为65535.
    枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递增。
    表现为字符串类型,存储却是整型。
    NULL值的索引是NULL。
    空字符串错误值的索引值是0。
-- 集合(set) ----------
set(val1, val2, val3...)
    create table tab ( gender set('男', '女', '无') );
    insert into tab values ('男, 女');
    最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。
    当创建表时,SET成员值的尾部空格将自动被删除。

十四、索引

磁盘IO和预读

磁盘读取数据靠的是机械运动,花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分。

  • 寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;
  • 旋转延迟就是我们经常听说的磁盘转速。一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms
  • 传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计

访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右(非常长)

计算机硬件延迟的对比图:(参考)

db-mysql-index-improve-mt-1

磁盘IO高昂操作,操作系统优化,当一次IO时把相邻的数据也都读取到内存缓冲区内。(一般为4k或8k)读取一页内的数据时候,实际上才发生了一次IO。

普通索引,主键索引,联合索引,全文索引

索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。

hash索引:查询单条快,范围查询慢(支持精确查找,无法用于部分查找和范围查找)
btree索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它),适合排序范围等操作,支持多列联合索引的最左匹配

真实的情况是,3层的b+树可以表示上百万的数据。

IO次数取决于b+数的高度h,

建索引的几大原则

  • 最左前缀匹配原则
  • =和in可以乱序
  • 字段不重复(比例越大我们扫描的记录数越少)
  • 索引列不能参与计算,保持列“干净”
  • 尽量的扩展索引,不要新建索引

聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。

优点:查询速度非常的快

缺点:依赖于有序的数据,更新代价大

非聚集索引即索引结构和数据分开存放的索引。也依赖于有序的数据

非聚集索引的叶子节点是不存放数据的。可能会二次查询

覆盖索引 :一个索引包含(或者说覆盖)所有需要查询的字段的值

联合索引:多个字段创建索引

十五、日志

MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类

二进制日志 binlog(归档日志)和事务日志 redo log(重做日志)和 undo log(回滚日志)较重要

redo log(重做日志)InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。

redo log 日志以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。采用的是环形数组形式

binlog 逻辑日志:记录所有涉及更新数据的逻辑操作,并且是顺序写。

数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

undo log回滚日志:事务的原子性需要在异常发生时,对已经执行的操作进行回滚。恢复机制是通过 回滚日志(undo log) 实现的,回滚日志会先于数据持久化到磁盘上

主从复制与读写分离

主从复制:涉及三个线程

binlog 线程 : 负责将主服务器上的数据更改写入二进制日志中。

I/O 线程 : 负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。

SQL 线程 : 负责读取中继日志并重放其中的 SQL 语句。

读写分离,数据热备,架构性能扩展,高可用

master-slave

读写分离

  • 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
  • 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
  • 增加冗余,提高可用性。

代理方式来实现

master-slave-proxy

执行计划Explain

使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈。

官方文档

explain 两个变种

  • explain extended:会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通过 show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么。额外还有 filtered 列,是一个半分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的id值比当前表id值小的表)。
  • explain partitions:相比 explain 多了个 partitions 字段,如果查询是基于分区表的话,会显示查询将访问的分区。

explain执行计划信息

id select_type table type possible_keys key key_len ref rows extra

比较重要的字段有id,type,key,rows,extra

1. id

查询中执行select句的序列号,按出现顺序增长

id值越大,优先级越高,id相同则从上往下执行,id为NULL最后执行

这里写图片描述

2. select_type:

查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询

  • SIMPLE:简单的select查询,查询中不包含子查询或者union
  • PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary
  • SUBQUERY:在select 或 where列表中包含了子查询 (不在 from 子句中)
  • DERIVED:在from列表中包含的子查询,mysql或递归执行这些子查询,把结果放在临时表里
  • UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived
  • UNION RESULT:从union表获取结果的select

这里写图片描述

3. table

这一列表示 explain 的一行正在访问哪个表。

当 from 子句中有子查询时,table列是格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查 询。

当有 union 时,UNION RESULT 的 table 列的值为,1和2表示参与 union 的 select 行id。

4. type

访问类型,决定如何查找表中的行,查找数据行记录的大概范围

结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,好的sql查询至少达到range级别,最好能达到ref

  • Null : mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表
  • system:表只有一行记录(等于系统表),这是const类型的特例
  • const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。

这里写图片描述

  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。

这里写图片描述

  • ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体

    • 简单 select 查询,使用普通索引(非唯一索引)

    • 关联表查询,idx_film_actor_id是film_id和actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分

      mysql> explain select film_id from film left join film_actor on film.id = film_actor.film_id;
      

这里写图片描述

  • range:范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。

这里写图片描述

  • index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接 对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这 种通常比ALL快一些(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)

这里写图片描述

  • ALL:即全表扫描,扫描聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。

这里写图片描述

5. possible_keys

查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用

possible_keys列有值,而 key 显示 NULL,通常是表中数据少,mysql选择全表扫描

该列为null,则表示没有相关的索引,可以考虑建索引了

6. key

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

# 强制mysql使用或忽视possible_keys列中的索引
explain select  CustName,count(1) c from WorkOrder  ignore index(ix_date) 

查询中如果使用了覆盖索引,则该索引仅出现在key列表中

这里写图片描述

这里写图片描述

7. key_len

表示索引中使用的字节数,越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的。

通过这个值可以算出具体使用了索引中的哪些列。

key_len计算规则如下:

  • 字符串,char(n)和varchar(n),5.0.3以后版本中,n均代表字符数,而不是字节数,如果是utf-8,一个数字 或字母占1个字节,一个汉字占3个字节

    • char(n):如果存汉字长度就是 3n 字节
    • varchar(n):如果存汉字则长度是 3n + 2 字节,加的2字节用来存储字符串长度,因为 varchar是变长字符串
  • 数值类型

    • tinyint:1字节

    • smallint:2字节

    • int:4字节

    • bigint:8字节

  • 时间类型

    • date:3字节

    • timestamp:4字节

    • datetime:8字节

  • 如果字段允许为 NULL,需要1字节记录是否为 NULL

索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索 引。

8. ref

显示索引key列表查找值所用到的列或常量【const(常量),字段名(例:film.id)】

9. rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,不是结果集里的行数

10.Extra

十分重要的额外信息:

  • Using filesort
    mysql对数据使用一个外部的索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。一般也是要考虑使用索引来优化

这里写图片描述

索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了,mysql内部必须再实现一次“文件排序”

  • Using temporary

使用临时表保存中间结果,常见于order by 和 group by ,首选用索引优化

这里写图片描述

  • Using index

使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)
如果没有同时出现Using where,表明索引用来读取数据而非执行查找动作

这里写图片描述

覆盖索引(Covering Index):select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖

  • Using where

    使用 where 语句来处理结果,并且查询的列未被索引覆盖

  • Using index condition

    查询的列不完全被索引覆盖,where条件中是一个前导列的范围;

  • Using join buffer

    使用了链接缓存

  • Impossible WHERE

    where子句的值总是false,不能用来获取任何元祖

    这里写图片描述

  • select tables optimized away
    在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化

  • distinct

    优化distinct(去重)操作,在找到第一个匹配的元祖后即停止找同样值得动作

慢sql查询处理

1、开启慢查询日志,准确定位到哪个sq语句出现了问题
2、分析sql语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并
不需要的列,对语句进行分析以及重写
3、分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中
索引
4、如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分
表。

MVCC

数据库并发场景有三种,分别为:
读读:不存在任何问题,也不需要并发控制

读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
写写:有线程安全问题,可能存在更新丢失问题

MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个
版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MCC可以为数据库解决一下问
题:
1、在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并
发读写的性能
2、解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题

实现原理

mvcc的实现原理主要依赖于记录中三个隐藏行字段undolog,read views来实现的。
隐藏行字段
DB_TRX_ID
6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
DB_ROLL_PTR
7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
DB_ROW_ID
6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id
示例:

DB_TRX_ID DB_ROW_ID DB_ROLL_PTR
1 1 0x1110

undo log

undolog被称之为回滚日志,表示在进行insert,delete,update操作的时候产生的方便回滚的日志
当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃
当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需
要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purg线程统一清除(当数据发生更新和删除操作的时候都只是设置一下老记录的deleted_bit,并不是真正的将过时的记录删除,因为为了节省磁盘空间,innodb有专门的purge线程来清除deleted_bit为true的记录,如果某个记录的deleted_id为true,
并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定时可以被清除的)

undolog记录链

image-20220713152513838

image-20220713152609600

从上述的一系列图中,大家可以发现,不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog
生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,随尾就是最早的旧记录。

read views

Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统.
当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的,
其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,
也有可能读取的是当前行记录的undolog中某个版本的数据
Read View遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与
系统当前其他活跃事务的id去对t比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过
DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件
的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本数据。
Read View的可见性规则如下所示:

筒先要知道Read View中的三个全局属性:
trx_list:一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID(1,2,3)
up_limit_id:记录trx_list列表中事务ID最小的ID (1)
low_limit_id: Read Views生成时刻系统尚未分配的下一个事务ID(5)
具体的比较规则如下:
1、首先比较DB_TRX_ID<up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断
2、接下来判断DB_TRX_ID>=low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才
出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断
3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read Views生成之前就已经开始commit,那么修改的结果是能够看见的。

image-20220713154804215

从上述表格中,当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View视图

image-20220713155119358

image-20220713155543127

当事务2在快照读该行记录的是,会拿着该行记录的DB_TRXD去跟up_limit_id,lower_limit_id和活跃事务列表进行比较,判读事务2能看到该行记录的版本是那个。
具体流程如下:先拿该行记录的事务D(4)去跟Read View中的up_limit_id相比较,判断是小于,通过对比发现不小于,所以不符合条件,继续判断4是否大于等于low_limit_id,通过比较发现也不大于,所以不符合条件,判断事务4是处理tXst列表中,发现不再次列表中,那么符合可见性条件,所以事务4修改后提交的最新结果对事务2的快照是可见的,因此,事务2读取到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度的最新版本。

在RC隔离级别下,是每个快照读都会生成并获取最新的Read View,而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View

分表分库

水平切分

63c2909f-0c5f-496f-9fe5-ee9176b31aba

一个表中的记录拆分到多个结构相同的表中

垂直切分

e130e5b8-b19a-4f1e-b860-223040525cf6

按照列的关系密集程度进行切分

Sharding 策略

  • 哈希取模: hash(key) % NUM_DB
  • 范围: 可以是 ID 范围也可以是时间范围
  • 映射表: 使用单独的一个数据库来存储映射关系

Sharding 存在的问题及解决方案

事务问题: 分布式事务 XA接口

链接:分解成多个单表查询,然后在用户程序中进行 JOIN

ID 唯一性:

  • 使用全局唯一 ID: GUID
  • 为每个分片指定一个 ID 范围
  • 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)

美团: SQL优化工具SQLAdvisor

https://github.com/Meituan-Dianping/SQLAdvisor

posted @ 2022-10-14 17:31  浮沉丶随心  阅读(82)  评论(0编辑  收藏  举报