mysql性能优化知识汇总
一、本文内容大纲
- mysql索引优化
- mysql事务管理
- mysql日志机制
- mysql全局优化
- 工作中对于mysql使用的一些技巧
本博客会保持更新,将日常工作中涉及到的mysql有用的知识囊括进来
二、mysql索引优化
1、什么是索引?
索引就是一种排好序的数据结构,而选取哪种数据结构最为高效也是mysql底层最核心的原理。
以下将讨论二叉树、红黑树、Hash表、B-Tree、B+树这5种数据结构作为mysql索引数据结构的优劣性
- 为什么mysql底层不选择二叉树?
因为二叉树这种数据结构的特点是:左子节点的值小于或等于父节点的值,右子节点的值大于或等于父节点的值。那么,当插入的数据是递增的话,二叉树会变成如下图所示的链表。
树的高度决定了树的查找效率
- 为什么不选择红黑树?
红黑树的出现就是为了解决二叉树的缺点(即极端情况下二叉树会变成 一条链表),但是当数据量过大时,其高度不可控,效率就变得很低。
随着数据量的增大,红黑树的高度变得不可控
- B树和B+树的选择


B树 B+树
B树是通过在一个节点中放很多索引来控制树的高度。
B+树是对B树的优化,B+树的非叶子节点只存放索引,不存放数据。叶子结点存放所有的索引及数据。且叶子结点间通过指针相连接。
这样B+树就产生了冗余索引,即存放了重复的索引数据,为什么要这么设计呢?
因为一个节点中存放元素的大小是有限制的(mysql限制是16KB),这样,存储相同的元素B+树比B树的高度更低(非叶子结点中只存索引比既存索引又存数据所存放的元素要高很多),此外B+树的叶子结点用指针连接,提高了区间访问的能力。如select * from table where colum > 30 and colum < 40,那么只需要查找到coluum = 30 和 colum = 40 的叶子节点元素,中间部分的元素可以直接通过指针来进行遍历。
- Hash数据结构的优势和劣势

将索引进行一次hash运算后再存储,每个索引元素都有一个对应的hash值。很多时候hash索引比B+树的效率更高,但是一般不用,因为有很多缺点:
①仅仅能满足"=","in",不支持范围查找②hash冲突问题
2、MyISAM存储引擎和InnoDB存储引擎对于索引的实现
这两个引擎的区别在于聚集索引和非聚集索引:
- 聚集索引(InnoDB引擎采用的方式)
B+树的叶子结点存放的是索引所在行其他列的所有数据,即叶子结点包含了完整的数据记录。
- 非聚集索引(MyISAM引擎采用的方式)
用B+树结构展开查找数据后,叶子结点存放的是数据所在的地址,再通过该地址去相关文件查找数据(数据在另外的一个文件中存储)。
3、explain工具的使用

- id:代表了各个表的查询顺序,id大的先查询。
- select_type:表示对应行是简单还是复杂的查询
①simple:简单查询,查询不包含子查询和union
②primary:复杂查询中最外层的select
③subquery:包含在select中的子查询(不在from子句中)
④derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)
⑤union:在 union 中的第二个和随后的 select
- table:表示正在访问哪张表
- type:表示查询的效率
依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
一般来说,得保证查询达到range级别,最好达到ref
system和const都是在mysql查询优化阶段能将其转换为一个常量,system是const的特例,当表里只有一条数据的时候就是system.
eq_ref,两张表关联通过主键关联或者唯一索引关联
ref:通过普通索引查询
range:范围查找,如in、between等
index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描直接遍历叶子结点,速度还是比较慢的。因为二级索引比主键索引要小,所以、index比ALL要快一些
all:全表扫描,效率最低
- possible_key:可能会用到的索引,实际如果数据量比较小的话可能不走索引。
- key:实际采用哪个索引,可以采用force index(索引名)的方式强制走索引。
- 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会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
- ref列:这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)
- rows列:这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
- Extra列:有以下几种情况:
1)Using index:使用覆盖索引
覆盖索引定义:mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
2)Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖
3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
4)Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。
5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的。
6)Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段
4、like、in、or是否走索引比较特殊
在用in 或者or的时候,当表的数据量较大时,会走索引,表数据量较小时可能就全表扫描不走索引了。
在用like xx%的时候不论表数据量大小都会走索引。like如此特殊的原因是使用了索引下推。
索引下推:正常来说,当使用like后,后面的字段是无序的,走不了索引,但是mysql底层对此做了优化,再遍历索引的过程中会对其他的字段同时做判断,删选出符合条件的主键id,再根据主键进行回表。

