mysql
2020-03-01 22:02:41 626 举报
AI智能生成
mysql高级总结
作者其他创作
大纲/内容
引擎
MyISAM(非聚集索引)
是一种非事务性的引擎,提供高速存储和检索,以及全文搜索能力,适合数据仓库等查询频繁的应用
不支持事务;使用表级锁,并发性差;主机宕机后,MyISAM表易损坏,灾难恢复性不佳;
可以配合锁,实现操作系统下的复制备份、迁移
可以配合锁,实现操作系统下的复制备份、迁移
只缓存索引,数据的缓存是利用操作系统缓冲区来实现的;可能引发过多的系统调用且效率不佳;
数据紧凑存储,因此可获得更小的索引和更快的全表扫描性能
数据紧凑存储,因此可获得更小的索引和更快的全表扫描性能
innoDB(聚集索引)
innodb主键使用自增bigint效率比uuid高
1.方便比较大小
2.不会破坏B+TREE结构
1.方便比较大小
2.不会破坏B+TREE结构
innodb使用b+tree存索引和数据 不使用hash的原因:
范围查找使用hash不合适,需要全表扫描,hash(主键)直接存储到位置,因此一般使用B+Tree
范围查找使用hash不合适,需要全表扫描,hash(主键)直接存储到位置,因此一般使用B+Tree
支持事务的引擎。给MySQL提供了具有提交,回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎
使用行级锁,支持外键关联,支持热备份
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制不是所谓的间隙锁(Next-Key锁)。InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求;另一方面,是为了满足其恢复和复制的需要。
聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引
辅助索引
InnoDB采用的方式是在叶子页中保存主键值,通过这个主键值来回表查询到一条完整记录,因此按辅助索引检索实际上进行了二次查询,
效率肯定是没有按照主键检索高的
效率肯定是没有按照主键检索高的
由于每个辅助索引都包含主键索引,因此,为了减小辅助索引所占空间,我们通常希望 InnoDB 表中的主键索引尽量定义得小一些(值得一提的是,MySIAM会使用前缀压缩技术使得索引变小,而InnoDB按照原数据格式进行存储。),并且希望InnoDB的主键是自增长的,因为如果主键并非自增长,插入时,由于写入时乱序的,会使得插入效率变低。
事务
ACID属性
持久性(Durable)
事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持
原子性(Atomicity)
事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行
隔离性(Isolation)
数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。
这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然
这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然
一致性(Consistent)
在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,
以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的
以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的
并发事务处理带来的问题
更新丢失(Lost Update)
两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,
就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新
就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新
脏读(Dirty Reads)
事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。
此时,如果B事务回滚,A读取的数据无效,不符合一致性要求
此时,如果B事务回滚,A读取的数据无效,不符合一致性要求
不可重读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,
却发现其读出的数据已经发生了改变、
或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性
却发现其读出的数据已经发生了改变、
或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性
幻读(Phantom Reads)
个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,
这种现象就称为“幻读”
这种现象就称为“幻读”
脏读是事务B里面修改了数据,幻读是事务B里面新增了数据
事务隔离级别
分类
读未提交(Read uncommitted)
脏读/不可重复读/幻读都可能
读已提交(Read uncommitted)
脏读不可能,不可重复读/幻读都可能
可重复读(Repeatable read)
不可重复读/脏读不可能,幻读都可能
可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本)
insert、update和delete会更新版本号,是当前读(当前版本)
insert、update和delete会更新版本号,是当前读(当前版本)
要避免幻读可以用间隙锁在Session _1下面执行update acc ount se t name ='zhuge'
where i d> 10and id<= 20;,则其他Session没法插入这个范围内的数据
where i d> 10and id<= 20;,则其他Session没法插入这个范围内的数据
可串行化(Serializable)
脏读/不可重复读/幻读都不可能
mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,
这种隔离级别并发性极低,开发中很少会用到
这种隔离级别并发性极低,开发中很少会用到
查看事务隔离级别
show variables like 'transaction_isolation';
select @@transaction_isolation;
默认的事务隔离级别(Repeatable read)
分表
横向:表字段相同,数据量太大
union会去重,union all不会
纵向:一个表存基本信息,另外一个表存详情
join
分区
优缺点
优点
和单个磁盘或者文件系统分区相比,可以存储更多数据
优化查询。在where子句中包含分区条件时,可以只扫描必要的一个或者多个分区来提高查询效率;
同时在涉及sum()和count()这类聚合函数的查询时,可以容易的在每个分区上并行处理,最终只需要汇总所有分区得到的结果
对于已经过期或者不需要保存的数据,可以通过删除与这些数据有关的分区来快速删除数据
跨多个磁盘来分散数据查询,以获得更大的查询吞吐量
优化查询。在where子句中包含分区条件时,可以只扫描必要的一个或者多个分区来提高查询效率;
同时在涉及sum()和count()这类聚合函数的查询时,可以容易的在每个分区上并行处理,最终只需要汇总所有分区得到的结果
对于已经过期或者不需要保存的数据,可以通过删除与这些数据有关的分区来快速删除数据
跨多个磁盘来分散数据查询,以获得更大的查询吞吐量
分区方式
Range
基于属于一个给定连续区间的列值,把多行分配给分区。
这些区间要连续且不能相互重叠,使用VALUES LESS THAN操作符来进行定义
这些区间要连续且不能相互重叠,使用VALUES LESS THAN操作符来进行定义
CREATE TABLE employees (
id INT NOT NULL,
NAME VARCHAR ( 30 ),
hired DATE NOT NULL DEFAULT '2018-12-01',
job VARCHAR ( 30 ) NOT NULL,
dept_id INT NOT NULL
)
PARTITION BY RANGE ( dept_id )(
PARTITION p0 VALUES LESS THAN ( 6 ),
PARTITION p1 VALUES LESS THAN ( 11 ),
PARTITION p2 VALUES LESS THAN ( 16 ),
PARTITION p3 VALUES LESS THAN ( 21 ) );
id INT NOT NULL,
NAME VARCHAR ( 30 ),
hired DATE NOT NULL DEFAULT '2018-12-01',
job VARCHAR ( 30 ) NOT NULL,
dept_id INT NOT NULL
)
PARTITION BY RANGE ( dept_id )(
PARTITION p0 VALUES LESS THAN ( 6 ),
PARTITION p1 VALUES LESS THAN ( 11 ),
PARTITION p2 VALUES LESS THAN ( 16 ),
PARTITION p3 VALUES LESS THAN ( 21 ) );
1)、当需要删除一个分区上的"旧的"数据时,只删除分区即可。如果你使用上面最近的那个例 子给出 的分区方案,你只需简单地使用"ALTER TABLE employees DROP PARTITION p0;"来删除所有在1991年前就已经停止工作的雇员相对应的所有行。对于有大量行的表,这比 运行一个如“DELETE FROM employees WHERE YEAR (separated) <= 1990;”这样的一个DELETE查询要有效得多。
2)、想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。
3)、 经常运行直接依赖于用于 分割 表的 列的查询。例如,当执行一个如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY dept_id;”这样的查询时,MySQL可以很 迅速地确定只有分区p2需要扫描 ,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。
2)、想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。
3)、 经常运行直接依赖于用于 分割 表的 列的查询。例如,当执行一个如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY dept_id;”这样的查询时,MySQL可以很 迅速地确定只有分区p2需要扫描 ,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。
List
设置若干个固定值进行分区,如果某个字段的值在这个设置的值列表中就会被分配到该分区。
适用于字段的值区分度不高的,或者值是有限的,特别是像枚举这样特点的列
适用于字段的值区分度不高的,或者值是有限的,特别是像枚举这样特点的列
CREATE TABLE employees (
id INT NOT NULL,
NAME VARCHAR ( 30 ),
hired DATE NOT NULL DEFAULT '2015-12-10',
job_code INT,
store_id INT )
PARTITION BY LIST ( store_id )(
PARTITION pQY VALUES IN ( 3, 5, 6, 17 ),
PARTITION pJN VALUES IN ( 1, 10, 11, 19 ),
PARTITION pCH VALUES IN ( 4, 12, 14, 18 ),
PARTITION pJJ VALUES IN ( 2, 9, 13, 16 ),
PARTITION pGX VALUES IN ( 7, 8, 15, 20 ));
id INT NOT NULL,
NAME VARCHAR ( 30 ),
hired DATE NOT NULL DEFAULT '2015-12-10',
job_code INT,
store_id INT )
PARTITION BY LIST ( store_id )(
PARTITION pQY VALUES IN ( 3, 5, 6, 17 ),
PARTITION pJN VALUES IN ( 1, 10, 11, 19 ),
PARTITION pCH VALUES IN ( 4, 12, 14, 18 ),
PARTITION pJJ VALUES IN ( 2, 9, 13, 16 ),
PARTITION pGX VALUES IN ( 7, 8, 15, 20 ));
range columns
create table rc3 (
a int,
b int
)
partition by range columns(a, b) (
partition p01 values less than (0, 10),
partition p02 values less than (10, 10),
partition p03 values less than (10, 20),
partition p04 values less than (10, 35)
);
insert into rc3(a, b) values(1, 10);
a int,
b int
)
partition by range columns(a, b) (
partition p01 values less than (0, 10),
partition p02 values less than (10, 10),
partition p03 values less than (10, 20),
partition p04 values less than (10, 35)
);
insert into rc3(a, b) values(1, 10);
hash分区
常规hash分区
常规hash分区使用的是取模算法,对应一个表达式expr是可以计算出它被保存到哪个分区中,N = MOD(expr, num)
线性hash分区
线性hash分区使用的是一个线性的2的幂运算法则
常规hash分区在管理上带来了的代价太大,不适合需要灵活变动分区的需求。为了降低分区管理上的代价,mysql提供了线性hash分区,分区函数是一个线性的2的幂的运算法则。同样线性hash分区的记录被存在那个分区也是能被计算出来的。线性hash分区的优点是在分区维护(增加、删除、合并、拆分分区)时,mysql能够处理的更加迅速,缺点是:对比常规hash分区,线性hash各个分区之间数据的分布不太均衡
key分区
按照key进行分区非常类似于按照hash进行分区,只不过hash分区允许使用用户自定义的表达式,
而key分区不允许使用用于自定义的表达式,需要使用mysql服务器提供的hash函数,
同时hash分区只支持整数分区,而key分区支持使用出blob or text类型外的其他类型的列作为分区键
而key分区不允许使用用于自定义的表达式,需要使用mysql服务器提供的hash函数,
同时hash分区只支持整数分区,而key分区支持使用出blob or text类型外的其他类型的列作为分区键
partition by key(expr) partitions num;
-- 不指定默认首选主键作为分区键,在没有主键的情况下会选择非空唯一键作为分区键
partition by key() partitions num;
-- linear key
partition by linear key(expr)
-- 不指定默认首选主键作为分区键,在没有主键的情况下会选择非空唯一键作为分区键
partition by key() partitions num;
-- linear key
partition by linear key(expr)
子分区
是分区表中对每个分区的再次分割,又被称为复合分区,支持对range和list进行子分区,
子分区即可以使用hash分区也可以使用key分区。
复合分区适用于保存非常大量的数据记录
子分区即可以使用hash分区也可以使用key分区。
复合分区适用于保存非常大量的数据记录
create table ts (
id int,
purchased date
)
partition by range(year(purchased))
subpartition by hash(to_days(purchased)) subpartitions 2
(
partition p0 values less than (1990),
partition p0 values less than (2000),
partition p0 values less than maxvalue
);
id int,
purchased date
)
partition by range(year(purchased))
subpartition by hash(to_days(purchased)) subpartitions 2
(
partition p0 values less than (1990),
partition p0 values less than (2000),
partition p0 values less than maxvalue
);
管理分区
-- 删除list或者range分区(同时删除分区对应的数据)
alter table <table> drop partition <分区名称>;
-- 新增分区
-- range添加新分区
alter table <table> add partition(partition p4 values less than MAXVALUE);
-- list添加新分区
alter table <table> add partition(partition p4 values in (25,26,28));
-- hash重新分区
alter table <table> add partition partitions 4;
-- key重新分区
alter table <table> add partition partitions 4;
-- 子分区添加新分区,虽然我没有指定子分区,但是系统会给子分区命名的
alter table <table> add partition(partition p3 values less than MAXVALUE);
-- range重新分区
ALTER TABLE user REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES LESS THAN MAXVALUE);
-- list重新分区
ALTER TABLE <table> REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES in (1,2,3,4,5));
alter table <table> drop partition <分区名称>;
-- 新增分区
-- range添加新分区
alter table <table> add partition(partition p4 values less than MAXVALUE);
-- list添加新分区
alter table <table> add partition(partition p4 values in (25,26,28));
-- hash重新分区
alter table <table> add partition partitions 4;
-- key重新分区
alter table <table> add partition partitions 4;
-- 子分区添加新分区,虽然我没有指定子分区,但是系统会给子分区命名的
alter table <table> add partition(partition p3 values less than MAXVALUE);
-- range重新分区
ALTER TABLE user REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES LESS THAN MAXVALUE);
-- list重新分区
ALTER TABLE <table> REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES in (1,2,3,4,5));
优化
步骤:
1.开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来;
2.EXPLAIN+慢SQL分析;
3.SHOW profile,查询SQL在MySQL服务器里面的执行细节和生命周期情况
4.具体优化
1.开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来;
2.EXPLAIN+慢SQL分析;
3.SHOW profile,查询SQL在MySQL服务器里面的执行细节和生命周期情况
4.具体优化
explain
语法explain select * from xxl_job_log l where l.job_id in (select id from xxl_job_info)
字段说明
id:表示查询中执行select子句或操作表的顺序
id相同,执行顺序由上至下;
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行;
id相同不同,都存在
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行;
id相同不同,都存在
select_type:表示查询的类型,主要用于区别普通查询,联合查询,子查询等复杂查询
https://www.cnblogs.com/danhuangpai/p/8475458.html
https://www.cnblogs.com/danhuangpai/p/8475458.html
SIMPLE:简单的select查询,查询中不包括子查询或者UNION
PRIMARY:查询中若包括任何复杂的子部分,最外层查询则被标记为PRIMARY
SUBQUERY:在select或where列表中包含了子查询
DERIVED: 在FROM列表中,包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,
把结果放在临时表里
把结果放在临时表里
UNION: 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,
外侧SELECT将被标记为 DERIVE
外侧SELECT将被标记为 DERIVE
UNION RESULT: 从UNION表获取结果的SELECT
table:查询的表
type:在表中找到所需行的方式 ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
ALL:全部扫描,效率低
index:Full Index Scan,index与ALL区别为index类型只遍历索引树
explain select * from film; file中有name字段为索引
explain select * from film; file中有name字段为索引
range:只检索给定范围的行,使用一个索引来选择行
explain select * from employee where rec_id < 3其中rec_id为主键
explain select * from employee where rec_id < 3其中rec_id为主键
ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
查找条件列使用了索引而且不为主键和unique。其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。下面为了演示这种情形,给employee表中的name列添加一个普通的key(值允许重复)
explain select * from film where name = "film1" ref name是film中的普通索引
查找条件列使用了索引而且不为主键和unique。其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。下面为了演示这种情形,给employee表中的name列添加一个普通的key(值允许重复)
explain select * from film where name = "film1" ref name是film中的普通索引
eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
eq_ref 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个?什么情况下结果集只有一个呢!那便是使用了主键或者唯一性索引进行查找的情况,比如根据学号查找某一学校的一名同学,在没有查找前我们就知道结果一定只有一个,所以当我们首次查找到这个学号,便立即停止了查询。这种连接类型每次都进行着精确查询,无需过多的扫描,因此查找效率更高,当然列的唯一性是需要根据实际情况决定的
explain select * from film_actor left join film on film_actor.film_id= film.id eq_ref 根据主键或唯一索引查询film
eq_ref 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个?什么情况下结果集只有一个呢!那便是使用了主键或者唯一性索引进行查找的情况,比如根据学号查找某一学校的一名同学,在没有查找前我们就知道结果一定只有一个,所以当我们首次查找到这个学号,便立即停止了查询。这种连接类型每次都进行着精确查询,无需过多的扫描,因此查找效率更高,当然列的唯一性是需要根据实际情况决定的
explain select * from film_actor left join film on film_actor.film_id= film.id eq_ref 根据主键或唯一索引查询film
const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
select 1 from actor where id =1 const
explain extended select * from (select * from film where id =1) tmp 临时表中只有一条记录,system
select 1 from actor where id =1 const
explain extended select * from (select * from film where id =1) tmp 临时表中只有一条记录,system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成explain select min(id) from film id是主键
possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null),可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mys ql认为索引对此查询帮助不大,选择了全表查询
key:显示MySQL实际决定使用的键(索引)
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,
并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)不损失精确性的情况下,长度越短越好
key_len计算规则如下:
字符串
char(n):n字节长度
varchar(n):2字节存储字符串长度,如果是utf-8,则长度3n + 2
数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节
时间类型
date:3字节timestamp:4字节
datetime:8字节如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)不损失精确性的情况下,长度越短越好
key_len计算规则如下:
字符串
char(n):n字节长度
varchar(n):2字节存储字符串长度,如果是utf-8,则长度3n + 2
数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节
时间类型
date:3字节timestamp:4字节
datetime:8字节如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
ref:列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
rows:估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
filtered:一个半分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的id值比当前表id值小的表)
Extra
Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,
这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by;distinct
Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。
如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能
如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能
Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
No tables used:Query语句中使用from dual 或不含任何from子句
缺点:
• EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
• EXPLAIN不考虑各种Cache
• EXPLAIN不能显示MySQL在执行查询时所作的优化工作
• 部分统计信息是估算的,并非精确值
• EXPLAIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划
• EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
• EXPLAIN不考虑各种Cache
• EXPLAIN不能显示MySQL在执行查询时所作的优化工作
• 部分统计信息是估算的,并非精确值
• EXPLAIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划
SHOW WARNINGS 在explain执行后执行,查看翻译后的sql
最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列
索引优化规则
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei'; 使用索引
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei'; 未使用索引
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei'; 使用索引
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei'; 未使用索引
存储引擎不能使用索引中范围条件右边的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager'; 使用索引
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager'; 未使用索引
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager'; 使用索引
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager'; 未使用索引
尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager'; 只查询索引不用查询具体的数据,效率更高
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager'; 只查询索引不用查询具体的数据,效率更高
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
is null,is not null 也无法使用索引
like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
字符串不加单引号索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
or 只有两边都有索引才走索引,如果都没有或者只有一个是不走索引的
in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内
union all 不去重复,union去重复,union使用了临时表,应尽量避免使用临时表
order by如果根据多个值进行排序,那么排序方式必须保持一致,要么同时升续,要么同时降续,排序方式不一致不走索引
优化方式
优化数据库表结构的设计
字段的数据类型
不同的数据类型的存储和检索方式不同,对应的性能也不同,所以说要合理的选用字段的数据类型。比如人的年龄用无符号的unsigned tinyint即可,没必要用integer
数据类型的长度
数据库最终要写到磁盘上,所以字段的长度也会影响着磁盘的I/O操作,如果字段的长度很大,那么读取数据也需要更多的I/O, 所以合理的字段长度也能提升数据库的性能。比如用户的手机号11位长度,没必要用255个长度
表的存储引擎
分库分表
数据库参数配置优化
主从复制,读写分离
数据库编码: 采用utf8mb4而不使用utf8
字段名
MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、 表名、字段名,都不允许出现任何大写字母,避免节外生枝。
一般所有表都要有id, id必为主键,类型为bigint unsigned,单表时自增、步长为1; 有些特殊场景下(如在高并发的情况下该字段的自增可能对效率有比价大的影响)id是通过程序计算出来的一个唯一值而不是通过数据库自增长来实现的。
一般情况下主键id和业务没关系的,例如订单号不是主键id,一般是订单表中的其他字段,一般订单号order_code为字符类型
一般情况下每张表都有着四个字段create_id,create_time,update_id,update_time, 其中create_id表示创建者id,create_time表示创建时间,update_id表示更新者id,update_time表示更是时间,这四个字段的作用是为了能够追踪数据的来源和修改
最好不要使用备用字段(个人观点), 禁用保留字,如 desc、range、match、delayed 等
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否), 任何字段如果为非负数,必须是unsigned。表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除
如果某个值能通过其他字段能计算出来就不需要用个字段来存储,减少存储的数据
为了提高查询效率,可以适当的数据冗余,注意是适当
强烈建议不使用外键, 数据的完整性靠程序来保证
单条记录大小禁止超过8k, 一方面字段不要太多,有的都能上百,甚至几百个,另一方面字段的内容不易过大像文章内容等这种超长内容的需要单独存到另一张表
一般所有表都要有id, id必为主键,类型为bigint unsigned,单表时自增、步长为1; 有些特殊场景下(如在高并发的情况下该字段的自增可能对效率有比价大的影响)id是通过程序计算出来的一个唯一值而不是通过数据库自增长来实现的。
一般情况下主键id和业务没关系的,例如订单号不是主键id,一般是订单表中的其他字段,一般订单号order_code为字符类型
一般情况下每张表都有着四个字段create_id,create_time,update_id,update_time, 其中create_id表示创建者id,create_time表示创建时间,update_id表示更新者id,update_time表示更是时间,这四个字段的作用是为了能够追踪数据的来源和修改
最好不要使用备用字段(个人观点), 禁用保留字,如 desc、range、match、delayed 等
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否), 任何字段如果为非负数,必须是unsigned。表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除
如果某个值能通过其他字段能计算出来就不需要用个字段来存储,减少存储的数据
为了提高查询效率,可以适当的数据冗余,注意是适当
强烈建议不使用外键, 数据的完整性靠程序来保证
单条记录大小禁止超过8k, 一方面字段不要太多,有的都能上百,甚至几百个,另一方面字段的内容不易过大像文章内容等这种超长内容的需要单独存到另一张表
字段类型
字符类型
不同存储引擎对char和varchar的使用原则不同,myisam:建议使用国定长度的数据列代替可变长度。
innodb:建议使用varchar,大部分表都是使用innodb,所以varchar的使用频率更高
innodb:建议使用varchar,大部分表都是使用innodb,所以varchar的使用频率更高
数值类型
金额类型的字段尽量使用long用分表示,尽量不要使用bigdecimal,严谨使用float和double因为计算时会丢失经度
如果需要使用小数严谨使用float,double,使用定点数decimal,decimal实际上是以字符串的形式存储的,所以更加精确,java中与之对应的数据类型为BigDecimal
如果值为非负数,一定要使用unsigned,无符号不仅能防止负数非法数据的保存,而且还能增大存储的范围
不建议使用ENUM、SET类型,使用TINYINT来代替
如果需要使用小数严谨使用float,double,使用定点数decimal,decimal实际上是以字符串的形式存储的,所以更加精确,java中与之对应的数据类型为BigDecimal
如果值为非负数,一定要使用unsigned,无符号不仅能防止负数非法数据的保存,而且还能增大存储的范围
不建议使用ENUM、SET类型,使用TINYINT来代替
日期类型
根据实际需要选择能够满足应用的最小存储日期类型。
如果应用只需要记录年份,那么仅用一个字节的year类型。
如果记录年月日用date类型, date占用4个字节,存储范围10000-01-01到9999-12-31
如果记录时间时分秒使用它time类型
如果记录年月日并且记录的年份比较久远选择datetime,而不要使用timestamp,因为timestamp表示的日期范围要比datetime短很多
如果记录的日期需要让不同时区的用户使用,那么最好使用timestamp, 因为日期类型值只有它能够和实际时区相对应
datetime默认存储年月日时分秒不存储毫秒fraction,如果需要存储毫秒需要定义它的宽度datetime(6)
timestamp与datetime
两者都可用来表示YYYY-MM-DD HH:MM:SS[.fraction]类型的日期。
都可以使用自动更新CURRENT_TIMESTAMP
对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
timestamp占用4个字节:timestamp所能存储的时间范围为:’1970-01-01 00:00:01.000000’ 到 ‘2038-01-19 03:14:07.999999’
datetime占用8个字节 :datetime所能存储的时间范围为:’1000-01-01 00:00:00.000000’ 到 ‘9999-12-31 23:59:59.999999’
总结:TIMESTAMP和DATETIME除了存储范围和存储方式不一样,没有太大区别。如果需要使用到时区就必须使用timestamp,如果不使用时区就使用datetime因为datetime存储的时间范围更大
注意:
禁止使用字符串存储日期,一般来说日期类型比字符串类型占用的空间小,日期时间类型在进行查找过滤是可以利用日期进行对比,这比字符串对比高效多了,日期时间类型有丰富的处理函数,可以方便的对日期类型进行日期的计算
也尽量不要使用int来存储时间戳
如果应用只需要记录年份,那么仅用一个字节的year类型。
如果记录年月日用date类型, date占用4个字节,存储范围10000-01-01到9999-12-31
如果记录时间时分秒使用它time类型
如果记录年月日并且记录的年份比较久远选择datetime,而不要使用timestamp,因为timestamp表示的日期范围要比datetime短很多
如果记录的日期需要让不同时区的用户使用,那么最好使用timestamp, 因为日期类型值只有它能够和实际时区相对应
datetime默认存储年月日时分秒不存储毫秒fraction,如果需要存储毫秒需要定义它的宽度datetime(6)
timestamp与datetime
两者都可用来表示YYYY-MM-DD HH:MM:SS[.fraction]类型的日期。
都可以使用自动更新CURRENT_TIMESTAMP
对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
timestamp占用4个字节:timestamp所能存储的时间范围为:’1970-01-01 00:00:01.000000’ 到 ‘2038-01-19 03:14:07.999999’
datetime占用8个字节 :datetime所能存储的时间范围为:’1000-01-01 00:00:00.000000’ 到 ‘9999-12-31 23:59:59.999999’
总结:TIMESTAMP和DATETIME除了存储范围和存储方式不一样,没有太大区别。如果需要使用到时区就必须使用timestamp,如果不使用时区就使用datetime因为datetime存储的时间范围更大
注意:
禁止使用字符串存储日期,一般来说日期类型比字符串类型占用的空间小,日期时间类型在进行查找过滤是可以利用日期进行对比,这比字符串对比高效多了,日期时间类型有丰富的处理函数,可以方便的对日期类型进行日期的计算
也尽量不要使用int来存储时间戳
是否为null
MySQL字段属性应该尽量设置为NOT NULL,除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL 。
在MySql中NULL其实是占用空间的,“可空列需要更多的存储空间”:需要一个额外字节作为判断是否为NULL的标志位“需要mysql内部进行特殊处理”, 而空值”“是不占用空间的。
含有空值的列很难进行查询优化,而且对表索引时不会存储NULL值的,所以如果索引的字段可以为NULL,索引的效率会下降很多。因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替null。
联表查询的时候,例如SELECT user.username, info.introduction FROM tbl_user user LEFT JOIN tbl_userinfo info ON user.id = info.user_id; 如果tbl_userinfo.introduction设置的可以为null, 假如这条sql查询出了对应的记录,但是username有值,introduction没有值,那么就不是很清楚这个introduction是没有关联到对应的记录,还是关联上了而这个值为null,null意思表示不明确,有歧义
注意:NULL在数据库里是非常特殊的,任何数跟NULL进行运算都是NULL, 判断值是否等于NULL,不能简单用=,而要用IS NULL关键字。使用 ISNULL()来判断是否为 NULL 值,NULL 与任何值的直接比较都为 NULL。
1) NULL<>NULL的返回结果是NULL,而不是false。
2) NULL=NULL的返回结果是NULL,而不是true。
3) NULL<>1的返回结果是NULL,而不是true。
在MySql中NULL其实是占用空间的,“可空列需要更多的存储空间”:需要一个额外字节作为判断是否为NULL的标志位“需要mysql内部进行特殊处理”, 而空值”“是不占用空间的。
含有空值的列很难进行查询优化,而且对表索引时不会存储NULL值的,所以如果索引的字段可以为NULL,索引的效率会下降很多。因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替null。
联表查询的时候,例如SELECT user.username, info.introduction FROM tbl_user user LEFT JOIN tbl_userinfo info ON user.id = info.user_id; 如果tbl_userinfo.introduction设置的可以为null, 假如这条sql查询出了对应的记录,但是username有值,introduction没有值,那么就不是很清楚这个introduction是没有关联到对应的记录,还是关联上了而这个值为null,null意思表示不明确,有歧义
注意:NULL在数据库里是非常特殊的,任何数跟NULL进行运算都是NULL, 判断值是否等于NULL,不能简单用=,而要用IS NULL关键字。使用 ISNULL()来判断是否为 NULL 值,NULL 与任何值的直接比较都为 NULL。
1) NULL<>NULL的返回结果是NULL,而不是false。
2) NULL=NULL的返回结果是NULL,而不是true。
3) NULL<>1的返回结果是NULL,而不是true。
实例
EXPLAIN SELECT * FROM tbl_user LIMIT 100000,2;
EXPLAIN SELECT * FROM tbl_user u INNER JOIN (SELECT id FROM tbl_user ORDER BY id ASC LIMIT 10000,2) temp ON u.id = temp.id;
id为主键,性能高于第一条全表扫描
EXPLAIN SELECT * FROM tbl_user u INNER JOIN (SELECT id FROM tbl_user ORDER BY id ASC LIMIT 10000,2) temp ON u.id = temp.id;
id为主键,性能高于第一条全表扫描
where中如果有多个过滤条件,在没有索引的情况下将过滤多的写在前面,过滤少的写在后面
禁止使用select *,需要什么字段就去取哪些字段
不要使用count(列名)或 count(常量)来替代 count(),count()是SQL92定义的标准统计行数的语法,跟数据库无关,跟 NULL和非NULL无关。 说明:count(*)会统计值为NULL 的行,而count(列名)不会统计此列为NULL值的行
禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。避免使用存储过程、触发器
除了 IO 瓶颈之外,SQL优化中需要考虑的就是 CPU 运算量的优化了。order by, group by,distinct … 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算)。当我们的 IO 优化做到一定阶段之后,降低 CPU 计算也就成为了我们 SQL 优化的重要目标
锁
分类
乐观锁和悲观锁
乐观锁
根据版本号控制
悲观锁
锁定表或者行,让其他数据操作等待
读锁(共享锁)
针对同一份数据,多个读操作可以同时进行而不会互相影响
不能进行写操作
写锁(排他锁)
当前写操作没有完成前,它会阻断其他写锁和读锁
表锁和行锁
表锁
表锁偏向MyISAM存储引擎,开销小,加锁快,无思索,锁定粒度大,发生锁冲突的概率最高,并发度最低
当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞
当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞
lock table表名称 read(write)
unlock tables
unlock tables
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。1、对MyISAM表的读操作(加读锁) ,不会阻寒其他进程对同一表的读请求,但会阻赛对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。2、对MylSAM表的写操作(加写锁) ,会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作总结:简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞
行锁
行锁偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高。InnoDB与MYISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
快照读
snapshot read
定义:读取记录的可见版本(可能是历史版本),不加锁
表现
普通 select
当前读
current read
定义:读取记录最新版本,会加锁保证不并发修改
表现
特殊查询 select .. where ? for update 加S锁
select .. where ? lock in share mode 加x锁
insert,update,delete等 DML操作 加x锁
特点
针对当前读,innoDB交互是一条一条的进行,加锁也是一条一条
过程
发送一个update语句
使用当前读取出第一条并加锁
mysql 收到会再发一个update请求,更新这条记录
完成后再读下一条重复上述过程
两阶段锁实现
2PL就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁
通过索引+mvcc 实现
行锁分析
show status like'innodb_row_lock%';
Innodb_row_ lock_current_wait
当前正在等待锁定的数量
Innodb_row_ lock_time
从系统启动到现在锁定总时间长度
Innodb_row_ lock_time_avg
每次等待所花平均时间
Innodb_row_ lock_time_max
从系统启动到现在等待最长的一次所花时间
Innodb_row_ lock_waits
系统启动后到现在总共等待的次数
死锁
Session _1执行:select *from account where i d= 1 for update;
Session _2执行:select *from account where i d= 2 for update;
Session _1执行:select *from account where i d= 2 for update;
Session _2执行:select *from account where i d= 1 for update;
查看近期死锁日志信息:show engine inno db statu s\G;
Session _2执行:select *from account where i d= 2 for update;
Session _1执行:select *from account where i d= 2 for update;
Session _2执行:select *from account where i d= 1 for update;
查看近期死锁日志信息:show engine inno db statu s\G;
mvcc
为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读;
为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照
为数据库解决
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
两个组合
MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突
原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的
隐含字段
DB_TRX_ID 6字节 ;事务id,随事务增加
DB_ROLL_PTR 7字节 回滚指针
DB_ROW_ID 隐藏的id,随记录自然增长
undo log
insert undo log:代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log:事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
Read View: 事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
过程
select
1、当前session 事务版本号>=DB_TRX_ID
2、delete_id is null or 当前事务号<DB_TRX_ID
insert
新插入的行,DB_TRX_ID=当前事务号
update
更新行的时候,InnoDB会把原来行复制一份,DB_TRX_ID=当前事务号
delete
设置删除版本号为当前事务id (DB_TRX_ID=当前事务号??) delete_id = 当前事务号
相当于标记为删除,而不是物理的删除。真是的删除是在InnoDB的purge线程去做的。
commit
修改事务状态为commit,在 redo log 中写入 commit 记录
rollback
根据当前回滚指针从undo log中找出事务修改前的版本,并恢复
主从同步
聚集索引:叶节点包含了完整的数据记录
非聚集索引:叶节点中只保存数据地址,而不保存数据
非聚集索引:叶节点中只保存数据地址,而不保存数据
为什么InnoDB表必须有主键,并且推荐使用证型的自增主键?
InnoDB引擎使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)
1、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页,这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很 多开销在维护索引上。
2、如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置。此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
为什么非主键索引结构叶子节点存储的是主键值(一致性和节省存储空间)
InnoDB引擎使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)
1、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页,这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很 多开销在维护索引上。
2、如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置。此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
为什么非主键索引结构叶子节点存储的是主键值(一致性和节省存储空间)
0 条评论
下一页