千万级别的数据库优化

前言

平时在写一些小web系统时,咱们总会对mysql不觉得然。然而真正的系统易用应该讲数据量展望拓展到千万级别来考虑。所以,今天下午实在是无聊的慌,本身随手搭建一个千万级的数据库,而后对数据库进行一些简单的CRUD来看看大数据状况下的CRUD效率。html

结果发现,曾经简单的操做,在数据量大的时候仍是会形成操做效率低下的。所以先写下这篇文章,往后不断更新纪录一下本身工做学习到的Mysql优化技巧。mysql


搭建千万级数据库

首先,须要一个测试环境。一开始想到的是写一个SImple JDBC程序然进行简单的数据INSERT。结果发现单线程状况下,每次INSERT了一百多万条的时候效率就变得很是的低下。可是程序也没报OUT MEMORY之类的异常。初步判断应该是单一线程不断的疯狂建立PrepareStatement对象CG没来得及清理形成内存逐渐被吃紧的缘由。web

后来改进了一下,用多线程的机制。建立十个进程,每一个负责一万条数据的插入。这效率一会儿提高了好几倍。然而好景不长,很快的Java程序报错:OUT MEMORY。内存溢出了,CG没来得及清理。sql

这可把我给急的了。插入的太快内存CPU吃紧,插入的太慢又失去了建立测试环境“快”的初衷。后来想了下,既然是要批量插入数据,那么不是能够简单的写一段数据库存储过程吗?数据库

