Java面试准备-数据存储

问题链接转载  Java面试通关要点汇总集【终极版】

一、MySQL 索引使用的注意事项

  • 不要在列上使用函数和进行计算,这将导致索引失效而进行全表扫描
select * from news where year(publish_time) < 2017;  // 不要在列上使用函数
select * from news where publish_time < '2017-01-01';  // 可以

select * from news where id/100 = 1;   // 不要在列上进行计算
select * from news where id = 1*100;   // 可以
  • 尽量避免使用 != 或 not in or <>等否定操作符,会影响索引失效
  • 尽量避免使用 or 来连接条件,也会导致索引失效
  • 多个单列索引并不是最佳选择,可以使用复合索引,如new_year_month_idx(new_year,new_month)
  • 复合索引的最左前缀原则,即在查询条件中使用复合索引的第一个字段
  • 覆盖索引的好处:如果一个索引包含所有需要的查询的字段值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能
  • 查询中的某个列有范围查询则右边所有列都无法使用索引优化查找,尽量减少范围查询
  • 索引不会包含有NULL值的列,尽量不要让字段的默认值为NULL
  • 当查询条件左右两侧类型不匹配的时候会发生隐式转换,这样可能会导致索引失效而进行全表扫描。要时刻注意通过同类型进行比较
  • like的方式进行查询,在like "value%" 可以使用索引,但对于like "%value%" 这样的方式会执行全表查询。

二、说说反模式设计

数据库范式是为解决关系数据库中数据冗余,更新异常,插入异常,删除异常问题而引入的。即数据库范式可以避免数据冗余,减少数据库的空间并减轻维护数据完整性的麻烦

  • 第一范式(1NF):强调属性的原子性约束,要求属性具有原子性,不可再分解。如活动表(活动编码,活动名称,活动地址),假设这个场景中,活动地址可以细分为国家,省份,城市,市区,位置,那么就没达到第一范式
  • 第二范式(2NF):强调记录的唯一性约束,表必须有一个主键,并且没有包含在主键中的列必须完全依赖于主键,而不是只依赖于主键的一部分。如版本表(版本编码,版本名称,产品编码,产品名称),其中主键是(版本编码,产品编码),这个数据库设计并不符合第二范式,因为产品名称只依赖于产品编码,存在部分依赖。要满足第二范式,可以改成两个表:版本表(版本编码,产品编码)和产品表(产品编码,产品名称)
  • 第三范式(3NF):强调属性冗余性的约束,即非主键列必须直接依赖于主键。如订单表(订单编码,顾客编码,顾客名称),其中主键是(订单编码),此时顾客编码,顾客名称都完全依赖于主键,符合第二范式。但顾客名称依赖于顾客编码,从而间接依赖于主键,所以不符合第三范式。可以改成订单表(订单编码,顾客编码)和顾客表(顾客编码,顾客名称)

反模式

通过数据库范式设计,将会导致数据库业务涉及的表变多,并有可能多表联查导致性能变差,也不利于分库分表。出于性能考虑,可能在数据库结构中需要使用反模式的设计,即空间换时间,采取数据冗余的方式避免表之间的关联查询。但需要谨慎使用反模式设计数据库。一般尽可能使用范式化的数据库设计。

三、说说分库与分表设计

  • 分表

通过分表,可以减少数据库的单表负担,将压力分散到不同的表上,起到提高查询性能,缩短查询时间的作用,可以很大缓解表锁的问题。分表策略可以归为垂直拆分和水平拆分。

垂直拆分,把表的字段进行拆分,即一张字段比较多的表拆分为多张表。如何设计

  • 将不常用的字段单独拆分到另外一张扩展表
  • 将大文本的字段单独拆分到另一张扩展表,如BLOB,TEXT,TINYBLOB,MEDIUMBLOB,LONGBLOB,TINYTEXT,MEDIUMTEXT和LONGTEXT字符串类型
  • 将不经常修改的字段放在同一张表中,将经常改变的字段放在另一张表中。
  • 对于需要经常关联查询的字段,建议放在同一张表中。

