MySQL设计规范与性能优化
作者:网络转载 发布时间:[ 2015/7/16 14:22:17 ] 推荐标签:数据库
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)完全不同;
建立索引的时机
一般来说,在where和join中出现的列需要建立索引,mysql只对<,<=,=,>,>=,between,in以及某些时候(不以通配符%和_开头的查询)的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)来指定查询时使用的索引);
对于值的列,索引效果好,对于具有多个重复值的列,如年龄或性别,建立索引不是好办法;
使用索引注意事项
索引不会包含有NULL值的列,在数据库设计时尽量不要让字段的默认值为NULL,否则无法建立相关字段的索引;
使用短索引,对varchar类型字段建索引时好指定长度,只要保证前n个字符多数值是的即可,提高查询速度,节省磁盘空间,降低I/O操作;
MySQL查询只是用一个索引,因此如果一条查询语句中有多个字段需要建索引,好按照左前缀匹配原则建立组合索引;
like语句一般不鼓励使用,在数据量大的情况下,非常容易造成性能问题,如果非使用,通配符%一定要放到后面,如like "abc%";
不要在列上进行运算,如select * from users where YEAR(datetime)<2015;,会导致索引失效,进行全表扫描;
不要使用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 processlistG;
结论:
对于以查询操作为主,并且更新操作耗时较低的应用,将不会存在太多的锁等待,可以使用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个线程;
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。

sales@spasvo.com