like KK%相当于=常量,%KK和%KK% 相当于范围
5、trace工具
因为开启trace工具会影响mysql的性能,所以用完之后要马上关闭
set session optimizer_trace="enabled=on",end_markers_in_json=on; -- 开启trace
然后在查询语句同时查询select * from information_schema.OPTIMIZER_TRACE
详细内容见trace工具解析
set session optimizer_trace="enabled=off",end_markers_in_json=off;-- 关闭trace
了解即可,基本上用不到
trace工具用法: 1 mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace 2 mysql> select * from employees where name > 'a' order by position; 3 mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE; 4 5 查看trace字段: 6 { 7 "steps": [ 8 { 9 "join_preparation": { ‐‐第一阶段:SQL准备阶段,格式化sql 10 "select#": 1, 11 "steps": [ 12 { 13 "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`empl oyees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`" 14 } 15 ] /* steps */ 16 } /* join_preparation */ 17 }, 18 { 19 "join_optimization": { ‐‐第二阶段:SQL优化阶段 20 "select#": 1, 21 "steps": [ 22 { 23 "condition_processing": { ‐‐条件处理 24 "condition": "WHERE", 25 "original_condition": "(`employees`.`name` > 'a')", 26 "steps": [ 27 { 28 "transformation": "equality_propagation", 29 "resulting_condition": "(`employees`.`name` > 'a')" 30 }, 31 { 32 "transformation": "constant_propagation", 33 "resulting_condition": "(`employees`.`name` > 'a')" 34 },35 { 36 "transformation": "trivial_condition_removal", 37 "resulting_condition": "(`employees`.`name` > 'a')" 38 } 39 ] /* steps */ 40 } /* condition_processing */ 41 }, 42 { 43 "substitute_generated_columns": { 44 } /* substitute_generated_columns */ 45 }, 46 { 47 "table_dependencies": [ ‐‐表依赖详情 48 { 49 "table": "`employees`", 50 "row_may_be_null": false, 51 "map_bit": 0, 52 "depends_on_map_bits": [ 53 ] /* depends_on_map_bits */ 54 } 55 ] /* table_dependencies */ 56 }, 57 { 58 "ref_optimizer_key_uses": [ 59 ] /* ref_optimizer_key_uses */ 60 }, 61 { 62 "rows_estimation": [ ‐‐预估表的访问成本 63 { 64 "table": "`employees`", 65 "range_analysis": { 66 "table_scan": { ‐‐全表扫描情况 67 "rows": 10123, ‐‐扫描行数 68 "cost": 2054.7 ‐‐查询成本 69 } /* table_scan */, 70 "potential_range_indexes": [ ‐‐查询可能使用的索引 71 { 72 "index": "PRIMARY", ‐‐主键索引 73 "usable": false, 74 "cause": "not_applicable" 75 }, 76 { 77 "index": "idx_name_age_position", ‐‐辅助索引 78 "usable": true, 79 "key_parts": [ 80 "name", 81 "age", 82 "position", 83 "id" 84 ] /* key_parts */ 85 } 86 ] /* potential_range_indexes */, 87 "setup_range_conditions": [88 ] /* setup_range_conditions */, 89 "group_index_range": { 90 "chosen": false, 91 "cause": "not_group_by_or_distinct" 92 } /* group_index_range */, 93 "analyzing_range_alternatives": { ‐‐分析各个索引使用成本 94 "range_scan_alternatives": [ 95 { 96 "index": "idx_name_age_position", 97 "ranges": [ 98 "a < name" ‐‐索引使用范围 99 ] /* ranges */, 100 "index_dives_for_eq_ranges": true, 101 "rowid_ordered": false, ‐‐使用该索引获取的记录是否按照主键排序 102 "using_mrr": false, 103 "index_only": false, ‐‐是否使用覆盖索引 104 "rows": 5061, ‐‐索引扫描行数 105 "cost": 6074.2, ‐‐索引使用成本 106 "chosen": false, ‐‐是否选择该索引 107 "cause": "cost" 108 } 109 ] /* range_scan_alternatives */, 110 "analyzing_roworder_intersect": { 111 "usable": false, 112 "cause": "too_few_roworder_scans" 113 } /* analyzing_roworder_intersect */ 114 } /* analyzing_range_alternatives */ 115 } /* range_analysis */ 116 } 117 ] /* rows_estimation */ 118 }, 119 { 120 "considered_execution_plans": [ 121 { 122 "plan_prefix": [ 123 ] /* plan_prefix */, 124 "table": "`employees`", 125 "best_access_path": { ‐‐最优访问路径 126 "considered_access_paths": [ ‐‐最终选择的访问路径 127 { 128 "rows_to_scan": 10123, 129 "access_type": "scan", ‐‐访问类型:为scan,全表扫描 130 "resulting_rows": 10123, 131 "cost": 2052.6, 132 "chosen": true, ‐‐确定选择 133 "use_tmp_table": true 134 } 135 ] /* considered_access_paths */ 136 } /* best_access_path */, 137 "condition_filtering_pct": 100, 138 "rows_for_plan": 10123, 139 "cost_for_plan": 2052.6, 140 "sort_cost": 10123,141 "new_cost_for_plan": 12176, 142 "chosen": true 143 } 144 ] /* considered_execution_plans */ 145 }, 146 { 147 "attaching_conditions_to_tables": { 148 "original_condition": "(`employees`.`name` > 'a')", 149 "attached_conditions_computation": [ 150 ] /* attached_conditions_computation */, 151 "attached_conditions_summary": [ 152 { 153 "table": "`employees`", 154 "attached": "(`employees`.`name` > 'a')" 155 } 156 ] /* attached_conditions_summary */ 157 } /* attaching_conditions_to_tables */ 158 }, 159 { 160 "clause_processing": { 161 "clause": "ORDER BY", 162 "original_clause": "`employees`.`position`", 163 "items": [ 164 { 165 "item": "`employees`.`position`" 166 } 167 ] /* items */, 168 "resulting_clause_is_simple": true, 169 "resulting_clause": "`employees`.`position`" 170 } /* clause_processing */ 171 }, 172 { 173 "reconsidering_access_paths_for_index_ordering": { 174 "clause": "ORDER BY", 175 "steps": [ 176 ] /* steps */, 177 "index_order_summary": { 178 "table": "`employees`", 179 "index_provides_order": false, 180 "order_direction": "undefined", 181 "index": "unknown", 182 "plan_changed": false 183 } /* index_order_summary */ 184 } /* reconsidering_access_paths_for_index_ordering */ 185 }, 186 { 187 "refine_plan": [ 188 { 189 "table": "`employees`" 190 } 191 ] /* refine_plan */ 192 }193 ] /* steps */ 194 } /* join_optimization */ 195 }, 196 { 197 "join_execution": { ‐‐第三阶段:SQL执行阶段 198 "select#": 1, 199 "steps": [ 200 ] /* steps */ 201 } /* join_execution */ 202 } 203 ] /* steps */ 204 } 205 206 结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描 207 208 mysql> select * from employees where name > 'zzz' order by position; 209 mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE; 210 211 查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描 212 213 mysql> set session optimizer_trace="enabled=off"; ‐‐关闭trace
6、mysql索引优化原则
- order by 和 group by 优化
尽量在索引上进行排序,遵循最左前缀原则。group by的实质是先排序后分组,如果不需要排序可以加上order by null 来禁止排序,注意where要高于having,能写在where中的限定条件就不要去having限定了。
- 索引设计原则
①当主体代码都写完之后再统筹的去建立索引
②可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句中的where、order by、group by的字段,尽量满足最左前缀原则
③尽量在值比较多的字段上建立索引,这样才能发挥出B+树二分查找的优势
④对于varchar(255)的大字段上建立索引,可以取该字段的前20位建立索引,类似 key index(name(20),age,position)。如果采用了这种方式,对于where查询来说是可以的,但是对于order by 和 group by就没有作用了
⑤where与order by冲突时优先where
⑥一般范围查找的字段要建在最后面,因为一旦涉及到范围查找它后面的索引都不生效了。
- 分页优化
①对于select * from table limit 90000,10
这种查询方式效率很低,mysql会查询出从第一条查询到90010条记录,并将前90000条记录舍弃掉
对于主键自增且连续的表(效率极高)
select * from table where id > 90000 limit 10
②根据非主键字段排序并分页
select * from table order by name limit 90000,10
这样查询效率很低,即便name建立了索引也有可能不走索引
select * from table e inner join (select id from table order by bane limit 90000,10) ed on e.id = ed.id
这样用覆盖索引去优化,会走索引,效率较高
- join连接优化
select * from a inner join b on a.id = b.id
mysql底层会选择一张小表作为驱动表,对于小表做全表扫描,然后再拿扫描到的结果去大表中根据关联条件做过滤
对于大表而言,连接条件一定要建立索引
一般默认小表作为驱动表,如果mysql选择驱动表错了,可以手动指定驱动表
select * from a straight_join b on a.id = b.id 代表选择b作为驱动表
- in和exists优化
select * from a where id in (select id from b)
这种写法相当于是先查询出b的id,然后拿着这个id循环去a表里面查询,对于b的数据集小于a的数据集的时候,in要优于exists
select * from a where exists (select 1 from b where b.id = a.id)
这种写法相当于是先查询出a的所有结果,然后拿着查询出的全部id循环去b表里面查询,对于a的数据集小于b的数据集的时候,exists要优于in
- count(*)优化
count(*),count(1),count(id),count(name)这四种写法效率几乎一样,差别可忽略不计,尽量使用count(*),因为count(*)会统计null值,count(列名)则不会
对于MyISAM引擎来说,count(*)的效率极高,因为底层会在一个地方维护这个表的总数
对于InnoDB引擎来说,如果表数据量很大,count(*)的效率也不会很高,可以用show table status like 表a 来用Rows粗略估计数量,但是不准,慎用。
- 建表规范
1、单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表
2、业务上具有唯一特性的字段,必须建成唯一索引
3、超过3张表不允许使用join
4、查询尽量避免使用左模糊或者全模糊查询,如果一定要使用建议走搜索引擎
5、尽量避免使用in,如果非要使用要控制in后面的集合元素要控制在1000个之内
6、所有字符存储要使用utf8字符集,存储表情用utf8mb4
- 各种类型数据选择