水平拆分,把表的行进行拆分,即把数据存放到多张表中。有很多策略,如取模分表,时间维度分表,以及自定义Hash分表。实际情况中,水平拆分往往会和垂直拆分结合使用。常见的水平分表策略可以总结为随机分表和连续分表。

  • 连续分表可以快速定位到表进行高效查询。大多数情况下可以有效避免跨表查询。要想扩展,只需要添加额外的分表,不需要迁移旧数据。但连续分表有可能存在数据热点问题,有些表可能会被频繁地查询从而造成较大压力,热数据的表就成为整个库的瓶颈
  • 随机分表是遵循规则策略进行写入与读取,而不是真正意义上的随机。采用取模分表或者自定义Hash分表的方式进行水平拆分。随机分表的数据相对比较均匀,不容易出现热点和并发访问瓶颈。但分表扩展需要迁移旧的数据。此外,随机分表比较容易面临跨表查询的复杂问题。

       对于日志场景,可以考虑根据时间维度分表。对于海量用户场景,可以考虑取模分表。对于用户场景,可以考虑按用户维度分表

  • 分库

分库可以减轻Mysql服务器的压力。分库策略也可以归纳为垂直拆分和水平拆分。

  • 垂直拆分,按照业务和功能划分,把数据分别放在不同的数据库中
  • 水平拆分,把一张表的数据划分到不同的数据库,两个数据库的表结构一样。水平分库跟水平分表类似,如取模分表,时间维度分表,以及自定义Hash分表。

四、分库与分表带来的分布式困境与应对之策

  • 数据迁移与扩容问题

连续分表可能存在数据热点问题,个别表被频繁查询从而造成较大压力,个别表又有可能很少被查询到。热数据的表成为整个库的瓶颈。但连续分表不需要迁移旧数据,只需要添加分表就可扩容。但随机分表的数据分布比较均匀,不会出现热点和并发访问的瓶颈,但扩容需要迁移旧数据。

所以针对水平分布的设计至关重要,需要评估中短期内业务的增长速度,对当前的数据量进行容量规划,综合成本因素,推算出大概需要多少分片。对于数据迁移问题,一般是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。

  • 表关联问题

随着分库与分表的演变,联合查询就遇到跨库关联和跨表关系问题。在设计之初就应该尽量避免联合查询,可以通过程序中进行拼装,或者通过反范式化设计进行规避。

  • 分页与排序问题

一般情况下,列表分页时需要按照指定字段进行排序,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。为了保证最终结果的准确性,需要在不同的分表中将数据将数据排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。

  • 分布式事务问题

如何保证数据的一致性就成为一个必须面对的问题。目前,分布式事务并没有很好的解决方案,难以满足数据强一致性,一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短时间的自我恢复和修正,数据最终达到一致。

  • 分布式全局唯一ID

在分库分表情况下,数据分布到不同的分表上,不能再借助数据库自增长特性。需要使用全局唯一ID,例如UUID,GUID等。

总结:

分库分表主要应对互联网常见的两个场景:海量数据和高并发。但同时也提高了系统的复杂度和维护成本。所以要结合实际需求,不宜过度设计。在项目一开始可以不采用分库与分表设计,而是随着业务的增长,在无法继续优化情况下,再考虑分库与分表提高系统的性能。

四、说说 SQL 优化之道

改善性能最好的方式,就是通过数据库中合理地使用索引。MySQL索引可以分为单列索引,复合索引,唯一索引,主键索引等

  • 单列索引
create index index_name on tbl_name(index_col_name); // 创建一个单列索引
alter table tbl_name add index index_name on (index_col_name); // 也可以通过修改表结构的方式添加索引
  • 复合索引:复合索引是在多个字段上创建的索引。复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。
create index index_name on tbl_name(index_col_name,...); // 创建一个复合索引
alter table tbl_name add index index_name on (index_col_name,...); // 也可以通过修改表结构的方式添加索引
  • 唯一索引:唯一索引限制列的值必须唯一,但允许有空值。对于多个字段,唯一索引规定列值的组合必须唯一
create unique index index_name on tbl_name(index_col_name); // 创建一个单列索引
alter table tbl_name add unique index index_name on (index_col_name); // 也可以通过修改表结构的方式添加索引
  • 主键索引:主键索引是一种特殊的唯一索引,不允许有空值。此外 CREATE INDEX 不能创建主键索引,需要使用ALTER TABLE 代替