因而,先创建一张测试表,就叫goods表吧。先写sql语句建立表:swift

 
 
  1.  
    CREATE TABLE goods (
  2.  
    id serial,
  3.  
    NAME VARCHAR (10),
  4.  
    price DOUBLE
  5.  
    ENGINE MYISAM DEFAULT CHARACTER
  6.  
    SET utf8 COLLATE utf8_general_ci AUTO_INCREMENT ROW_FORMAT COMPACT;

接下来根据表结构写一段存储过程让数据库自行重复插入数据:缓存

 
 
  1.  
    begin
  2.  
    declare int default ;
  3.  
    dd:loop
  4.  
    insert into goods values
  5.  
    (null,'商品1',20),
  6.  
    (null,'商品2',18),
  7.  
    (null,'商品3',16),
  8.  
    (null,'商品4',4),
  9.  
    (null,'商品5',13),
  10.  
    (null,'商品6',1),
  11.  
    (null,'商品7',11),
  12.  
    (null,'商品8',12),
  13.  
    (null,'商品9',13),
  14.  
    (null,'商品0',12);
  15.  
    commit;
  16.  
    set i+10 ;
  17.  
    if 10000000 then leave dd;
  18.  
    end if;
  19.  
    end loop dd ;
  20.  
    end

写完后运行一下,ok千万级别的数据库立刻就插入进去了。网络


再谈数据库优化

既然有了数据如今开始进入数据库优化环节。多线程

1、分页查询的优化

首先咱们经常涉及到的CRUD操做莫过于分页操做。 对于普通的分页操做咱们经常是这样子函数

select * from goods limit 100,1000; 

这样固然没有任何的问题,可是当咱们的数据量很是大,假如我要查看的是第八百万条数据呢?对应的sql语句为:

select * from goods limit 8000000,1000; 

Mysql执行时间为 1.5秒左右。那么咱们能够作一些什么优化吗?

上述的sql语句形成的效率低下缘由不外乎:

大的分页偏移量会增长使用的数据,MySQL会将大量最终不会使用的数据加载到内存中。就算咱们假设大部分网站的用户只访问前几页数据,但少许的大的分页偏移量的请求也会对整个系统形成危害

那么我要怎样来优化呢?若是咱们的id为自增的。也就是说每一条记录的id为上一条id + 1那么,分页查找咱们可使用id进行范围查找替代传统的limit。

例如上述的sql语句能够代替为:

select * from goods where id > 8000000 limit 1000; 

上述sql的到一样的执行结果,执行时间却只有0.04秒。提高了40倍左右。

结论:对应于自增id的表,若是数据量很是大的分页查找,能够观察id的分布规律计算出其id的范围经过范围查找来实现分页效果。

2、索引优化

谈到数据库效率,大部分人的第一想法应该就是创建索引。没错,正确的创建索引能够很好的提高效率。

关于索引,这是一个很大的话题我就不打算在这篇文章归纳起来了。推荐一篇美团技术博客关于索引的文章。这篇文章很好的概述了索引的使用场景。主要要注意最左前缀匹配原则,而且将索引创建在区分度高的列。区分度的计算公式为:

count(distinct col)/count(*) 

所以像个人模拟数据中即便创建了索引效率也提高不了多少,由于区分度很是的低。

总结一些索引会失效的状况,咱们在实际的开发中应该尽可能避免:

  1. like查询是以%开头,不会使用索引
  2. WHERE条件中有or,即便其中有条件带索引也不会使用
  3. !=,not in ,not exist不会使用索引
  4. WHERE字句的查询条件里使用了函数或计算
  5. 复合索引若是单独使用,只有复合索引里第一个字段有效

结论:索引很重要,也是一个大话题。推荐看看那篇美团技术博客的文章能够学习到不少。有些看似简单的函数操做若是放在SQL语句中却会致使索引失效,严重的影响效率,所以推荐将一些操做放到客户端中进行计算而不是SQL语句中。索引的使用状况可使用EXPLAIN进行查看。

3、谈谈COUNT(*)

查询一张表有多少条记录经常使用语句为:

SELECT COUNT(*) FROM `goods`; 

有些人认为这里使用了星号可能效率不如直接使用COUNT(COL)来的高,因此他们认为对于goods表(存在逻辑主键)更高效的语句应该是这样的:

SELECT COUNT(id) FROM `goods`; 

可是其实两条执行的时间是同样的。由于COUNT(*)默认走最短的索引。因为id是这里最短的索引因此COUNT(*)等价于COUNT(id)。

结论:若是表中的最短索引很长,并且须要COUNT(*)操做,不放添加一个冗余的索引在一个比较短的列上,这样能够大大加大索引的速度。而且记住:COUNT(*)走的永远是最优的。

4、varchar 不是越大越好

有人认为varchar在的大小是按数据实际大小存储的,因此为了防止长度溢出就一开始就将长度定义的很长。可是事实是:

  • VARCHAR在硬盘占用上确实是按实际大小占用
  • 但若是涉及到临时表,是按后面的数字分配内存的
  • 在VARCHAR列创建索引,ken_len也是按照后面数字分配的

结论:varchar按需取长,防止临时表占满内存溢出至磁盘致使速度降低。

5、联合查询与单表查询的选择

Mysql有不少联合查询的方式,诸如left join、inner join等等。

可是这些联合查询其实效率是很低的,如今考虑两张表一张为job表 数据量大约10万 + ,另一张是job的分析表数据量较少,想经过job表中的job_name查询全部工做的工做分析状况。其中在两张表的列 job_name 均创建了索引。如今若是用联合查询:

 
 
  1.  
    SELECT FROM `job` LEFT JOIN `job_analysis` ON a.job_name b.job_name;
  2.  
    -- 运行时间: 0.93s
  3.  
    -- EXPLAIN 结果:
  4.  
    1 SIMPLE a ALL 169497
  5.  
    1 SIMPLE b ref job_name job_name 212 jobs.a.job_name 1

能够看到使用left join的查询只有一张表使用了索引,而另一张表却要ALL去遍历。这对数据不是很大的时候还好,对数据量上百万 千万简直是噩梦。

诚然这种状况下使用单表屡次效率并不能更高(至少一次ALL + 一次走索引)但数据量大仍是要选择单表屡次可能更优,由于单表屡次查询有利于后面对数据的分库分表,且屡次查询能够支持部分的缓存操做以及分为屡次减小数据库锁的竞争。

摘自《高性能MYSQL》

事实上,用分解关联查询的方式重构查询有以下优点:

  1. 让缓存的效率更高。许多应用程序能够很方便的缓存单表查询对应的结果对象。另外对于MYSQL的查询缓存来讲,若是关联中的某个表发生了变化,那么就没法使用查询缓存了,而拆分后,若是某个表不多改变,那么基于该表的查询就能够重复利用查询缓存结果了。
  2. 将查询分解后,执行单个查询就能够减小锁的竞争。
  3. 在应用层作关联,能够更容易对数据库进行拆分,更容易作到高性能和可扩展。
  4. 查询自己效率也可能会有提高。
  5. 能够减小冗余记录的查询。在应用层作关联查询,意味着对于某条记录应用只需查询一次,而在数据库中作关联查询,则可能须要重复地访问一部分数据(冗余数据引发)。从这点看,这样的重构还可能会减小网络和内存的消耗。
  6. 更进一步,这样作至关于在应用中实现了哈希关联,而不是使用MySQL的嵌套循环关联。某些场景哈希关联的效率要高的多。

结论:恰当的时候选择恰当的方法,遵循如下原则:

  • 数据量小时,联合查询比较简便,10万记录以上不建议
  • 数据量大时,单表屡次查询好处多多。
相关文章
相关标签/搜索