尽量使用更小的数据类型,更节省空间。尽量把字段定义成NOT NULL,避免使用NULL。
如果确定数字类型的数据没有负数,一定要勾选无符号,可以扩大一倍的数据范围。
对于char()和varchar()的选择,因为char类型是定长的字符串,如果长度不够尾部会补空格(检索的时候自动去掉空格),varchar是变长字符串。如果确定每条数据的长度都是一致的可以用CHAR,否则还是用varchar吧
二、mysql事务管理
1、什么是事务?
一组操作要么成功,要么失败,目的是保证数据最终的一致性。
- 原子性:当前事务的操作要么同时成功,要么同时失败。原子性由 undo log日志来实现。
- 一致性:使用事务的最终目的,由其他3个特性和业务逻辑的正确性保证的。
- 隔离性:在事务并发执行时,他们内部的操作不能互相干扰。由mysql的各种锁以及MVCC机制来实现。
- 持久性:一旦提交了事务,它对数据库的改变就应该是永久的。持久性由redo logo日志来实现。
2、4种隔离级别
set tx_isolation='read-uncommitted';
set tx_isolation='read-committed';
set tx_isolation='repeatable-read';
set tx_isolation='serializable';
1、读未提交(RU) 脏读 备注:读到了别的事务中未提交的数据。
脏读最大的问题就是可以读到别的事务中未提交的数据。这时候拿着这个数据去进行修改操作后,如果之前未提交的事务发生了回滚,那么之前查到的数据就是脏数据,如果要解决脏读问题,可以采用“读已提交”
2、读已提交 (RC) 不可重复度 备注:查询结果受其他事务影响
不可重复读最大的问题就是,在用一事务中的同一条查询语句查询。如果两次查询的中间有别的事务提交修改了该数据,那么两次查询的结果就会不同。
3、可重复读(RR) 幻读 备注:在开启事务的时候会开启一个快照,查询过程中不受其他事务影响
可重复读最大的问题就是,当我开启了两个事务a,b。其中b更改了某个表的数据,并提交了事务,当然这时候事务a并查不到b提交的数据,但是a却可以通过update语句修改这条数据,很明显是脏写了。
可重复读会存在脏写的情况,怎么解决呢?
直接在事务中用更新语句更新,这样的话在更新的时候就会用数据库最新的数据来进行更新。如:
update a set b = b*20 where id = 1,这样,b的值就会被用实际数据库的数据*20来运算。
4、串行 解决上面所有问题,包括脏读。但是生产环境一般不用串行,效率太低。
持久性:一旦提交了事务,他对数据库的改变就应该是永久性的。持久性由redo log日志来实现
一致性:使用事务的最终目的,由其他3个特性以及业务代码正确逻辑来实现
3、事务的优化原则
①将查询等数据准备操作放到事务外
②事务避免远程调用,远程调用要设置超时,防止事务等待太久
③事务中避免一次性处理太多数据,可以拆分成多个事务分次处理
④更新等涉及加锁的操作尽可能放在事务靠后的位置
⑤能异步处理的尽量异步处理
⑥应用侧(业务代码)保证数据一致性,非事务执行
4、事务时间过长杀掉进程(锁表、卡ddl)
查询事务执行时间超过10秒的事务
select * from information_schema.innodb_trx
where TIME_TO_SEC(timediff(now(),trx_started)) > 10;
查到线程id,直接kill id
5、查询操作方法需要使用事务吗?
一般是不用的,但是如果是数据修改比较频繁的业务场景,且查询时间较长(报表场景)。最好加一个可重复读的事务,因为可重复读的“快照”特性,使得就算查询时间很长,前后两个sql查的都是快照下的数据,虽然查询的不是最新的数据库中的数据,但是能保证两个数据的时间维度相同。
三、mysql锁机制及MVCC底层原理
1、锁的分类
- 乐观锁:乐观锁适合读操作较多的地方。如果在写操作较多的场景使用乐观锁会导致对比次数过多,影响性能。
-
举个例子:有事务A、B对某个数据同时进行更新操作,update table set colum = 500 where id= 1 and version = 1,当事务A对某个数据做修改时,会对其版本号+1,当事务B再执行此更新操作时,数据库中已经没有版本为1的数据了。
事务B就会再次执行update table set colum = 500 where id= 1 and version = 2,不断轮询版本号,直到找到数据库中存储的版本号,修改完数据为止。
-
- 悲观锁:悲观锁适合写操作较多的地方。
举个例子:事务A、B对某个数据同时进行更新操作,想将colum的值+50,那么事务A先执行了Update table set colum = colum + 50 where id = 1,事务B等待A提交完事务之后再次执行同样的操作。
- 行锁:开销大、加锁慢、并发高
InnoDB的行锁是针对索引上加的锁(在对应索引项上做标记),不是在整行记录上加的锁。并且该索引不能失效,否则会从行锁升级为表锁(RR级别会升级为表锁,RC级别不会升级为表锁)。
为什么不走索引的情况下RR级别会从行锁升级为表锁,而RC级别不会呢?
因为在RR隔离级别下,需要解决不可重复读和幻读的问题,所以在遍历扫描聚集索引记录时,为了防止扫描过的索引被其他事务修改(不可重复读问题)或间隙被其它事务插入记录(幻读问题),从而导致数据不一致,
所以mysql的解决方案就是把所有扫描过的索引记录和间隙都锁上。
- 表锁:开销小、加锁慢、并发低
- 页锁:Innodb不支持
- 间隙锁:只要在数据间隙范围内锁了一条不存在的记录,那么就会锁住整个间隙范围(不锁边界记录),这样就能防止其他事务在这个间隙范围内插入数据,就解决了可重复读隔离级别的幻读问题。
2、查询数据库中的事务及锁
show status like 'innodb_row_lock%';
对各个状态量的说明如下:
Innodb_row_lock_current_waits: 当前正在等待锁定的数量
Innodb_row_lock_time: 从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg: 每次等待所花平均时间
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间
Innodb_row_lock_waits: 系统启动后到现在总共等待的次数
对于这5个状态变量,比较重要的主要是:
Innodb_row_lock_time_avg (等待平均时长)
Innodb_row_lock_waits (等待总次数)
Innodb_row_lock_time(等待总时长)
-- 查看事务
select * from INFORMATION_SCHEMA.INNODB_TRX;
-- 查看锁,8.0之后需要换成这张表performance_schema.data_locks
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
-- 查看锁等待,8.0之后需要换成这张表performance_schema.data_lock_waits
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到
kill trx_mysql_thread_id
3、MVCC底层原理
mysql在读已提交和可重复读这两个隔离级别下都实现了MVCC(多版本并发控制机制)
MVCC的核心实现原理是undo日志(事务历史记录版本链)和read-view(一致性视图),下面举一个例子深入理解这两个东西:


