赞
踩
大致分未2层
server层包括
当一个看客户端和mysql进行tcp连接,使用show processlist
就可以查看到当前连接数。
建立连接过程通常很复杂,所以建议使用长连接。
但是长连接在mysql使用过程会积累很多内存占用,所以经常因为内存占用过大,导致被系统kill(OOM),mysql重启。那么就需要有解决办法:
Mysql_reset_connection
重置连接,但是不需要重连和权限验证8.0版本已经删除缓存功能
mysql会以k-v形式把sql保存到缓冲中,后面的分析步骤都无需执行。
但是如果修改数据就会导致缓存大规模失效需要重新更新,所以不建议使用。
执行步骤
在开始执行之前,还要先经过优化器的处理。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。
sql编写顺序
SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> ORDER BY <order_by_condition> LIMIT <limit_params>
sql执行顺序
FROM <left_table>
ON <join_condition>
<josin_type> JOIN <right_table>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
SELECT DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_params>
查看sql查询频率
#global返回有所连接的连接操作信息 session则是当前连接信息
show global/session status like "com_%";
查看连接数
show processlist;
慢查询
vim /etc/my.conf
slow_query_log = 1
long_query_time = 5 #慢查询时间 超过1秒则为慢查询
slow_query_log_file = /data/mysql/mysql-slow.log
explain是解析优化一个sql的执行
id代表每个条sql查询的执行顺序,id越大权重越大,则执行顺序越快
#id都是1,权重一样
select * from a,b,c;
# select a 的id=1 select b id=2
select * from a where id=(select * from b where = 1)
select_type | 含义 | 对应实例sql |
---|---|---|
SIMPLE | 简单select查看,没有连表或子查询或union等 | select * from a |
PRIMARY | 查询中有子查询,最外层的标记(间接的说就是id=1的标记) | select * from a where id=(select * from b where = 1) 第一个select才是primary |
SUBQUERY | 子查询标记 select * from a where id=(select * from b where = 1) | select * from a where id=(select * from b where = 1) 第二个select是subquery |
DERIVED | 在FROM列表中包含的子查询,被标记为DERIVED(衍生) MYSQL会递归执行 select * from (select * from b where = 1) | select * from (select * from b where = 1) 第一个select是DERIVED |
UNION | UNION之后的sql | select a union select b 第二个select是union |
UNION RESULT | 在union表获取结果的select |
从上之下,效率又高至低
type | 含义 | 对应sql实例 |
---|---|---|
null | 不访问任何表或索引,直接返回结果 | select now() |
system | 表中只有一行记录,相当于系统表,它是const类型的特例 | select * from a where id=(select * from b where = 1) 第一个select是system |
const | 常亮,只返回一条记录。所以是主键=value 或 唯一索引=value,就可以确保是一条返回记录 | select * from a where id=1 |
re_ref | 多表关联查询,且返回记录只有一条 | |
ref | 非唯一索引查询 | |
range | 检索给定返回的行,使用一个索引来选择行,where之后出现 >, < ,in 等操作 | |
index | index 和 all的区别 ,index是了整个索引树,all是全表扫描 | |
all | 扫全表 |
从上之下,效率又高至低
可能使用的索引
实际使用的索引
注意:因为可能数据表内容少,虽然有索引,可是mysql优化时,觉得不必要去读取索引树,所以会导致某些能用索引的条件,没有使用索引
索引最大的使用的字节数,并非使用时的实际长度。索引字节越短,效率就越高
当前条件扫描的行数
extra | 含义 | 对应sql实例 |
---|---|---|
using filesort | 使用了外部的索引排序(文件排序),没有使用索引的排序功能。效率低 | 优化的方法:对排序条件加索引 |
using temporary | 对临时表排序,常见于order by 和 group by 。效率低 | |
using index | 表明select操作使用了覆盖索引,避免访问表的数据行,效率不错 | |
using where | 在查找使用索引的情况下,需要回表查询数据 | |
using index condition | 查找使用了索引,但是需要回表查询 | |
using index;using where | 查找使用了索引,但是需要的数据都在索引列中 能找到,所以不需要回表查询 |
#查看mysql是否支持profile
select @@have_profile;
#是否开启了profile
select @@profiling;
#开启profile
set profiling=1;
#查看每条sql的耗时
show profiles;
return query_id and duration
#查看sql的耗时具体时间
show profile for query query_id;
查看mysql对sql进行了哪些具体的优化
# 开启优化器
set optimizer_trace="enable=on",end_markers_in_json=on; #开启,并返回结构是json
set optimizer_trace_max_mem_size=100000; #内存大小
#查看优化器表内容,内容里面记录了之前执行过的sql优化
select * from information_scheme.optimizer_trace\G
单条 转 多条
insert into tb values(),()
事务插入
begin;
insert into tb values ();
insert into tb values ();
insert into tb values ();
commit;
主键有序插入
insert into tb values(1,"a");
insert into tb values(2,"b");
insert into tb values(3,"c");
#单列字段排序
select * from tb order by a; #虽然使用了搜索引,但是extra:using filesort,通过文件排序
select a,b,c from tb order by a; #因为覆盖索引的原因,extra:using index通过索引排序
select a,d from tb order by a; #虽然使用了搜索引,但是extra:using filesort,通过文件排序,d不是覆盖索引的字段
#多字段排序
select a,b from tb order by a,b #两个同时升降序 extra:using index
select a,b from tb order by a asc,b desv #两个升降序不同 extra:using filesort.
!!并且order by 后的字段必须要和索引的位置一致,才能using index。这个和是否命中索引不同,优化器不会自动优化sql
#group by 默认会根据 group by field进行排序。
select a from tb group by a; #extra:using index;using filesort;
select a from tb group by a order by null; #extra:using index
#少用子查询,尽量用多表联查或join
select a from tb1 where b in (select * from tb2); # type:index
select a from tb1,tb2 where tb1.a=1 and tb2.x=1; #type :ref
select a from tb1 join tb2 on tb1.a = tb2.x where tb1.a=1; #type :ref
#建议使用union替换or
select * from tb where id=1 or b=2; #key: null
select * from tb where id=1 union select * from tb where b=2; #key:a_b_c;null
#其实就是优化排序
#方案一:优化select * ,这样就需要使用了覆盖索引,这样就可以在索引树上排序 using index
select * from tb limit 20000,10; #花费时间2s key:null
select * from tb where id in (select id from tb limit 20000,10) AS tmp_tb #花费时间1s key: primary
#方案二:只使用于自增id并且不能断层,那就是使用索引将前面的数据过滤掉
select * from tb where id>200000 limit 10; #key:primary extra: using index condition
#创建单列索引
create index idx_a on tb_name(a);
#系统默认使用复合索引的情况下,可以提示系统使用单列idx_a索引
select * from tb use index(idx_a) where a='1';
#系统默认使用复合索引的情况下,告诉系统忽略a_b_c复合索引
select * from tb ignore index(a_b_c) where a='1';
#系统默认使用复合索引的情况下,强制系统使用单列idx_a索引
select * from tb force index(idx_a) where a='1';
max_conections
back_log
table_open_cache
thread_cache_size
innodb_lock_wait_timeout
粒度锁类型 | 特点 |
---|---|
全局锁 | 整个表阻塞 |
表锁 | MyISAM引擎,开销小,加锁快,不会出现死锁。粒度大并发小 |
行级锁 | InnoDB引擎,开销大,加速慢,会出现死锁。粒度小,并发高 |
页面锁 | BDB引擎, |
全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
全局锁的典型使用场景是,做全库逻辑备份,如果备份不是一个逻辑时间点,那么视图(read view)是逻辑不一致的(破坏了隔离性),导致数据不一致。所以备份数据时,应该生成一个全局事务,保证数据一致性。
mysqldump –single-transaction
显示表锁,在客户端断开连接时或使用unlock tables 可以释放锁。
显式读表锁
# x client lock table a read; #显式加锁,是不会自动释放锁的 # xy client select * from a ; #执行成功 #x client select * from b; #执行失败,因为x客户端没有释放锁,因为系统默认会给x客户端对b表加锁,系统提示b表加锁失败。 #y client select * from b; #执行成功,因为y客户端没有加读锁 #x client update a set name=1 where id=1; #执行失败,提示a表加了读锁,不能写 #y client update a set name=1 where id=1; #执行阻塞,等待x客户端释放锁
显式写表锁
# x client
lock table tb write;
#x client
select #执行成功
insert #执行成功
update #执行成功
#y client
select #任何表都执行阻塞
insert #任何表都执行阻塞
update #任何表都执行阻塞
查看锁的情况
show open tables;
show status like "Table_locks%"
MDL 是隐式表锁,是mysql加上的,无需用户显示添加
互斥情况(所有读写锁互斥情况都是一样的):
# MDL 隐式读锁
select * from tb; #这样就默认加上了读锁
# MDL 隐式写锁
alter table tb add columns newColumn int(8)
行锁的对象是数据行,InnoDB支持行锁,锁类型又分:
update,delete,insert这些当前读语句,innodb引擎会隐式加上排他锁,事务提交后会解锁,这称为两阶段锁。
innoDB的每一个更新语句都会开启了一个新事务。如果autocommit=0,则事务不会被自动commit。autocommit默认是1
显示加锁
共享锁: select * from tb where a=1 lock in share mode;
排他锁:select * from tb where a=1 for update;
隐式加锁,并且不自动提交
#关闭自动提交,
set autocommit=0
#clinet x
update tb set b=1 where a=1; #加上了行锁,并且没有自动提交
#clinet y
update tb set b=2 where a in (1,2,3,4,5); #阻塞
#client x
commit; #client y阻塞执行
因为mysql的行锁是作用于索引树上的,并不是数据页内的数据,当where条件后 没有索引 或者 索引失效,就会导致锁升级成 表锁
使用范围条件时,并请求使用共享锁或者排他锁。innodb会给符合的条件加上锁,对于符合条件又不存在的行也会加上锁,叫做间隙锁
#client x
select id from tb<5 for update;
#response
1,3,5存在,加上了写锁
2,4不存在,但是满足条件,就加上了间隙锁
#client y
insert into tb (id, a) values (2,1); #client y阻塞,因为id=2被加上了间隙锁,等待client x 提交
作用:
为了防止幻读,A事务内第一次读a>100
只有2条数据,如果没有间隙锁把a>100
的锁定,那么B事务内插入一条数据并提交,此时A事务再去读就会导致看到了3条数据,那么就没有了隔离性。有了间隙锁B事务就会阻塞等待A事务提交。
不过这种加锁机制,会导致大量请求阻塞等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,需要尽量用相等条件来访问更新数据,避免范围条件。
在执行插入语句时先加上表级别的AUTO-INC锁
,插成功了立即释放,不需要等事务提交
innodb行锁争用情况
show status like "innodb_row_lock%"
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁
事务 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。
其实最终还是要通过更改业务代码来避免死锁,尽量使用短事务,把update统一后移
索引就是一种便于查找的数据结构,节点使用地址引用指向对应的数据块
优点
缺点
依据索引字段个数划分
聚簇索引 / 非聚簇索引
not null unique
列是聚簇索引唯一性
查询
B+TREE是一个N叉数。N叉数可以减少访问磁盘的次数,例子:
一棵 100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,这个查询可真够慢的。
为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N 叉”树。这里,“N 叉”树中的“N”取决于数据块的大小。
以 InnoDB 的一个整数字段索引为例,这个 N 差不多是 1200。这棵树高是 4 的时候,就可以存 1200 的 3 次方个值,这已经 17 亿了。考虑到树根的数据块总是在内存中的,一个 10 亿行的表上一个整数字段的索引,查找一个值最多只需要访问 3 次磁盘。
extra_type : Using index
每个innodb表一定会有一个聚簇索引,
extra_type : Using index condition
非叶子节点存放的是单列数据或多列数据
叶子节点存放的是 主键值
因为叶子节点存放的不是行数据,所以需要查找主键后,再回聚簇索引查找一次数据。这个过程称为回表查询
extra_type : Using index condition; using where
如果先命中了普通索引
extra_type : Using where
直接在物理表中查询
#创建索引
create index idx_name on tb_name(column1,column2);
#查看索引
show index from tb_name;
#删除索引
drop index idx_name on tb_name;
#alter
alter table tb_name add unique idx_name(column1,column2);
alter table tb_name add primary key idx_name(column1);
alter table tb_name add index idx_name(column1,column2);
最左前缀(多列索引)
#创建多列索引
create index id_name on tb_name(column1, column2, column3)
#可以使用id_name这个索引的查询条件(查询条件中的字段不分先后),下面5个条件都能命中索引,只是看mysql是否使用而已
where column1
where column1 and column2
where column1 and column2 and column3 (也可以是column3 and column1 and column2)
前缀索引:字符串的前N个字符作为索引键
好处:
影响:
alter table SUser add index index1(email); # 整个字符串的索引
alter table SUser add index index2(email(6)); # 字符串前6位索引
新插入一条最新ID的数据,只需要在索引树的最右叶子右边加上即可,如果是需要中间加入,那么会有2种情况:
CREATE TABLE (
id int(10) primary key auto_incrment,
a int(10) NOT NULL,
b int(10) NOT NULL,
c int(10) NOT NULL,
d int(10) NOT NULL,
)engine=innodb default charset=utf8mb;
create index a_b_c on tb_name(a, b, c);
1、最左匹配
WHERE A=1; #explain的 key=a_b_c key_len = len(A)
WHERE A=1 AND B=1; #explain的 key=a_b_c key_len = len(A)+len(b)
WHERE A=1 AND B=1 AND C=1; #explain的 key=a_b_c key_len = len(A)+len(b)+len(c) = len(a_b_c)
WHERE C=1 AND A=1; #explain的 key=a_b_c key_len = len(A) 这个说明条件部分字段先后,myqsl优化器会自动调整条件字段位置,使其命中索引
WHERE B=1 #这个不会命中索引,因为不能跳过一楼,直接爬2楼。就像爬楼一样,只能一层一层的爬
2、范围条件右边的列,不能使用索引
WHERE B>1 AND A=1 AND C=1 #explain的 key=a_b_c key_len = len(A)+len(b),因为(a,b,c)c字段在b的右边
3、不要在索引列中进行运算操作,否则索引失效
WHERE (a+1)=2 #索引失效
4.1对索引字段就行函数操作,
WHERE count(a)>0
4.2、字符串字段不要漏了单引号,相当于给字段加上了强制类型转换函数,等同于例子4.1
#假设b字段是varchar(10)并且value=1
WHERE a=1 AND b=1 #explain的 key=a_b_c key_len=len(a),因为 b是字符串,如果改成
WHERE a=1 AND b='1' #explain的 key=a_b_c key_len=len(a)+len(b)
5、尽量使用 覆盖搜索引,避免使用select *
EXPLAIN SELECT * FROM table WHERE a=1 AND b=1 AND C=1
#type:ref
#key:a_b_c
#extra:using index condition 查询完BTREE索引,在回表查询
EXPLAIN SELECT a,b,c FROM table WHERE a=1 AND b=1 AND C=1
#type:ref
#key:a_b_c
#extra:using where;using index 只在BTREE的叶子节点就把数据返回了
6、or分割开的条件,如果后面没有索引,则会让前面能使用索引的字段也失效
WHERE id=1 or a=1
#explain
#posiable_key:primary
#key: null 没命中,两个本来都会命中,但是因为or索引直接都失效了
7、以%开头的like查询,有可能导致索引失效
SELECT * FORM table WHERE a like "1%"; #这个能命中索引
SELECT * FORM table WHERE a like "%1%"; #这个无法能命中索引
#解决办法,就是使用覆盖索引,不让他回表查,就能命中索引
SELECT a,b,c FORM table WHERE a like "%1%"; #命中abc都在索引a_b_c中
SELECT a,b,c,d FORM table WHERE a like "%1%"; #不命中索引,d不在覆盖索引中
8、查表快的话,索引就会失效
#这个跟数据有关系,
#全表命中 或 命中90%的行
9、is null is not null 有时会索引失效
#这个跟数据有关系,
WHERE A IS NOT NULL
#全表命中 或 命中90%的行
10、in会命中索引 not in不发命中索引
show global status like "Handler_red%";
force index(index_name)
: 强制使用某个索引查看mysql支持的存储引擎
#查看存储引擎
show engines;
#创建表 并且使用innodb
create table (
id int(10) auto_increment primary key
)engine=innodb default charset=utf8;
数据文件
特性
#创建表 并且使用innodb
create table (
id int(10) auto_increment primary key
)engine=myisam default charset=utf8;
数据文件
特性
特性
特性
redolog 和 binlog。
它位于储存引擎层,作用:
因为mysql是页式存储,需要更改某一条记录就需要把整页数据都刷新,如果每一次的更新操作都需要写进磁盘,磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。
WAL技术(Write-Ahead Logging),就是解决上述问题,它的关键点就是先写日志,再写磁盘。
当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存 buffer pool,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录flush到磁盘里面,这个时机请看下面的“两阶段提交后的flush脏页”
此时redolog就已经算完成了事务。同时,InnoDB 引擎会在适当的时候,将这个操作记录flush到磁盘里面,这个时机请看下面的“两阶段提交后的flush脏页”
当mysql宕机,数据页丢失了只更改了内存的数据:
InnoDB的redo log 是固定大小的,例如是4个1G的文件。从头到尾的开始写入,如果超出了4G大小,那么会到头部循环写入
当write pos追上了 check point,代表文件写满了,这个时候不能再执行更新语句,需要把check point后移,并把移动位置的数据刷新到mysql磁盘中。
从基础架构能篇知道,mysql大致分2层,server层和储存引擎。上面的redolog属于储存引擎的日志,binlog则属于server层的日志
binlog是没有crash-safe能力的,他只用于归档。binlog和redolog的不同:
查询语句在索引树上查找的过程,先是通过 B+ 树从树根开始,按层搜索到叶子节点,然后可以认为数据页内部通过二分法来定位记录。
InnoDB 的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存buffer pool中。在 InnoDB 中,每个数据页的大小默认是 16KB。
上面的redolog有两个状态,分别在不同步骤进行状态更改,这就是2阶段提交。
为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。要说明这个问题,我们得从文章开头的那个问题说起:怎样让数据库恢复到半个月内任意一秒的状态?
当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:
首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,如果没有两阶段提交会出现什么情况呢?
两阶段提交如何解决的呢?4种情况
当事务提交完成后只是记录了redolog,并没有刷新到数据页中,此时内存和数据页内容不一致,我们称这个数据页叫**“脏页”**
flush
就是将内存buffer pool里的数据,写入到磁盘数据页里。这里并不是根据redolog数据刷进磁盘数据页,因为redolog没有整个数据页的完整数据,只有buffer pool里面有
flush触发的时机有:
查询语句在索引树上查找的过程,先是通过 B+ 树从树根开始,按层搜索到叶子节点,然后可以认为数据页内部通过二分法来定位记录。
InnoDB 的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存buffer pool中。在 InnoDB 中,每个数据页的大小默认是 16KB。
快照读(普通读)
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制和undo log实现,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
当前读
也称锁定读(locking read),通过对读取到的数据(索引记录)加锁来保证数据一致性,当前读会对所有扫描到的索引记录进行加锁,无论该记录是否满足WHERE条件都会被加锁。
当前读语句:
更新数据时,都是先读后写,这个读就是当前读。读取该条数据的已经提交的最新的事务生成的readview
MVCC:多版本并发控制,实现对数据库的并发访问。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
它的实现原理主要是依赖记录中的 4个隐式字段,undo日志 ,Read View 来实现的。
undo log主要分为两种:
purge
- 从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
- 为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:
在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
事务提交后,释放锁
在事务2修改该行数据时,数据库也先为该行加锁
然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)
巨人的肩膀:
https://mp.weixin.qq.com/s/sCgIWj0HjMgUqVIHwLXduQ
Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大),把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
概念
read view主要有4个部分组成
如果undo_log行的隐藏字段db_trx_id落在 小于 min_trx_id(未提交的最小事务id),那么此行是已提交的事务,数据可见
如果undo_log行的隐藏字段db_trx_id落在 大于max_trx_id(当前mysql最大的事务id),那么表示版本是由将来要启动的事务生成,数据不可见
如果undo_log行的隐藏字段db_trx_id落在min_id < trx_id < max_id
readview的生成时间:在创建事务后并第一次快照读时生成的readview不会改变!!!
不改变readview,避免不可重复读的原因:开启B事务再开启A事务,在A事务生成快照读并在提交前,B事务提交了修改x行内容,A事务根据不变的readview还是会判定B事务并未提交,保证了A事务中x行内容的可重复读
mvcc为基础,加部分条件
因为是要读到快照时的数据,所以undo_log读到数据的db_trx_id应该满足条件
幻读:
例子1:
select * from t where d=5 for update
只作用于x条数据insert into t (a,d)value(2,5);
或者 update t set a=3 where d=5;
select * from t where d=5 for update
,是会读到x+1条数据的。select for update当前读update set c=0 where d=5;
会作用于x+1条数据。update也是当前读[幻读实例]
实例1:https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/MySQL%E5%AE%9E%E6%88%9845%E8%AE%B2/20%20%20%E5%B9%BB%E8%AF%BB%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%8C%E5%B9%BB%E8%AF%BB%E6%9C%89%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F.md实例2 : https://blog.csdn.net/wdj_yyds/article/details/131897705
todo: 间隙锁 和 next-key lock,当前读就会自动加上间隙锁和next-key锁
readview的生成时间:readview是会改变的,每次快照读时,是会更改该事务的readview!!!
快照读都会改变readview导致了不可重复度的原因:开启B事务再开启A事务,在A事务生成快照读并在提交前,B事务提交了修改X行的内容,当A事务再次快照读时,生成了新readview(readview会在未提交事务id集合中去掉的B事务id),就会判断B事务已提交,那么A事务两次读X行的内容就会不同,导致了不可重复读
mvcc为基础,加部分条件
因为是要读到快照时的数据,所以undo_log读到数据的trx_id应该满足条件
巨人的肩膀:
innodb引擎数据存储是页式存储,所以读取时也是整页读取后,存放到内存中(buff pool)。
当更改数据时,更新数据页不在内存中,为了避免随机读磁盘IO
访问,在不影响数据一致性的前提下,更改内容会写入change buffer中。等之后有其他请求读到了这个数据页,并把数据页写入内存时,直接从change buffer合并到内存即可
随机读磁盘IO:数据在哪一些如果没有索引是不知道的,所以会去读数据页对比,这就有可能读很多也才可能找到,导致内存
他是一个内存存储,并且也是一个可以持久化数据
如果需要update数据时,
所以唯一索引的内存使用率比普通索引的内存使用率低
他们是不是功能一样的?都是先找一个地方暂存,最后一次性更新数据。
还记得redolog是物理日志吗?他存放的就是:这些数据应该写入哪些页中,下图是修改page1和page2的数据过程
解释:
add(id1,k1) to page1
写入redolognew change buffer item add(id2. k2) to page2
写入redolog总结:可以理解为,change buffer只到内存数据一致,redolog是到宕机后重启内存数据一致
count()
是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。
有一个统计字段,如果没有where条件会直接返回该字段,如果有条件还是会扫描全表
扫描全表,为什么,因为有事务,每个事务看到的总条数会不一样,还要判断版本链信息
对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server -层拿到 id 后,判断是不可能为空的,就按行累加。
对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
对于 count(字段) 来说:
但是 count(*) 是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count(*) 肯定不是 null,按行累加。
按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(),所以我建议你,尽量使用 count()。
我们可以通过explain sql
来查看语句的执行情况
如果extra字段出现了using filesort
表示需要排序。mysql会在buffer pool中分配一块内存用于排序,叫做sort_buffer
。
如果聚簇索引返回需要排序的结果集小于参数sort_buffer_size
,则会在内存完成排序,如果大于参数就会使用临时文件
全字段排序,就是把要查找的字段全部插入sort_buffer中
下面以这个sql为例,做讲解
select city,name,age from t where city='杭州' order by name limit 1000 ;
语句执行流程如下:
rowid排序,当要插入sort_buffer中的字段过长时,导致sort_buffer超出界值时,mysql会只将排序字段和主键ID插入sort_buffer,这就叫做rowid排序
下面以这个sql为例,做讲解
select city,name,age from t where city='杭州' order by name limit 1000 ;
语句执行流程如下:
mysql> select word from words order by rand() limit 3;
随机取出words表中的3行数据,explain sql:
加入表中有10000个数据,语句执行流程:
通过慢日志验证,扫描行数
# 开启慢查询
set global slow_query_log='ON';
# 设置慢查询时间为0
set global long_query_time = 0;
# Query_time: 0.900376 Lock_time: 0.000347 Rows_sent: 3 Rows_examined: 20003
SET timestamp=1541402277;
select word from words order by rand() limit 3;
tmp_table_size
字段限制了临时内存表的大小,默认是16m。如果超出就会转成磁盘临时表,磁盘临时表的引擎是innodb。
查询一条数据也会慢的原因有很多
主备同步是通过binlog日志来实现的。
statement格式:存放的是原始sql,缺点是:即使是原始sql也会导致数据不一致
例如:delete from t where a>=4 and t_modified<='2018-11-10' limit 1;
limit
只是取其中一个,因为主备库的有可能使用到的索引不同,主库只用a索引,备库使用t_modifyed索引,他们的第一条信息不一定是一样的,所以会可能删除的数据不是同一条
row格式:存放的是操作的行主键和数据变更,需要用mysqlbinlog
命令来解析文件。
优点:是没有数据不一致的情况。通过mysqlbinlog可以查看到原始数据,这样误删,误改还能手动恢复。而statement只有一条sql
缺点是占用的空间大。如果删除了1w行数据,那么这一条sql就会占用很多空间,而statement只需要记录一条sql
MySQL 就取了个折中方案,也就是有了 mixed 格式的 binlog。mixed 格式的意思是,MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式
互为主备,就能做到主备库都可以写入数据。
但是有 循环复制 问题:
主从延迟:备库同步binlog,和从库出现了时间差。
在库备执行show slave status;
命令,返回seconds_behind_master
字段值是主备延迟的时间差
下面是主从延迟的3个原因
一般主备主机都是相同,因为需要做主从切换,所以一般不会是这个问题
解决办法:一主多从,分摊压力
可靠性优先:数据一致性 大于 mysql可用性,主备库会有段时间只读,来避免切换时数据不一致
流程:
seconds_behind_master
,如果小于某个值(比如 5 秒)继续下一步,否则持续重试这一步;【手动执行show slave status】readonly
设置为 true;seconds_behind_master
的值,直到这个值变成 0 为止;readonly
设置为 false;在步骤4之前,主备库有时readonly状态,写入业务会挂掉。但是保证了主库的所有数据都同步到了备库
可用性优先:mysql可用性 大于 数据一致性
流程:
readonly
设置为 false;一步直接切换了主从,因为主从延迟,主库有可能还有数据未同步到备库,所以有可能数据不一致。但是系统是一直可访问的
因为MDL锁 直接的读写锁互斥,当表结构发生变化时,server会自动给表加上MDL写锁,之后其他session的增删改查(会自动加上MDL的读锁)会被阻塞。为了避免业务被阻塞,需要注意:
select * from table where name="a" limit 10000,10
某些管理后台数据展示页,当翻页到了几百页之后,数据查询就会很慢,这就是深分页问题。
每次非聚簇索引查询时,从索引树上拿到 主键id,还需要回表查询一次数据行内容,当查询到N条数据,就需要回表N次,所以查询效率就会很低。
关键就是要 避免多次回表。所以就是尽量使用聚簇索引。可是管理后台需要展示的数据会很大,dba不会允许开发将一个多列索引设计得很大。所以就需要使用一下方法:
select * from table where id in
(
selelct id from table where name='a' limit 10000, 10
)
这种方法子查询虽然也用到了limit 10000,10,但是他只需要在name索引树上查询,并不需要回表查询。避免了回表查询得开销,所以速度会有所提升
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。