mysql_booker
key
select_update
- select || update语句执行过程 developer.aliyun.com/article/942…
- select: 尽量使用长链接、select-cache(链接断开时清空)
- update: change_buffer
mysql_reset_connection初始化长连接query_cache_type=DEMAND根据需要使用查询缓存;缓存一致性实现=>cache_aside- WAL => Write ahead log,
- InnoDB的redolog和Server的binlog的对比, Server能对外提供其他服务
- cash-safe的解决=>redolog
- 2pc=>2阶段提交的过程: redolog(prepare) -> binlog -> redolog(commit)
innodb_flush_log_at_trx_commit=1;表示每次事务的redolog都直接持久化到磁盘sync_binlog=1;表示每次事务的binlog都持久化到磁盘
redolog-binlog对比
- redolog是InnoDB引擎特有的;binlog是MySQL的Server层实现的
- redolog是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是原生语句
- redolog是循环写的,空间固定会用完;binlog是可以追加写入的
- redolog的状体变化:
redolog in prepare=>binlog=>redolog in commit
transaction
- ACID
- 脏读(dirtyread)、不可重复读(non-repeatableread)、幻读(phantomread)
- 事务与MVCC 和 next-key-lock的关系
- undolog日志,MVCC,事务视图(read-view)
setautocommit=1避免长事务(事务启动后长时间不提交);- 一致性读(可重复性读问题怎么解决)? MVCC
长事务解决
- 应用端: 开发框架启动自提交:set autocommit =1
- 应用端: 拆分语句,减少执行时间
- mysql: 监控information_schema.Innodb_trx表
- mysql: 监控information_schema.Innodb_trx表
index
- index数据结构对比:hash,btree,btree+; 跳表(不方便动态的增加复合索引),LSM树
- 聚簇索引(clusteredindex);二级索引(secondaryindex)
- 回表查询,缩影覆盖
- 页分裂&页合并=>
要求建表语句里一定要有自增主键(因为索引是有序结构) - 最左前缀=>最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符;例如
like 'x%' - 索引下推=>MySQL5.6引入的索引下推优化(indexconditionpushdown)
lock
- 全局锁/表锁/行锁
- 全局锁: 全局锁主要用在逻辑备份过程中
- MySQL提供了一个加全局读锁的方法,命令是
Flush tables with read lock(FTWRL); client断开连接就解锁 - 数据备份工具
mysqldump –single-transaction, - 设置从库只读
set global readonly=true
- MySQL提供了一个加全局读锁的方法,命令是
- 表锁
- 手动加的语法是
lock tables...read/write->lock tables t1 readandunlock tables, - 另一类表级的锁是
MDL(meta data lock);在MySQL5.5版本中引入了MDL,当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁;系统自动加锁(你不用管...)
- 手动加的语法是
- 行锁:
两阶段锁协议: InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放- 死锁 & 死锁检测
- 一个是view。它是一个用查询语句定义的虚拟表
- 一致性读视图 consistent read view, InnoDB在实现MVCC时用到的一致性读视图(包括
当前已经启动但是还没有提交事务id的数组) - 更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读当前读”(current read)。
- sql加共享锁
lock in share mode=>一致读 - sql加排他锁:
for update=>当前读
死锁检测的办法
- 设置
innodb_lock_wait_timeout=50s锁超时避免死锁- 开启死锁检测:
innodb_deadlock_detect=on;发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行.
key
- InnoDB的数据是按
数据页为单位来读写的;在InnoDB中,每个数据页的大小默认是16KB;(索引节点包含多个数据页,数据页包含多个记录) - change buffer
- 普通索引与唯一索引的区别(select,update2个方面)
analyze table t命令,可以用来重新统计索引信息- 采用
force index(x)强行选择一个索引 - MySQL是支持
前缀索引的,也就是说,你可以定义字符串的一部分作为索引 - InnoDB用缓冲池(buffer pool)管理内存
- InnoDB刷脏页的控制策略
-
innodb_io_capacity建议设置成磁盘的IOPS,它会告诉InnoDB你的磁盘能力。
-
innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%
-
alter table A engine=InnoDB命令来重建表??? renameanalyze table t其实不是重建表,只是对表的索引信息做重新统计,没有修改数据optimize table t等于recreate+analyze;
changebuffer
- InooDB会将
更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作.changebuffer,系统有后台线程会定期merge。在数据库正常关闭(shutdown)的过程中,也会执行merge操作innodb_change_buffer_max_size=50=>表示change buffer的大小最多只能占用buffer pool的50%- 唯一索引的更新不能使用changebuffer,实际上也只有普通索引可以使用;
- 普通索引和changebuffer的配合使用,对于数据量大的表的
更新优化还是很明显的- 如果所有的更新后面,都马上伴随着对这个记录的查询,那么你应该关闭changebuffer
-
count_: 对于count()这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL优化器会找到最小的那棵树来遍历。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。(=>使用count_*统计,执行的过程涉及索引的选择,执行时的操作)
-
order-by
- 内存排序:
sort_buffer_size内存排序的缓存大小 - 文件排序:
- 全字段排序-rowId排序(<orderKey,rowid>=>需要回表查询需要的数据)
- 内存排序:
不使用索引的情况
- 索引使用了函数
- 索引被隐式类型转换
- 索引编码的字符集不一致
- lock算法
- record_lock,gap_lock,next_key_lock
- record_lock(行锁)=>锁之间同步;[某一行]
- gap_lock(间隙锁)=>保护这个间隙,不操作间隙的值,(前开后开)
- next_key_lock(gap_lock + record_lock合称next_key_lock):前开后闭区间(前开后闭];(定义gao的边界)
mysql加锁理解
- 原则1:
加锁的基本单位是next-key lock。希望你还记得,next-keylock是前开后闭区间。- 原则2:查找过程中访问到的对象才会加锁。
- 优化1:索引上的等值查询,给
唯一索引加锁的时候,next-keylock退化为行锁。(等值查询+唯一索引)- 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-keylock退化为间隙锁。(
等值查询+普通索引)- 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。(
范围查询=>(],包含后边界)。
- 加锁的基本单位next_key_lock,是(]; gap_lock是();行锁是等值;gap_lock + 行锁和合称next_key_lock;
- select 的对象都加锁
- 等值查询可以退化为行锁或gap_lock,主要看是唯一索引还是普通索引
- 范围查询都是(] => 这样是为了锁定边界。不是bug...不然边界可能被修改....
- 加锁的前提是:事务隔离级别=重复读; 读提交没有gap_lock
可重复读与读提交的那些区别
- RR支持gap_lock; 加锁的级别不一样..
- 都是只能读到已经提交的数据; RR是MVCC读; RC是可以读到所有提交
- gap_lock要时刻注意死锁的场景...
死锁的场景
insert/delete/update高并发操作; gap_lock导致..
| session-A | Session-B |
|---|---|
| begin; select * from t where id =9; | .. |
| .. | begin; select * from t where id =9; |
| insert into t values(9,9); | .. |
| .. | insert into t values(9,9); |
id是普通索引,session-A和Session-B都对9的间隙加锁,例如(5,10)加2个gap_lock; 加锁不冲突;但是更新时竞争对方锁造成死锁.
- id存在但不是唯一索引;或者不存在,总之加锁是
间隙锁. - sessionA,sessionB是不同的连接;高并发导致同时操作
- 竞争死锁..
有的公司的配置RC+bin_log_format=row
- RC读提交=>关闭间隙锁,防止死锁出现
- 读提交可能出现幻读;同时为了解决数据和日志不一致的情况bin_log设置为row; statement只会记录语句
幻读场景...
数据表t(id,d)=>(1,5)
| session-A | session-B |
|---|---|
| begin; select * from t where d=5 for update | . |
| . | insert into t (100,5); |
| select * from t where d=5 for update | . |
两次读到的数据不一样...(没有间隙锁,插入就能读到).
binlog的三种日志格式:Statement,row,mixed
show variables like '%binlog_format%';
-
log
- 2阶段提交:redolog-prepare;binlog;redolog-commit
-
数据库删库的结局方案...
- binlog_format=row,binlog_row_image=FULL => 数据恢复使用
sql_safe_updates=on,自动检查delete/update里面有没有where+索引;- sql审计=>第三方工具...
- 开发是有DML权限...不具有DDL权限...
- 删库的解决数据库层的解决方案...
删除数据表
delete from t where id >0; bin_log=row- truncate/drop table=>数据不能恢复;因为即使bin_log=row,但是记录的是sql语句statement(..反复确认啊)
mysql-binlog命令的基本使用
mysql数据恢复做成自动化工具研发设计.....(这个挑战牛逼....)
- kill
kill query id杀死语句kill connection id杀死连接mysql_store_result 和 mysql_use_result对比- mysql_strore_result:查询结果使用本地缓存,
sending to client=服务端网络栈满了 - mysql_use_result: client读一行处理一行
- mysql_strore_result:查询结果使用本地缓存,
show engine innodb statue查看BufferPool命中率=>Buffer pool hit rate=99/100
select大数据不会OOM
- server不会缓存全部的结果集,更具网路栈大小边读边处理
- LRU内存算法的分区更新(..有点偏了...)
- join的2中查询算法
- nested-loop-join(NLJ;嵌套loop的join):驱动表全表扫描,被驱表使用索引查询(像是嵌套使用了loop)
Block-nested-loop-join(BNL):使用join_buffer分块操作; 查询流程对比- 小表做驱动表=>怎么理解小表
- MRR(multi range read)算法
- BKA(batched key access)算法(BNL转BKA=>被驱动表的关联字端增加索引)
join的2个查询算法的对比
- index-nested-loop-join:使用index实现的子查询
- block-nested-loop-join:使用了join_buffer分区操作
- 临时表
create temporary table t like tx;临时表只能被当前session访问,会自动删除- binlog_format=row,binlog不记录临时表的的所有操作
- union / union all 流程对比; union需要
使用临时表去重 - group by 流程; 使用索引优化(能够实现
直接排序...) - 解释
直接排序... - generated clolumn机制
group by优化
- 如果对group by语句的结果没有排序要求,要在语句后面加
order by null;(group by默认有排序要求)- 尽量让group by过程用上表的索引,确认方法是explain结果里没有
Using temporary和Using filesort=> 索引是有顺序的,可以直接排序,查询只需要使用sortbuffer;- 如果groupby需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size参数,来避免用到磁盘临时表;
- 如果数据量实在太大,使用SQL_BIG_RESULT这个提示,来告诉优化器
直接使用排序算法得到group by的结果
(1)group by 默认要求排序,使用
order by null禁止排序; (2) group by 需要使用use temporary临时存储统计数据, 再使用use filesort对临时表排序; 再返回结果; (3) 如果group by 使用了索引,索引以已经有序..只需要使用sort buffer就可以....
临时表优化查询的方法...
- create temprary t
- alter t add index(x)
- insert t selec * tt where x>0
- 使用临时表查询....
sortbuffer, 内存临时表,join_buffer 对比
- 数据库自增id有哪些?
- PK:unsigned_int(32),big_int(64)
- row_id:InnoDB系统自增row_id;
- InnoDB没有主键时插入
- 计算方式row_id(48),循环利用
- row_id重复则覆盖....(还是要有自己的PK)
- Xid,binlog/redolog里面标记事务的一个id,
xid通过内存变量获取重启清零,mysql重启生成新的binlog,对于同一个binlog,xid是唯一的 - trx_id
- thread_id
xid,trx_id对比
- xid是Server维护,Innodb使用;是为了redoLog,binlog之间的配合使用
- trx_id是InnoDB自己维护的
- trx_id是为了支持MVCC,
通过一致性视图与当前行的Trx_id对比- trx_id(48和row_id一样大小会循环利用); 持久化存储,重启不清零
auto_increment(id);
- insert into t (id) value(0)
- 持久化存储的redolog文件
- 不连续的原因 1) 不是连续插入 2) 唯一索引插入失败 3) 事务回滚 4)防止高并发重复
- insert 语句加锁的情况
- insert 并发/insert+delete
interview
isolation-level
隔离级别-4
| 隔离级别 | 脏读(Dirty Read) | 不可重复(NonRepeatable Read) | 幻读(Phantom Read) |
|---|---|---|---|
| Read uncommitted | 可能 | 可能 | 可能 |
| Read committed | 不可能 | 可能 | 可能 |
| Repeatable read | 不可能 | 不可能 | 可能 |
| Serializable | 不可能 | 不可能 | 不可能 |
隔离级别与异常-3
- 脏读: 读到没有commit的数据;
- 重复读: 读到都是commited的数据,但是同一个事务中第二次读时被updated|delete(读到的数据值被修改)
- 幻读: 读到都是commited的数据,但是同一个事务中第二次读时被insert(读到的数据量被修改)
那么RR级别下,幻读怎么解决...
幻读的解决方式: (RR+mvcc+gap_lock)
隔离级别与日志
- RC+row,方便观察到
重复读的情况,能查看到数据值的变更记录 - RR+mixed(row+statement)
mvcc
optimistic-pessimistic
- 悲观锁:serializable,并行事务完全串行执行,读写都是排他属性
- 乐观锁:基于数据版本(version)做update
获取锁失败时的处理情况: dicard(丢弃),lock-list(排队); 大部分情况是乐观锁选择丢弃,悲观锁选择排队...
cas:compare and swap
SET session transaction isolation level read committed;
SET SESSION binlog_format = 'ROW';(或者是MIXED)
mvcc
多版本并发控制=>通过给数据多版本控制并发操作; 行记录增加了2个字端:trx_id,roll_ptr(事务ID,回滚指针)来标记不同的版本;通过mvcc控制每次能读(select)到的数据版本,从而减少读锁的使用;然后再锁住这些行进行下一步的操作(读/写).
RR + mvcc 一起解决的重复读的问题 再加gap-lock 解决幻读
lock
- S: lock in share mode
- X: for update
当前读-快照读
定义:
record-lock
- record-lock
- gap-lock
- next-key-lock
1解决的是事务update并发问题; 3,2解决的是事务insert/delete并发问题.
行锁是作用于有索引的字端,没有索引时则升级为表锁(所以update-where条件一定使用索引)....
总结+反思...
事务隔离级别定义了当前读的, mvcc通过多版本减少了读锁的添加; record-lock实现了事务隔离(update); next-key-lock解决的了幻读(insert); 美团文章很牛逼
index
索引类型
- 聚集索引
- 普通索引
最左匹配
联合索引(k1,k2,k3)=>建立索引时计算的顺序从左到右(先计算k1,再计算k2,最后计算k3); 所以最左匹配查询就是使用联合索引查询时,联合索引左边的key都尽量能等值查询; 如果不能则停止使用索引查询(一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配)
-- index(k1,k2,k3)
select * from tb where k1=2 and k2=2 and k3 >100;
-- 1. 索引只能用到k1,k2; k3开始遍历全部剩下索引
-- 2. and顺序不重要,会被mysql优化
类似场景:
- like 'x%'
索引递推
索引递推和最左匹配都是基于联合索引的场景.
回表查询
slow-sql
explain
id
执行顺序: id不同自下而上执行,id相同则自上而下执行
rows
查询的结果集
sql优化
- order by limit
- group by 1,2 order by null
字端
mind-view
ps
- 林晓斌
- 高性能MYSQL
- [mvcc]tech.meituan.com/2014/08/20/…
- [index]tech.meituan.com/2014/06/30/…
- [explain]tech.meituan.com/2014/06/30/…
- [官网]dev.mysql.com/doc/refman/…