引言

MySQL是目前使用最为广泛的关系型数据库之一,如果使用得当,可支撑企业级高并发、高可靠服务,使用不当甚至连并发量略高的个人网站都难以支撑;

就算使用了缓存,大量的数据库访问依旧在所难免,即使设置了较长的缓存有效期,而且缓存命中率较理想,但缓存的创建过期后的重建都是需要访问数据库的;

本文主要从MySQL表结构设计规范MySQL自身性能优化两方面来讨论该如何对MySQL数据库进行优化;

MySQL表结构设计规范

1. 数据库设计命名规范

(1)数据库,数据表一律使用前缀,前缀名称一般不超过5字;

# 正式数据库名使用小写英文以及下划线组成,尽量说明是哪个应用或者系统在使用的;
mcs_webim
ops_portal # 备份数据库名使用正式库名加上备份时间组成;
ops_portal_20150621
mcs_webim_20150622 # 相关应用的数据表使用同一前缀,前缀名称一般不超过5字;
webim_user
webim_group # 备份数据表名使用正式表名加上备份时间组成;
webim_user_20150620
webim_group_20150620

(2)字段名称使用单词组合完成,首字母小写,后面单词的首字母大写(驼峰式),最好是带表名前缀;

# 如表webim_user的字段
userId
userName
userPassword
# 表与表之间的相关联字段要用统一名称;
# 用尽量少的存储空间来存储一个字段的数据;

2. 数据库规范化设计

(1)范式化设计

实际关系模式设计中,一般遵循第三范式——在一个数据表中,非主键字段之间不能存在依赖关系;

具体可参考:规范化—数据库设计原则

(2)反范式化设计

举例:在页面显示我的好友列表;
1.遵循第三范式

(用户ID, 好友ID)
(用户ID, 用户昵称, 用户邮箱, 注册时间, 联系电话)

2.反范式化设计

(用户ID, 好友ID, 好友昵称)
(用户ID, 用户昵称, 用户邮箱, 注册时间, 联系电话)

反范式化问题:
1. 数据冗余;
2. 更新导致数据不一致问题,可通过定期同步的手段来修改不一致数据;

反范式化优势:
减少读取数据的开销,这点非常重要,需要根据不同场景来适当使用反范式化设计;

MySQL自身性能优化

MySQL自身优化主要从如下几方面来介绍:

1. Query语句优化;
2. 慢查询优化;
3. MySQL锁机制分析优化;
4. 参数配置优化;

Query语句优化

1. MySQL状态报告

show status;
show engine innodb status;

一些更加友好的第三方工具:mysqlreport下载地址),mysqltunnermytop等,可展示更加友好的状态报告;

2. 正确使用索引

如果索引使用不当,其他任何优化将毫无意义;

索引目的

索引类似于书的目录,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,加快查询速度;

索引类型

索引分单列索引和组合索引,组合索引的一个索引包含多列;
MySQL索引类型包括:

(1)普通索引——没有任何限制

# 直接创建索引
create index indexName on tableName(columnName(length));
# 如果是char,varchar类型,length长度可以小于字段实际长度;
# 如果是blob和text类型,必须指定length; # 修改表结构
alter tableName add index indexName on (columnName(length)); # 创建表时直接指定
create table tableName(
id int not null,
username varchar(16) not null,
index indexName (columnName(length))
); # 删除索引
drop index indexName on tableName; # 查看索引
show index from tableName;

