好比,如今有这么一个问题,常见的一个面试题:
有一张users表,数据量在五千万以上,存在一条查询语句:mysql
SELECT * FROM users WHERE name LIKE '%明%' AND sex='男' AND age=32 AND created_at BETWEEN 1388505600 AND 1420041600
;面试
就是说从users表中找出建立时间在14年到15年之间的年龄在32岁,名字中带有‘明’字的男性用户
spring
追溯到数据表的设计,sql查询语句的调优,而且须要考虑到这种大表的数据插入时候的注意事项,数据分页及将来数据表管理时可能碰见的问题及解决方案sql
本文将会从存储层,设计层,sql语句层和架构层进行优化:数据库
这里只展开InnoDB和MyIsam(选择哪一个存储引擎须要判断当前的业务场景)segmentfault
支持表级锁,锁的颗粒度比较大,因此被锁定的资源的争用状况会比其余的锁定级别会多,会下降并发处理的能力。 解决方法是能够设置 Concurrent Insert(并发插入)
windows
数据表的存储位置:在mysql安装目录/data/数据库名称
下,分为数据表结构,数据表索引,数据表数据三个文件分别进行存储。这种索引和数据相分离,是经过物理地址进行关联的,这种索引结构也叫作‘非聚合型索引’,因此能够选择直接拷贝对应表的文件进行数据表的备份。在进行数据查询的时候,是先查找索引文件中数据对应的记录地址而后在根据该地址找到对应的数据文件中对应的数据
。所以这种索引的方式也叫作“非汇集的”。缓存
支持数据表的压缩,牺牲数据表的更新操做,换取快速的查找速度。在CMD中执行myisamchk.exe -rq 表名
。 使用命令myisamchk.exe --unpack 表名
进行解压缩。而后使用flush table 表名
进行刷新数据表服务器
支持全文索引架构
不支持外键
表的具体行数:保存有表的具体行数。在针对一个140万的数据表的时候,count(*)的速度很快:0.0003秒
总结:myisam是mysql最为古老的存储引擎之一,对于以读为主的非事务性系统来讲,myisam无疑是最优先考虑的对象。可是对于存在必定的并发量的系统,仍是不建议使用Myisam
的,由于目前随着Innodb
引擎的成熟,Myisam
对于高并发时表锁的消耗太大
支持行级锁,带来的性能方面的锁比如较大,可是总体的并发性能却远远优于MyIsam表。可是若是使用不当可能会发生死锁,关于避免死锁的问题,并无深刻研究过,大致的发生死锁的缘由就是:由多个并发事务;每一个事物都持有了锁;每一个事务为了完成相关的逻辑都须要继续持有锁;多个事务之间产生加锁的循环等待->最终造成死锁
支持外键,支持事务。
在进行select count(*)的时候,若是数据库中只存在一个primary key 的时候,执行count的时候速度会很慢,一张140万的数据表,执行的速度是6s多。可是myisam的count时间却只有0.00几秒。可是若是在InnoDB表中在添加一个索引的时候,速度就会比较快。详情看:https://segmentfault.com/a/11...
数据表内容的存储位置:.frm文件用来存储表结构定义相关的元数据,表的索引和数据存放在一块儿。用户能够自定义,默认的初始化存储位置(windows)是在c:/programData/mysql 下。种数据和索引存储在一个文件中的索引结构叫作‘聚合型’索引
字段应该优先选择 数值型进行存储,整形数据比起字符型处理的开销会更小,好比性别,是否这些可使用enum进行存储。ip地址,时间等字段也存储成整形。可使用数值型和枚举类型的字段进行不使用字符型进行存储
越小的数据类型越好。越小的数据类型在硬盘,cpu缓存和内存上的使用空间都更小,处理起来会更快
尽可能避免使用null。在建立数据表的时候应该指定列为 NOT NULL ,而后设定默认值
添加索引能够增快数据查询的速度,可是对应的在数据的写入的时候须要去维护索引的数据,因此在数据的插入和更新等操做时速度回变慢。因此添加索引须要注意在经常使用的查询字段上面进行添加
这对于这里的需求来说,建立一个name,age,created_at
字段上的联合索引,在后面的查询条件的排列的地方先查询name
字段,而后是age
字段,而后created_at
字段,最后在是sex
字段
具体的关于索引的内容能够看 https://segmentfault.com/a/11...
分区
分区就是将整个的业务模块分散到不一样的服务器上进行存储,好比说用户模块,文章模块,相册模块等分别存在不一样的服务器上完成分区
水平分表
物理分表:能够根据求余的方式或者hash或根据当前月份等方式进行
手动建立多个数据表,主要是根据当前记录的索引值进行判断该数据所在的位置
数据查询
$id = $_GET['id']; $mod = $id%5; $sql = "SELECT * from goods_$mod WHERE id=$id";
新增数据
//在新增数据的时候须要一张临时表去判断当前表中的最大id值为多少,选择对应的数据存储的数据表
$sql = "INSERT INTO `临时表` values null"; $new_id = "SELECT mysql_insert_id()"; $mod = $new_id%5; $sql = "INSERT INTO goods_$mod VALUES ($new_id, 内容1, 内容2)";
逻辑分表(严格上来说,是在数据库的逻辑层进行分区)
为了保证分区时的查询效率,必须保证添加的分区字段为主键或unique key
在新建了分区以后,在查看数据表的存储文件的结构能够发现,数据表的索引和数据内容已经被单独拿出去存储了
在访问分区表时,在where条件中必定要带上分区列,即便是看似多余的,由于查询优化器会根据该列锁定数据所在的分区,否则会对全部数据扫描
key分区(求余)
CREATE TABLE test_key( id int not null auto_increment, title varchar(32) not null default '', price decimal(10,2) not null default 0, created_at datetime not null, PRIMARY KEY (id,created_at) ) engine=myisam charset=utf8 partition by key(id) partitions 5;
1
hash分区(求余)
CREATE TABLE test_hash( id int not null auto_increment, title varchar(32) not null default '', price decimal(10,2) not null default 0, created_at datetime not null , PRIMARY KEY (id,created_at) ) engine=myisam charset=utf8 partition by hash(month(created_at)) partitions 5;
list分区(范围)
CREATE TABLE test_list( id int not null auto_increment, title varchar(32) not null default '', price decimal(10,2) not null default 0, created_at datetime NOT NULL, PRIMARY KEY (id,created_at) ) engine=myisam charset=utf8 partition by list(month(created_at))( partition spring values in (3,4,5), partition summer values in (6,7,8), partition autumn values in (9,10,11), partition winter values in (12,1,2) );
range分区(范围)
CREATE TABLE test_list( id int not null auto_increment, title varchar(32) not null default '', price decimal(10,2) not null default 0, created_at datetime NOT NULL, PRIMARY KEY (id,created_at) ) engine=myisam charset=utf8 partition by range(year(created_at))( partition oldest values less than 1980, partition old values less than 1990, partition middle values less than 2010, partition new values less than 2010 );
垂直分表
对于一张很大的数据表,好比user
表,username,password,age
等字段是常常被使用到的字段,能够放在一张表中,表中其余不太经常使用的字段(不会拿来看成查询条件的字段),如person_info,profile
等能够拿出到一张单独的表中进行存储,这样能够保证主表在数据量很大的时候性能降低不会太严重。
1.配置mysql集群,完成数据的读写分离
基本原理:1.master记录本身改变了的记录的二进制文件(binlog),在每一个事务执行完毕以后,将这些改变记录在二进制文件中;2.在slave上存在两个进程:读取master上的二进制文件到本身的中继文件中,在中继文件中读取更新的事件内容并同步到本身的数据库中
可使用mysql官方提供的代理层产品完成MysqlProxy
相关的功能:https://segmentfault.com/a/11...
字段:在进行数据的查询的时候,在查询字段的选择上,使用 对应的字段
代替 select *
,这样作对查询速度不会有明显的提高,可是能够节省内存
条件:在sql语句的条件的书写的时候,条件的排列顺序应该是以字段上的数据差别性较大的列排列在最左端,也就是最可以区分出更少数据的列优先排列在最左端。好比说在常规的业务逻辑下,age
字段应该在sex
字段的左侧
分页:在后台的数据进行分页的时候,每页显示150条数据,在查看10000页的数据的时候确定会很慢,使用 where id > 1500000 limit 150
的写法代替 limit 10000,150
.这样能够极大的提升查询的速度
在只查询一条数据的时候,使用limit 1
,这样mysql在进行搜索的时候,找到了一条数据就不会在乡下进行搜索
子查询:在执行一条资查询:select ... from t1 where t1.uid IN (select uid from t2 )
,这条子查询至关于select ... from t1 where exists(select 1 from t2 where t2.uid=t1.id)
.这样一来至关于将两个结果集中的数据作乘法,相比于链接查询,速度会很慢
连表查询:将复杂的JOIN查询语句改写成针对于单表的sql查询语句。在JOIN多个表的时候,可能致使更多的锁定和堵塞
注意数据的饮食转换。好比说查询的字段的类型为varchar
,那么在写where条件的时候,where name='11'
会比 where name=11
更快,由于传入的字段类型是int
,会致使程序进行全表扫描
关于MyIsam切换到InnoDB:http://blog.itpub.net/1267930...