alter table tbl_name add primary key(index_col_name);
  • 强制索引:因为使用MySQL的优化器机制,原本应该使用索引的优化器,反而选择执行全部扫描或者执行的不是预期的索引。可以通过强制索引的方式引导优化器采用正确的执行计划。切记不要滥用强制索引。因为MySQL的优化器会同时评估I/O和CPU成本。
select * from tbl_name force index (index_col_name )...
  • 全文索引:全文搜索在MySQL中是一个FULLTEXT类型索引,它是在MySQL5.6版本之后支持InnoDB,而之前版本只支持MyISAM表
CREATE TABLE IF NOT EXISTS `app_full_text` (
    `app_id` bigint(20) NOT NULL,
    `app_name_full_text` text NOT NULL,
    `introduce_full_text` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

需要对应用创建全文索引,可以这么设计

alter table `app_full_text` add fulltext key `app_name_intro` (`app_name_full_text`);

MySQL默认不支持中文全文搜索。

五、MySQL 遇到的死锁问题

线上遇到MySQL死锁的相关问题,需要查看MySQL出现的Deadlock日志,可以通过如下命令执行

show engine innodb status;

来查看InnoDB类型数据库的状态,查找latest detected deadlock部分,可以看得最近造成死锁的两条msql

MySQL不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

在数据库中有两种基本的锁类型:排他锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排他锁时,其他事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。

  • 死锁的第一种情况

用户A访问表A(锁住了表A),然后又访问表B;用户B访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,用户B要等用户A释放表A才能继续,这样死锁就产生了。

解决方法:

这种死锁比较常见,是由于程序BUG产生。对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁住两个资源,如操作表A和表B时,总是按先A后B的顺序处理。必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

  • 死锁的第二种情况

用户A查询一条记录,然后修改该记录;这时用户B修改该记录,这时用户A的事务里锁的性质由查询的共享锁企图上升到排他锁,而用户B的排他锁由于A有共享锁存在所以必须等A释放共享锁,而A由于B的排他锁而无法上升排他锁,也就不可能释放共享锁,于是出现了死锁。

解决方法:

  1. 对于按钮等控件,点击后使其立即失效,不可重复点击,避免同时对同一条记录操作
  2. 使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方法中,一般是通过为数据库表增加一个"version"字段来实现。读取数据时将此版本号一同读出,之后更新时对此版本号加一。将提交数据的版本与数据库表对应记录的当前版本信息进行比对,如果提交的版本号大于当前版本号则更新数据。
  3. 使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select ... for update语句,以保证操作最大程度的排他性,但会加大数据库性能的大量开销。
  • 死锁的第三种情况

如果在事务中执行一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引键的过少或不合适时,使得经常发生全表扫描,最终应用系统会越来越慢,发生阻塞或死锁。

解决方法:

SQL语句中不要使用太复杂的关联多表查询;使用执行计划对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

六、存储引擎的 InnoDB 与 MyISAM

在MySQL 5.1及之前的版本中,MyISAM是默认的存储引擎,而在MySQL 5.5以后,默认使用InnoDB存储引擎。

  • MyISAM不支持行级锁,即MyISAM会对整张表加锁,不会针对行。同时MyISAM不支持事务和外键。MyISAM可被压缩,存储空间较小,而且MyISAM在筛选大量数据时非常快。
  • InnoDB是事务型引擎,当事务异常提交时,会被回滚。同时,InnoDB支持行级锁。此外,InnoDB需要更多存储空间,会在内存中建立其专用的缓冲池用于高速缓冲数据和索引。InnoDB支持自动奔溃恢复特性。

一般情况下,应该优先选择InnoDB存储引擎,并且尽量不要将InnoDB和MyISAM混合使用。

七、数据库索引的原理

当一个新表被创建之时,系统将在磁盘中分配一段以8K为单位的连续空间,当字段的值从内存写入磁盘时,就在这一既定空间随机保存,当一个8K空间用完的时候,SQL Server指针会自动分配一个8K的空间。这里,每个8K空间被称为一个数据页,又名页面或数据页面,并分配从0-7的页号,每个文件的第0页记录引导信息,叫文件头;每8个数据页(64K)的组合形成扩展区(Extent),称为扩展,全部数据页的组合形成堆(Heap)。

SQL Server规定行不能跨越数据页,所以,每行记录的最大数据量只能为8K。这就是char和varchar这两种字符串类型容量要限制在8K以内的原因。存储超过8K的数据应使用text类型,实际上,text类型的字符值不能直接录入和保存,它只是存储一个指针,指向由若干8K的文本数据页所组成的扩展区,真正的数据正是放在这些数据页中。

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库表中数据。索引的实现通常使用B树及其变种B+树。为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间

上图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针。

创建索引可以大大提高系统的性能

  1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
  2. 可以大大加快数据的检索速度,这也是创建索引的最主要原因
  3. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义
  4. 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
  5. 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能

创建索引的不足

  1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
  2. 索引需要占物理空间,除了数据表占数据空间外,每个索引还要占一定的物理空间。如果建立聚簇索引,那么需要的空间就会更大
  3. 当对表中的数据进行增加,删除和修改时,索引也要动态的维护,降低了数据的维护速度

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分之一。因此为了提高效率,要尽量减少磁盘I/O。所以磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。

  • MyISAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,下图是MyISAM索引的原理图

这里设表一共有三列,假设我们以Col1为主键,则上图是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一,而辅助索引的key可以重复。如果在Col2上建立一个辅助索引,则此索引结构如下图所示

同样也是一棵B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做“非聚集”

  • InnoDB索引实现

MyISAM索引文件和数据文件是分离。InnoDB的数据文件本身就是索引文件。表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

上图是InnoDB主索引(同时也是数据文件)的示意图,可以看得叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身是按主键聚集,所以InnoDB要求表必须有主键,如果没有显示指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

InnoDB的辅助索引data域存储相应记录主键的值而不是地址。InnoDB的所有辅助索引都引用主键作为data域。如下图为定义在Col3上的一个辅助索引:

聚集索引使得按主键搜索十分高效,但辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

八、为什么要用 B-tree

根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:

每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加上计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。综上所述,用B-Tree作为索引结构效率是非常高。而红黑树这种结构,h明显要深得多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看出,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

dmax = floor(pagesize / (keysize+datasize+pointsize))      (pagesize - dmax >= pointsize)

dmax = floor(pagesize / (keysize+datasize+pointsize)) - 1      (pagesize - dmax >= pointsize)

floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

九、聚集索引与非聚集索引的区别

一、聚集索引

  • 定义

聚集索引中键值的逻辑顺序决定了表中相应行的物理存储位置,因此一个表中只能有一个聚集索引。索引的逻辑顺序与相应行的物理位置一致。

  • 聚集索引的适用情形 
  1. 经常对某些列进行范围搜索,例如查询一段日期范围。 
  2. 经常对查出的数据按照某一列进行排序,也可以再该列上建立聚集索引,避免在每次查询数据是都进行排序,从而节约时间成本。
  3. 当索引值唯一时,使用聚集索引查找特定的行也很有效率,例如建立主键。
  • 如何建立聚集索引 

MySQL的InnoDB引擎 
InnoDB按照主键进行聚集,当没有设置主键时,InnoDB会以唯一的非空索引来代替,如果既没有主键,也没有唯一的非空索引,InnoDB则会生成一个隐藏的主键然后在上面进行聚集。

SQL SERVER 在建立索引时,可以将索引设置为聚集索引,但是varbinary类型的字段是不可以建立索引的。

二、非聚集索引

  • 定义:非聚集索引中键值的逻辑顺序与行的物理存储位置不一致

十、limit 20000 加载很慢怎么解决

limit 分页优化方法

  • 子查询优化法,要求子查询必须是连续的
select * from Member where MemberID >= (select MemberID from Member limit 100000,1) limit 100;
  • 倒排表优化法

类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据,缺点是只适合数据数固定的情况,数据不能删除,维护表困难

  • 反向查找优化法

当偏移超过一半记录数的时候,先用排序,这样偏移就反转了。缺点是order by优化比较麻烦,要增加索引,索引会影响数据的修改效率,并且要知道总记录数,偏移大于数据的一半

正向查找SQL:
   Sql代码
   1.SELECT * FROM `abc` WHERE `BatchID` = 123 LIMIT 1199960, 40
   SELECT * FROM `abc` WHERE `BatchID` = 123 LIMIT 1199960, 40
   时间:2.6493 秒
   反向查找sql:
   Sql代码
   1.SELECT * FROM `abc` WHERE `BatchID` = 123 ORDER BY InputDate DESC LIMIT 428775, 40
   SELECT * FROM `abc` WHERE `BatchID` = 123 ORDER BY InputDate DESC LIMIT 428775, 40
    时间:1.0035 秒
  • limit限制优化法:把limit偏移量限制低于某个数。超过这个数等于没数据

十一、选择合适的分布式主键方案

  • 使用自增主键ID
  • 通过应用程序生成一个GUID,然后和数据一起插入切分后的集群

优点:维护简单,实现也容易

缺点:应用的计算成本较大,且GUID的长度比较长,占用数据库存储空间较大,涉及到应用的开发

  • 通过独立的应用程序事先在数据库中生成一系列唯一的ID,各应用程序通过接口或自己去读取再和数据一起插入到切分后的集群中

优点:全局唯一主键简单,维护相对容易

缺点:实现复杂,需要应用开发,且ID表要频繁查和频繁更新,插入数据时影响性能

  • 通过中心数据库服务器利用数据库自身的自增类型(如MySQL的auto_increment字段),或者自增对象(如Oracle的Sequence)等先生成一个唯一ID再和数据一起插入切分后的集群(不推荐)

优点:没有特别明显的优点

缺点:实现较为复杂,且整体可用性维系在这个中心数据库服务器上,一旦这里crash,所有的集群都无法进行插入操作

  • 通过集群编号加集群内的自增(auto_increment类型)两个字段共同组成唯一主键

优点:实现简单,维护也比较简单,对应用透明

缺点:引用关联操作相对比较复杂,需要两个字段,主键占用空间较大,在使用InnoDB时这点副作用特别明显

  • 通过设置每个集群中自增ID起始点(auto_increment_offset),将各个集群的ID进行绝对的分段来实现全局唯一。当遇到某个集群数据增长过快后,通过命令调整下一个ID起始位置跳过可能存在的冲突

优点:实现简单,且比较容易根据ID大小直接判断数据处于哪个集群,对应用透明

缺点:维护相对复杂,需要高度关注各个集群ID增长状况

  • 通过设置每个集群中自增ID起始点(auto_increment_offset)以及ID自增步长(auto_increment_increment),让目前每个集群的起始点错开1,步长选择大于将来基本不可能达到的切分集群数,达到将ID相对分段的效果来满足全局唯一的效果

优点:实现简单,后期维护简单,对应用透明

缺点:第一次设置相对较为复杂

十二、选择合适的数据存储方案

数据存储的选型问题,是选择使用关系型数据库MySQL,还是选择内存数据库Redis,还是选择文档数据库MongoDB,还是选择列族数据库HBase,还是选择全文搜索引擎ElasticSearch

  • 关系型数据库MySQL

MySQL是一个最流行的关系型数据库,在互联网产品中应用比较广泛。一般情况下,MySQL数据库是选择的第一方案,基本上有80% - 90%的场景都是基于MySQL数据库的。因为,需要关系型数据库进行管理,此外,业务存在许多事务性的操作,需要保证事务的强一致性。同时,可能还存在一些复杂的SQL查询,值得注意的是,前期尽量减少表的联合查询,便于后期数据量增大的情况下,做数据库的分库分表。

  • 内存数据库Redis

随着数据量的增长,MySQL已经满足不了大型互联网应用的需求。因此,Redis基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充。例如,为了提高服务端接口的访问速度,尽可能将读频率高的热点数据存放在Redis中。这个是非常典型的以空间换时间的策略,使用更多的内存换取CPU资源,通过增加系统的内存消耗,来加快程序的运行速度。

在某些场景下,可以充分的利用Redis特性,大大提高效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户判定信息,交集,并集和差集,热门列表与排行榜,最新动态等。

使用Redis做缓存时,需要考虑数据不一致与脏读,缓存更新机制,缓存可用性,缓存服务降级,缓存穿越,缓存预热等缓存使用问题。

  • 文档数据库MongoDB

MongoDB是对传统关系型数据库的补充,它非常适合高伸缩性的场景,它是可扩展性的表结构。基于这点,可以将预期范围内,表结构可能会不同扩展的MySQL表结构,通过MongoDB来存储,这就可以保证表结构的扩展性。

此外,日志系统数据量特别大,如果用MongoDB数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和MapReduce能力,是个很好的选择。

MongoDB还适合存储大尺寸的数据,GridFS存储方案是基于MongoDB的分布式文件存储系统

  • 列族数据库HBase

HBase适合海量数据的存储与高性能实时查询,它是运行于HDFS文件系统之上,并且作为MapReduce分布式处理的目标数据库,以支撑离线分析型应用。在数据仓库,数据集市,商业智能等领域发挥了越来越多的作用,在数以千计的企业中支撑着大量的大数据分析场景的应用

  • 全文搜索引擎ElasticSearch

在一般情况下,关系型数据库的模糊查询,都是通过like的方式进行查询,其中like "value%"  可以使用索引,但对于 like "%value%" 执行全表查询。这在数据量小的表,不存在性能问题,但对于海量数据,全表扫描是非常可怕的事情。ElasticSearch作为一个建立在全文搜索引擎Apache Lucene基础上的实时分布式搜索和分析引擎,适用于处理实时搜索应用场景。此外,使用ElasticSearch全文搜索引擎,还可以支持多词条查询,匹配度与权重,自动联想,拼写纠错等高级功能。因此,可以使用ElasticSearch作为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到ElasticSearch上,达到处理复杂的业务与提高查询速度的目的。

ElasticSearch不仅仅适用于搜索场景,还非常适合日志处理与分析的场景。著名的ELK日志处理方案,由ElasticSearch,Logstash和Kibana三个组件组成,包括了日志收集,聚合,多维度查询,可视化显示等

十三、ObjectId 规则

MongoDB采用了一个称之为ObjectId的类型来做主键。ObjectId是一个12字节的BSON类型字符串。按照字节顺序依次代表:

  • 4字节:UNIX时间戳
  • 3字节:表示运行MongoDB的机器
  • 2字节:表示生成此_id的进程
  • 3字节:由一个随机数开始的计数器生成的值

ObjectId获取时间

从ObjectId的构造上来看,内部就嵌入了时间类型。我们肯定可以从中获取时间信息:即插入此文档时的时间。MongoDB对ObjectId对象提供了getTimestamp()方法来获取ObjectId的时间

> a = new ObjectId()
ObjectId("53102b43bf1044ed8b0ba36b")
> a.getTimestamp()
ISODate("2014-02-28T06:22:59Z")

根据时间构造ObjectId

通过字符串来构造

//使用Date的字符串构造方法生成日期,然后使用Date对象的getTime获取豪秒数,再除以1000得到标准时间戳

> a = new Date("2012-12-12 00:00:00").getTime()/1000

1355241600

//获取时间戳的标准十六进制表示
> a = a.toString(16)

50c75880


//在后面填补16个0
> a = a + new Array(17).join("0") 50c7588000000000000000000  // 使用24个字符串构造ObjectId

> b = new ObjectId(a)  ObjectId("50c7588000000000000000000")  // 获取时间以验证

> b.getTimestamp()  ISODate("2012-12-12T16:00:00Z")

上述过程中 new Array(17).join("0") 目的是生成16个0拼接的字符串。

根据ObjectId按照插入时间排序

MongoDB默认在ObjectId建立索引,是按照插入时间排序的。我们可以使用此索引进行查询和排序

// 按序插入三个文档

> db.col.insert({"num":1})
> db.col.insert({"num":2})
> db.col.insert({"num":3})

>db.col.find().pretty()
{"_id":ObjectId("53102fb4bf1044ed8b0ba36c"),"num":1}
{"_id":ObjectId("53102fb4bf1044ed8b0ba36c"),"num":2}
{"_id":ObjectId("53102fb4bf1044ed8b0ba36c"),"num":3}

十四、聊聊 MongoDB 使用场景

MongoDB是对传统关系型数据库的补充,但是MongoDB不支持事务,因此对事务性有要求的程序不建议使用MongoDB。此外,MongoDB也不支持表联合查询。

  • 高伸缩性的场景

MongoDB非常适合高伸缩性的场景,它是可扩展性的表结构。基于这点,可以将预期范围内,表结构可能会不断扩展的MySQL表结构,通过MongoDB来存储,这就可以保证表结构的扩展性

  • 日志系统的场景

日志系统数据量特别大,如果用MongoDB数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和MapReduce的能力

  • 分布式文件存储

MongoDB还适合存储大尺寸的数据,如GridFS存储方案,就是基于MongoDB的分布式文件存储系统

十五、倒排索引

倒排索引源于实际应用中需要根据属性的值来查找记录,Lucene是基于倒排索引实现的。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件被称为倒排索引文件,简称倒排文件(inverted file)。倒排索引一般表示为一个关键词,然后是它的频度,位置。

倒排索引由两个部分组成:单词词典和倒排文件

  • 倒排文件:所有单词的倒排列表顺序的存储在磁盘的某个文件里,即为倒排文件。倒排文件是存储倒排索引的物理文件
  • 单词词典:由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。单词词典是用来维护文档集合中所有单词的相关信息,同时用来记载某个单词对应的倒排列表在倒排文件中的位置信息。在支持搜索时,根据用户的查询词,去单词词典里查询,就能获得相应的倒排列表。

假设我们有两个文档,每个文档的content域包含如下内容:

  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先将每个文档的content域拆分成单独的词,创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档,如下

 
Term Doc_1 Doc_2
Quick  

X

The X  
brown X X
dog X  
dogs   X
fox X  
foxes   X
in   X
jumped X  
lazy X X
leap   X
over X X
quick X  
summer   X
the X X

如果要搜索quick brown,两个文档都匹配,但第一个文档Doc_1比第二个文档Doc_2匹配度更高。

如果我们将词条规范为标准模式,便可找到与用户搜索的词条不完全一致,但具有足够相关性的文档,如

  • Quick可以小写化为quick
  • foxes可以词干提取 -- 变为词根格式 -- 为fox,同理 dogs可以提取为dog
  • jumped和leap是同义词,可以索引为相同的单词jump
Term Doc_1 Doc_2
brown X X
dog X X
fox X X
in   X
jump X X
lazy X X
over X X
quick X X
summer   X
the X X

如果对搜索的字符串使用与content域相同的标准化规则,会变成查询quick+fox,这样两个文件都匹配。

十六、聊聊 ElasticSearch 使用场景

  • 场景一:使用ElasticSearch作为主要的后端

传统项目中,搜索引擎是部署在成熟的数据存储的顶部,以提供快速且相关的搜索能力。这是因为早期的搜索引擎不能提供耐用的存储或其他经常需要的功能,如统计。

ElasticSearch是提供持久存储,统计等多项功能的现代搜索引擎。如果开发一个新项目,可以考虑使用ElasticSearch作为唯一的数据存储,以帮助保持设计尽可能简单。此场景不支持包含频繁更新,事务操作。

如新建一个博客系统使用es作为存储:

  1. 可以向ES提交新的博文
  2. 使用ES检索,搜索和统计数据

ES作为存储的优势:如果一台服务器出现故障时可以通过复制数据到不同的服务器以达到容错的目的。

  • 场景二:在现有系统中增加ElasticSearch

由于ES不能提供存储的所有功能,一些场景下需要在现有系统数据存储的基础上新增ES支持。

  • 场景三:使用ElasticSearch和现有的工具

在一些使用情况下,您不必写一行代码就能通过ElasticSearch完成一项工作。很多工具都可以与ElasticSearch一起工作,不必从头编写。如部署一个大规模的日志框架存储,搜索,并分析了大量的事件。

如下图,处理日志和输出到ElasticSearch,可以使用日志记录工具,如rsyslog(www.rsyslog.com),Logstash(www.elastic.co/products/logstash),或apache Flume(flume.apache.org)。搜索和可视化界面分析这些日志,可以使用Kibana

为什么那么多工具适配ElasticSearch?原因如下

  1. ElasticSearch是开源
  2. ElasticSearch提供了JAVA API接口
  3. ElasticSearch提供了RESTful API接口
  4. REST请求和应答是典型的JSON格式。通常情况,一个REST请求包含一个JSON文件,回复也是一个JSON文件。

猜你喜欢

转载自blog.csdn.net/haima95/article/details/84332903