(2唯一索引——索引列的值必须唯一,允许有空值,如果是组合索引,则列值的组合必须唯一;

# 直接创建索引
create unique index indexName on tableName(columnName(length)); # 修改表结构
alter tableName add unique indexName on (columnName(length)); # 创建表时直接指定
create table tableName(
id int not null,
username varchar(16) not null,
unique indexName columnName(length)
);

(3)主键索引——是唯一索引的一种,但不允许有空值,一般是建表的时候直接创建主键索引;

create table tableName(
id int not null,
username varchar(16) not null,
primary key(id)
);

(4)组合索引——为多列添加索引;

# 假设数据表中name, age, address, zip等多个字段,需要为name, age, zip建立组合索引;
create index indexName on tableName(name(10), age, zip);
# 或
alter table tableName add index indexName (name(10), age, zip);

对于varchar类型字段,如果长度过长,最好限制一下索引的长度,可以加快索引查询速度,减少索引文件的大小;

最左前缀匹配

如上面的name,age,zip的组合索引,如下的组合都会用到该索引,可使用explain来进行分析:

select * from tableName where name="lee" and age=20 and zip="050000";
select * from tableName where name="lee" and age=20;
select * from tableName where name="lee"; # 组合索引对于包含order by和group by的查询也可发挥作用,同样遵循最左前缀原则(对于Hash索引,对order by无效);
select * from tableName order by name, age, zip;
select * from tableName where name="lee" order by age, zip; # 对于group by,一般需要先将记录分组后放在新的临时表中,然后分别进行函数运算,如count(),sum(),max()等;
# 如有恰当的索引,可使用索引来取代创建临时表;
select count(id) from tableName where sex='m' group by age, zip;

查询优化器会自动调整条件表达式的顺序,以匹配组合索引;
建立索引时一定要注意顺序,(key1, key2)和(key2, key1)完全不同;

建立索引的时机

一般来说,在wherejoin中出现的列需要建立索引,mysql只对<<==>>=betweenin以及某些时候(不以通配符%和_开头的查询)的like才会使用索引;

select a.name from table1 as a left join table2 b on a.name=b.username where b.age=20 and b.zip='053000';
# 此时,需要对username,age和zip建立索引;

索引的不足之处

索引有很大优势,但是不能滥用,需要根据实际情况来决定到底使不使用索引,该为哪些字段建索引,一般在查询量占比较多的表才会建立索引;

  • 索引会降低更新表的速度,如insert,update,delete操作,更新表时不仅需要保存数据,还要保存索引文件;
  • 过多的组合索引会大大加剧索引文件的膨胀速度,引起磁盘空间存储的问题,一个包含多个字段的组合索引的尺寸可能已经超过了数据本身,而且索引过多,可也能会使MySQL选择不到要使用的最好的索引(可使用use index(key_list)来指定查询时使用的索引);
  • 对于唯一值的列,索引效果最好,对于具有多个重复值的列,如年龄或性别,建立索引不是好办法;

使用索引注意事项

  1. 索引不会包含有NULL值的列,在数据库设计时尽量不要让字段的默认值为NULL,否则无法建立相关字段的索引;
  2. 使用短索引,对varchar类型字段建索引时最好指定长度,只要保证前n个字符多数值是唯一的即可,提高查询速度,节省磁盘空间,降低I/O操作;
  3. MySQL查询只是用一个索引,因此如果一条查询语句中有多个字段需要建索引,最好按照最左前缀匹配原则建立组合索引;
  4. like语句一般不鼓励使用,在数据量大的情况下,非常容易造成性能问题,如果非使用,通配符%一定要放到后面,如like "abc%";
  5. 不要在列上进行运算,如select * from users where YEAR(datetime)<2015;,会导致索引失效,进行全表扫描;
  6. 不要使用NOT IN和IN;

索引的原理

主要参考:MySQL索引原理及慢查询优化

3. join语句优化

join语句分为内连接和外连接;

内连接:

select * from a inner join b on a.id = b.id;
# 等价于
select * from a,b where a.id = b.id;

内连接就是检索出与连接条件完全匹配的数据行;

外连接:

select id, name, action from user as u left join user_action a on u.id = a.user_id;

外连接保留了所有驱动表的数据,匹配表中无法匹配的数据则以NULL输出;

外连接工作原理

从左表读取一条记录,选出所有与on中条件匹配的右表记录的(n条)数据,进行连接,形成n条记录(包括重复的行),如果右边没有与on条件匹配的记录,那连接的字段都是null,继续读下一条;

找出所有在左表而不在右表中的记录:

# 注意:a.user_id必须声明为NOT NULL,如果a,u两表连接条件中的两个列具有相同的名字,可使用using(col);
select id, name, action from user as u
left join user_action a on u.id = a.user_id
where a.user_id is NULL;
# 查询时手动指定索引
select * from table1 use index (key1, key2) where key1=1 and key2=2 and key3=3;
select * from table1 ignore index (key3) where key1=1 and key2=2 and key3=3;

慢查询优化

开启慢查询日志:

# 在my.cnf中增加如下配置:
long_query_time = 1
log-slow-queries = /var/log/mysql/mysql_slow.log
# 将所有没有使用索引的查询记录也记录下来(根据需要决定是否开启):
log-queries-not-using-indexes

慢查询工具mysqlsla,可使用此工具对慢查询日志进行分析;

# mysqlsla -lt slow /var/log/mysql/mysql_slow.log

大多数慢查询都是因为索引使用不当造成的,使用索引时一定要谨慎,其他原因还有查询语句本身太过复杂(多表联合查询)数据表记录数太多等;

锁机制分析与优化

锁机制是影响查询性能的另一个重要因素;

查询的时间开销主要包括两部分:

1. 查询本身的计算时间;(主要受索引影响) 
2. 查询开始前的等待时间;(受锁机制影响)

减少表锁定等待

MyISAM类型表提供了表级别锁定,可使用mysqlreport来查看等待表锁定查询所占的比例;

MyISAM的表锁定允许多线程同时读取数据,如select查询,无需锁等待;

对于更新操作,如update、insert、delete操作,会排斥对当前表的所有查询,并且更新操作有着默认的更高优先级,即当表锁释放后,更新操作将先获得锁定,全部执行完毕后,才轮到读取操作,应尽量避免在有大量查询请求时,批量更改数据表,否则非常容易造成慢查询;

可使用如下命令监视所有线程的状态:

show processlist\G;

结论:

对于以查询操作为主,并且更新操作耗时较低的应用,将不会存在太多的锁等待,可以使用MyISAM存储引擎;
对于有频繁数据更新并且查询请求量也不低的站点,必须使用提供行锁定功能的Innodb存储引擎;

行锁定

Innodb存储引擎提供了行锁定的支持;

行锁定优势:在select和update混合的情况下,行锁定可以解决读和写互斥的问题,由于update操作和select操作来自不同的线程,并且针对的是不同行的记录,可以并发进行;

行锁定并不一定总是好的:

1. 行锁定的开销并不比表锁定小;

2. 在全部都是更新操作的场景下,行锁定耗时可能会更长,虽然表锁定每次只有一个线程处于Updating状态,而行锁定所有线程都是Updating状态,但锁定只是一种逻辑层面的约束,即使全部线程都是Updating状态,但是磁盘的物理写操作还是串行执行的;

3. 对于全部查询的场景,行锁定也需要更多额外的开销,速度相对表锁定略慢;

存储引擎查看

show table status from DataBaseName where name='TableName';
alter table tableName type=myisam;

参数配置优化

事务性表性能优化

Innodb存储引擎除了支持行锁定,外键以及其易于修复的特性,另一个优势就是其支持事务(ACID),当然,事实上大多数站点都不需要事务级别的保障;
Innodb是通过预写日志(WAL)方式来实现事务的,即当有事务提交时,首先写入内存中的事务日志缓冲区,随后当事务日志写入磁盘时,Innodb才更新实际的数据和索引;
如果选择使用事务,那事务日志何时写入磁盘,就是一个优化点了;

# 事务提交时立即将事务日志写入磁盘,数据和索引也立即更新,符合持久性原则;
innodb_flush_log_at_trx_commit = 1 # 事务提交时不立即写入磁盘,每隔1S写入磁盘文件一次,并刷新到磁盘,同时更新数据和索引;
# 如果mysql崩溃,事务日志缓冲区中最近1秒内的数据永久性丢失;
innodb_flush_log_at_trx_commit = 0 # 事务提交时立即写入磁盘文件,但间隔1S才会刷新磁盘,同时更新数据和索引;
# 操作系统崩溃才会造成数据损失;
innodb_flush_log_at_trx_commit = 2

注意:

“写入磁盘文件”只是将数据写入位于物理内存中的内核缓冲区,“刷新到磁盘“是将内核缓冲区中的数据真正写入到磁盘;

innodb_flush_log_at_trx_commit设置为0,可以获得最好的性能,同时数据丢失的可能性也最大;如果希望尽量避免数据丢失,可设置为2;

# 设置Innodb数据和索引的内存缓冲池大小,一般可设置为服务器物理内存的80%;
innodb_buffer_pool_size = 12G

使用查询缓存

目的:将select的查询结果缓存在内存中,以供下次直接获取;

query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 1M

对于缓存过期策略,MySQL采用的机制是:当一个表有更新操作后,涉及这个表的所有查询缓存都会失效;

这个看场景,对于密集select操作且很少更新的表,比较适合使用查询缓存;对于select和update混合的应用,不适合使用查询缓存;

临时表

目的:在磁盘上创建临时表非常耗时,开销大,需要降低在磁盘上创建临时表的次数;

# 尽量给临时表设置较大的内存空间,当内存空间不够时,MySQL将会启用磁盘来保存临时表;
tmp_table_size = 512M

线程池

MySQL采用多线程来处理并发连接,如果每次都新建连接,都要创建新的线程,在系统繁忙的时候,也会增加MySQL的开销;

# 尽量使用持久连接,减少线程的重复创建;
thread_cache_size = 100
# 可以使MySQL缓存100个线程;

参考

MySQL设计规范与性能优化的更多相关文章

  1. MySQL 基础及性能优化工具

    数据库,用户及权限 常用用户管理操作 # 创建本地用户 abc create user abc@localhost # 创建内网能够访问的用户 abc create user abc@'192.168 ...

  2. mysql配置以及性能优化(转)

    MySQL配置文件my.cnf中文详解,附mysql性能优化方法分享 ================================================================= ...

  3. 利用 gperftools 对nginx mysql 内存管理 性能优化

    利用 gperftools 对nginx 与 mysql  进行 内存管理  性能优化 降低负载. Gperftools 是由谷歌开发.官方对gperftools 的介绍为: These tools ...

  4. MySQL系列:性能优化

    1. 优化简介 MySQL性能优化包括:查询优化.数据库结构优化.MySQL服务器优化等. 2. 查询优化 2.1 分析查询语句 MySQL提供EXPLAIN和DESCRIBE,用来分析查询语句. E ...

  5. 2018/09/17《涂抹MySQL》【性能优化及诊断】学习笔记(七)

    读 第十三章<MySQL的性能优化与诊断> 总结 一说性能优化,整个人都像被打了鸡血一样

  6. MySQL抓包工具:MySQL Sniffer 和性能优化

    简介 MySQL Sniffer 是一个基于 MySQL 协议的抓包工具,实时抓取 MySQLServer 端的请求,并格式化输出.输出内容包访问括时间.访问用户.来源 IP.访问 Database. ...

  7. MySql学习—— 查询性能优化 深入理解MySql如何执行查询

    本篇深入了解查询优化和服务器的内部机制,了解MySql如何执行特定查询,从中也可以知道如何更改查询执行计划,当我们深入理解MySql如何真正地执行查询,明白高效和低效的真正含义,在实际应用中就能扬长避 ...

  8. MySQL之查询性能优化(二)

    查询执行的基础 当希望MySQL能够以更高的性能运行查询时,最好的办法就是弄清楚MySQL是如何优化和执行查询的.MySQL执行一个查询的过程,根据图1-1,我们可以看到当向MySQL发送一个请求时, ...

  9. 2020重新出发,MySql基础,性能优化

    @ 目录 MySQL性能优化 MySQL性能优化简述 使用 SHOW STATUS 命令 使用慢查询日志 MySQL 查询分析器 EXPLAIN DESCRIBE 索引对查询速度的影响 MySQL优化 ...

随机推荐

  1. CodeForces 131D【图特性+BFS】

    题意: 只有一个环,然后环都是0(环缩点相当于树的根),然后其余的输出到根的距离 思路: 可以从度为1的 开始搜 把那些分支全标记掉,然后再取没有标记掉的,BFS一下搞出距离. 具体这个标记: 倒着搜 ...

  2. zoj3195(lca / RMQ在线)

    题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3195 题意: 给出一棵 n 个节点的带边权的树, 有 q 组形 ...

  3. 洛谷P3145 [USACO16OPEN]分割田地Splitting the Field

    P3145 [USACO16OPEN]分割田地Splitting the Field 题目描述 Farmer John's NN cows (3 \leq N \leq 50,0003≤N≤50,00 ...

  4. Mac安装vue

     Mac安装vue 一.安装brew 打开终端运行以下命令: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com ...

  5. 求最短路径(Bellman-Ford算法与Dijkstra算法)

    前言 Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的.这时候,就需要使用其他的算法来求 ...

  6. Unity 坐标

    一. transform.position 世界坐标 transform.localposition 相对于父亲的坐标 二. 三.应用 1.物体跟随鼠标 Vector3 screenPos = Cam ...

  7. springMvc json 参数

    以前,一直以为在SpringMVC环境中,@RequestBody接收的是一个Json对象,一直在调试代码都没有成功,后来发现,其实 @RequestBody接收的是一个Json对象的字符串,而不是一 ...

  8. POJ1845 Sumdiv 数学?逆元?

    当初写过一篇分治的 题意:求A^B的所有因子之和,并对其取模 9901再输出 对于数A=p1^c1+p2^c2+...+pn*cn,它的所有约数之和为(1+p1+p1^2+p1^3+...+p1^(c ...

  9. Java中常用的数据源

    几种常用的Java数据源解决方案 Java中的数据源就是javax.sql.DataSource.DataSource的创建可以有不同的实现. JNDI方式创建DataSource 以JNDI方式创建 ...

  10. 10----padding(内边距)

    padding padding:就是内边距的意思,它是边框到内容之间的距离 另外padding的区域是有背景颜色的.并且背景颜色和内容的颜色一样.也就是说background-color这个属性将填充 ...