在可重复读隔离级别:当事务开启,执行任何查询sql时都会形成当前事务的一致性视图read-view,该视图在事务结束之前都不会发生变化(如果是读已提交的话每次查询都会重新生成read-view),
四、mysql日志机制
1、一条sql执行的全流程

总结为以下几点:
update table set name = '1' where id = '1'
1、将id为1的记录所在的一整页数据加载进缓存池
2、将更新数据的旧值写入undo日志中,形成版本链,便于回滚
3、将第一步缓存池中的数据更新到内存中
4、写redo日志,将redo日志写到redo log buffer缓存中
5、将redo日志顺序写入磁盘,准备提交事务
6、向binlog日志写入磁盘
7、写入commit标记到redo日志中,提交事务完成,该标记为了保证事务提交后redo与binlog数据一致
8、在系统空闲时将数据随机以page为单位写入到磁盘表文件中
2、redo log日志和binlog日志
redo log 日志的作用的当事务成功提交,但是缓存池中的数据没有及时写入磁盘中,可以用redo日志来进行恢复。
binlog日志的作用是磁盘文件的数据被删除了,想要恢复。
- redo log日志关键参数
1、innodb_log_buffer_size:设置redo log buffer大小参数,默认16M,最大值是4096M,最小值为1M
show variables like '%innodb_log_buffer_size%'
2、innodb_log_group_home_dir:设置redo log文件存储位置参数,默认为"./",即innodb数据文件存储位置,其中ib_logfile0 和 ib_logfile1即为redo log文件。
show variables like '%innodb_log_group_home_dir%'
3、innodb_log_files_in_group:设置redo log文件的个数,默认2个,最大100个
show variables like '%innodb_log_files_in_group%'
4、innodb_log_file_size:设置单个redo log文件大小,默认值是48M,最大值为512G,注意最大值指的是整个redo log系列文件之和
show variables like '%innodb_log_file_size%'
5、innodb_flush_log_at_trx_commit:这个参数控制redo log的写入策略,它有三种可能取值:
设置为0:表示每次事务提交时都只是把 redo log 留在 redo log buffer 中,数据库宕机可能会丢失数据。
设置为1(默认值):表示每次事务提交时都将 redo log 直接持久化到磁盘,数据最安全,不会因为数据库宕机丢失数据,但是效率稍微差一点,线上系统推荐这个设置。
设置为2:表示每次事务提交时都只是把 redo log 写到操作系统的缓存page cache里,这种情况如果数据库宕机是不会丢失数据的,但是操作系统如果宕机了,page cache里的数据还没来得及写入磁盘文件的话就会丢失数据。
InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 操作系统函数 write 写到文件系统的 page cache,然后调用操作系统函数 fsync 持久化到磁盘文件。

# 查看innodb_flush_log_at_trx_commit参数值:
show variables like 'innodb_flush_log_at_trx_commit';
# 设置innodb_flush_log_at_trx_commit参数值(也可以在my.ini或my.cnf文件里配置):
set global innodb_flush_log_at_trx_commit=1;
- binlog日志
5.7默认关闭,8.0默认开启
- 开启binlog日志
需要修改配置文件my.ini(windows)或my.cnf(linux),然后重启数据库。 在配置文件中的[mysqld]部分增加如下配置:
# log‐bin设置binlog的存放位置,可以是绝对路径,也可以是相对路径,这里写的相对路径,则binlog文件默认会放在data数据目录下 log‐bin=mysql‐binlog # Server Id是数据库服务器id,随便写一个数都可以,这个id用来在mysql集群环境中标记唯一mysql服务器,集群环境中每台mysql服务器的id不能一样,不加启动会报错
server‐id=1 # 其他配置 binlog_format = row
# binlog_format可以配三个参数,各参数代表的含义如下所示:
- STATEMENT:基于SQL语句的复制,每一条会修改数据的sql都会记录到master机器的bin-log中,这种方式日志量小,节约IO开销,提高性能,但是对于一些执行过程中才能确定结果的函数,比如UUID()、SYSDATE()等函数如果随sql同步到slave机器去执行,则结果跟master机器执行的不一样。
- ROW:基于行的复制,日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改记录下每一行数据修改的细节,可以解决函数、存储过程等在slave机器的复制问题,但这种方式日志量较大,性能不如Statement。举个例子,假设update语句更新10行数据,Statement方式就记录这条update语句,Row方式会记录被修改的10行数据。
- MIXED:混合模式复制,实际就是前两种模式的结合,在Mixed模式下,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种,如果sql里有函数或一些在执行时才知道结果的情况,会选择Row,其它情况选择Statement,推荐使用这一种。
# expire_logs_days = 15 # 执行自动删除binlog日志文件的天数, 默认为0, 表示不自动删除 max_binlog_size = 200M # 单个binlog日志文件的大小限制,默认为 1GB
# show variables like '%log_bin%'; # 查看binlog是否开启
# show binary logs; # 查看binlog存放的位置
①服务器启动或重新启动
②服务器刷新日志,执行命令flush logs
③日志文件大小达到 max_binlog_size 值,默认值为 1GB
- 删除 binlog 日志文件:
删除当前的binlog文件 reset master; # 删除指定日志文件之前的所有日志文件,下面这个是删除6之前的所有日志文件,当前这个文件不删除 purge master logs to 'mysql-binlog.000006'; # 删除指定日期前的日志索引中binlog日志文件 purge master logs before '2023-01-21 14:00:00';
- 查看bnlog日志
mysqlbinlog --no-defaults -v --base64-output=decode-rows "binlog文件地址"
- binlog日志文件恢复数据
有两种方式,可以通过binlog的开始结束位置恢复,也可以通过起始结束时间来恢复
mysqlbinlog --no-defaults --start-position=219 --stop-position=701 --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test # 找到第一条sql BEGIN前面的时间戳标记 SET TIMESTAMP=1674833544,再找到第二条sql COMMIT后面的时间戳标记 SET TIMESTAMP=1674833663,转成datetime格式 mysqlbinlog --no-defaults --start-datetime="2023-1-27 23:32:24" --stop-datetime="2023-1-27 23:34:23" --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test
- 定期备份数据库
binlog与定期备份数据库相结合才可以做到恢复数据的功效,以下是几个备份数据库的命令
mysqldump -u 用户名 -p 数据库名 > 备份文件路径.sql; #备份单个数据库
mysqldump -u 用户名 -p --all-databases > 备份文件路径.sql;#备份整个数据库
mysqldump -u 用户名 -p 数据库名 表1 表2 > 备份文件路径.sql; #备份指定表mysql -u root -p test < 备份文件名 #恢复整个数据库,test为数据库名称,需要自己先建一个数据库test五、mysql全局优化与mysql8.0新特性
1、mysql全局参数配置(提升的性能不是特别高,一般不是dba不建议自己去优化)
下面参数都是服务端参数,默认在配置文件的 [mysqld] 标签下
max_connections=3000 # 最大连接数
max_user_connections=2980 #允许用户最大连接数,剩余的20个连接数作为管理员使用
back_log=300 #MySQL能够暂存的连接数量。如果MySQL的连接数达到max_connections时,新的请求将会被存在堆栈中,等待某一连接释放 资源,该堆栈数量即back_log,如果等待连接的数量超过back_log,将被拒绝。
wait_timeout=300 # 指的是app应用通过jdbc连接mysql进行操作完毕后,空闲300秒后断开,默认是28800,单位秒,即8个小时。这里基本上得改,默认的太大了。
innodb_buffer_pool_size=40G #innodb存储引擎buffer pool缓存大小,一般为物理内存的60%-70%。
innodb_lock_wait_timeout=10 # 行锁锁定时间,默认50s,根据公司业务定,没有标准值。
2、mysql 8.0新特性
- 新增降序索引
create table t1(c1 int,c2 int,index idx_c1_c2(c1,c2 desc));
这时候如果查询的时候按照c1升序,c2降序(或者相反)的顺序进行排序,那么将不会走文件排序,而是利索索引排序。
- group by 不再隐式排序
5.7版本的mysql在group by 之后会自动根据group by字段做一个排序,而8.0版本的不再自动排序。
- 增加隐藏索引
alter table t2 alter index idx_c2 visible; # 取消设置隐藏索引
alter table t2 alter index idx_c2 invisible; # 设置为隐藏索引
在生产环境中,如果想删掉一个索引,可以先把它设置为隐藏索引,当确定其没有用了之后再删掉就行了。
- 新增函数索引
基本上用不到,很鸡肋。
- 窗口函数
在聚合函数后面加上over()就变成分析函数了,后面可以不用再加group by制定分组,因为在over里已经用partition关键字指明 了如何分组计算,这种可以保留原有表数据的结构,不会像分组聚合函数那样每组只返回一条数据。
select name,channel,balance,sum(balance) over(partition by name) as sum_balance from account_channel;
- 默认字符集由latin1变为utf8mb4
六、工作中用到的一些不常见的sql语句
-- 查看建表ddl语句 show create table a; -- 查看表中所有字段信息 describe a -- 创建1个表b和表a的表结构一样 create table b like a; -- 创建1个表c和表a的表结构和数据都一样 CREATE TABLE c AS SELECT * FROM a;
浙公网安备 33010602011771号