首页> 搜索结果页
"浅谈基于SQL Server分页存储过程五种方法及性能比较" 检索
共 3 条结果
SQLServer海量数据库的查询优化及分页算法方案
  随着“金盾工程”建设的逐步深入和公安信息化的高速发展,公安计算机应用系统被广泛应用在各警种、各部门。与此同时,应用系统体系的核心、系统数据的存放地――数据库也随着实际应用而急剧膨胀,一些大规模的系统,如人口系统的数据甚至超过了1000万条,可谓海量。那么,如何实现快速地从这些超大容量的数据库中提取数据(查询)、分析、统计以及提取数据后进行数据分页已成为各地系统管理员和数据库管理员亟待解决的难题。 在以下的文章中,我将以“办公自动化”系统为例,探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构: CREATE TABLE [dbo].[TGongwen] (    --TGongwen是红头文件表名    [Gid] [int] IDENTITY (1, 1) NOT NULL , --本表的id号,也是主键    [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,  --红头文件的标题    [fariqi] [datetime] NULL , --发布日期    [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL , --发布用户    [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL , --需要浏览的用户。每个用户中间用分隔符“,”分开 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO   下面,我们来往数据库中添加1000万条数据: declare @i int set @i=1 while @i<=250000 begin     insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最先的25万条记录')     set @i=@i+1 end GO   declare @i int set @i=1 while @i<=250000 begin     insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')     set @i=@i+1 end GO   declare @h int set @h=1 while @h<=100 begin declare @i int set @i=2002 while @i<=2003 begin declare @j int         set @j=0         while @j<50             begin declare @k int             set @k=0             while @k<50             begin     insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')             set @k=@k+1             end set @j=@j+1         end set @i=@i+1 end set @h=@h+1 end GO   declare @i int set @i=1 while @i<=9000000 begin     insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')     set @i=@i+1000000 end GO 通过以上语句,我们创建了25万条由通信科于2004年2月5日发布的记录,25万条由办公室于2004年9月6日发布的记录,2002年和2003年各100个2500条相同日期、不同分秒的由通信科发布的记录(共50万条),还有由通信科于2004年5月5日发布的900万条记录,合计1000万条。   一、因情制宜,建立“适当”的索引 建立“适当”的索引是实现查询优化的首要前提。 索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显。注意,在这句话中,我们用了“适当”这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。 (一)深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别: 其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。 我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。 我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。 进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。 (二)何时使用聚集索引或非聚集索引 下面的表总结了何时使用聚集索引或非聚集索引(很重要)。 动作描述  使用聚集索引  使用非聚集索引  列经常被分组排序  应  应  返回某范围内的数据  应  不应  一个或极少不同值  不应  不应  小数目的不同值  应  不应  大数目的不同值  不应  应  频繁更新的列  不应  应  外键列  应  应  主键列  应  应  频繁修改索引列  不应  应   事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。 (三)结合实际,谈索引使用的误区 理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。 1、主键就是聚集索引 这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。 通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。 显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。 从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。 在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。 通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。 在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条): (1)仅在主键上建立聚集索引,并且不划分时间段: Select gid,fariqi,neibuyonghu,title from tgongwen 用时:128470毫秒(即:128秒) (2)在主键上建立聚集索引,在fariq上建立非聚集索引: select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:53763毫秒(54秒) (3)将聚合索引建立在日期列(fariqi)上: select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:2423毫秒(2秒) 虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。 得出以上速度的方法是:在各个select语句前加:declare @d datetime set @d=getdate() 并在select语句后加: select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 2、只要建立索引就能显著提高查询速度 事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。 从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。 3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度 上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。 很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列) (1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' 查询速度:2513毫秒 (2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='办公室' 查询速度:2516毫秒 (3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室' 查询速度:60280毫秒 从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。 (四)其他书上没有的索引使用经验总结 1、用聚合索引比用不是聚合索引的主键速度快 下面是实例语句:(都是提取25万条数据) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' 使用时间:3326毫秒 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000 使用时间:4470毫秒 这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。 2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下 select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi 用时:12936 select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid 用时:18843 这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。 3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' 用时:6343毫秒(提取100万条) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6' 用时:3170毫秒(提取50万条) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' 用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6' 用时:3280毫秒 4 、日期列不会因为有分秒的输入而减慢查询速度 下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi 用时:6390毫秒 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi 用时:6453毫秒 (五)其他注意事项 “水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。 所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。 当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。   二、改善SQL语句 很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如: select * from table1 where name='zhangsan' and tID > 10000 和执行: select * from table1 where tID > 10000 and name='zhangsan' 一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name='zhangsan'的,而后再根据限制条件条件tID>10000来提出查询结果。 事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。 虽然查询优化器可以根据where子句自动的进行查询优化,但大家仍然有必要了解一下“查询优化器”的工作原理,如非这样,有时查询优化器就会不按照您的本意进行快速查询。 在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。 SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值得范围内的匹配或者两个以上条件的AND连接。形式如下: 列名 操作符 <常数 或 变量> 或 <常数 或 变量> 操作符列名 列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如: Name=’张三’ 价格>5000 5000<价格 Name=’张三’ and 价格>5000 如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。 介绍完SARG后,我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验: 1、Like语句是否属于SARG取决于所使用的通配符的类型 如:name like ‘张%’ ,这就属于SARG 而:name like ‘%张’ ,就不属于SARG。 原因是通配符%在字符串的开通使得索引无法使用。 2、or 会引起全表扫描 Name=’张三’ and 价格>5000 符号SARG,而:Name=’张三’ or 价格>5000 则不符合SARG。使用or会引起全表扫描。 3、非操作符、函数引起的不满足SARG形式的语句 不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外还有函数。下面就是几个不满足SARG形式的例子: ABS(价格)<5000 Name like ‘%三’ 有些表达式,如: WHERE 价格*2>5000 SQL SERVER也会认为是SARG,SQL SERVER会将此式转化为: WHERE 价格>2500/2 但我们不推荐这样使用,因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。 4、IN 的作用相当与OR 语句: Select * from table1 where tid in (2,3) 和 Select * from table1 where tid=2 or tid=3 是一样的,都会引起全表扫描,如果tid上有索引,其索引也会失效。 5、尽量少用NOT 6、exists 和 in 的执行效率是一样的 很多资料上都显示说,exists要比in的执行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发现二者无论是前面带不带not,二者之间的执行效率都是一样的。因为涉及子查询,我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开。 (1)select title,price from titles where title_id in (select title_id from sales where qty>30) 该句的执行结果为: 表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。 表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。     (2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30) 第二句的执行结果为: 表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。 表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。 我们从此可以看到用exists和用in的执行效率是一样的。 7、用函数charindex()和前面加通配符%的LIKE执行效率一样 前面,我们谈到,如果在LIKE前面加上通配符%,那么将会引起全表扫描,所以其执行效率是低下的。但有的资料介绍说,用函数charindex()来代替LIKE速度会有大的提升,经我试验,发现这种说明也是错误的: select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader)>0 and fariqi>'2004-5-5' 用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。 select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队' + '%' and fariqi>'2004-5-5' 用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。 8、union并不绝对比or的执行效率高 我们前面已经谈到了在where子句中使用or会引起全表扫描,一般的,我所见过的资料都是推荐这里用union来代替or。事实证明,这种说法对于大部分都是适用的。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000 用时:68秒。扫描计数 1,逻辑读 404008 次,物理读 283 次,预读 392163 次。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' union select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000 用时:9秒。扫描计数 8,逻辑读 67489 次,物理读 216 次,预读 7499 次。 看来,用union在通常情况下比用or的效率要高的多。 但经过试验,笔者发现如果or两边的查询列是一样的话,那么用union则反倒和用or的执行速度差很多,虽然这里union扫描的是索引,而or扫描的是全表。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5' 用时:6423毫秒。扫描计数 2,逻辑读 14726 次,物理读 1 次,预读 7176 次。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' union select gid,fariqi,neibuyonghu,reader,title from Tgongwen where  fariqi='2004-2-5' 用时:11640毫秒。扫描计数 8,逻辑读 14806 次,物理读 108 次,预读 1144 次。 9、字段提取要按照“需多少、提多少”的原则,避免“select *” 我们来做一个试验: select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc 用时:4673毫秒 select top 10000 gid,fariqi,title from tgongwen order by gid desc 用时:1376毫秒 select top 10000 gid,fariqi from tgongwen order by gid desc 用时:80毫秒 由此看来,我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的速度还要看您舍弃的字段的大小来判断。 10、count(*)不比count(字段)慢 某些资料上说:用*会统计所有列,显然要比一个世界的列名效率低。这种说法其实是没有根据的。我们来看: select count(*) from Tgongwen 用时:1500毫秒 select count(gid) from Tgongwen 用时:1483毫秒 select count(fariqi) from Tgongwen 用时:3140毫秒 select count(title) from Tgongwen 用时:52050毫秒 从以上可以看出,如果用count(*)和用count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总的速度就越慢。我想,如果用count(*), SQL SERVER可能会自动查找最小字段来汇总的。当然,如果您直接写count(主键)将会来的更直接些。 11、order by按聚集索引列排序效率最高 我们来看:(gid是主键,fariqi是聚合索引列) select top 10000 gid,fariqi,reader,title from tgongwen 用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。 select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc 用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。 select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc 用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。 select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc 用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。 select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc 用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。 从以上我们可以看出,不排序的速度以及逻辑读次数都是和“order by 聚集索引列” 的速度是相当的,但这些都比“order by 非聚集索引列”的查询速度是快得多的。 同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。 12、高效的TOP 事实上,在查询和提取超大容量的数据集时,影响数据库响应时间的最大因素不是数据查找,而是物理的I/0操作。如: select top 10 * from ( select top 10000 gid,fariqi,title from tgongwen where neibuyonghu='办公室' order by gid desc) as a order by gid asc 这条语句,从理论上讲,整条语句的执行时间应该比子句的执行时间长,但事实相反。因为,子句执行后返回的是10000条记录,而整条语句仅返回10条语句,所以影响数据库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使用TOP关键词了。TOP关键词是SQL SERVER中经过系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的应用,发现TOP确实很好用,效率也很高。但这个词在另外一个大型数据库ORACLE中却没有,这不能说不是一个遗憾,虽然在ORACLE中可以用其他方法(如:rownumber)来解决。在以后的关于“实现千万级数据的分页显示存储过程”的讨论中,我们就将用到TOP这个关键词。   到此为止,我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数据方法。当然,我们介绍的这些方法都是“软”方法,在实践中,我们还要考虑各种“硬”因素,如:网络性能、服务器的性能、操作系统的性能,甚至网卡、交换机等。   三、实现小数据量和海量数据的通用分页显示存储过程 建立一个web 应用,分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法,也就是利用ADO自带的分页功能(利用游标)来实现分页。但这种分页方法仅适用于较小数据量的情形,因为游标本身有缺点:游标是存放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标提供了对特定集合中逐行扫描的手段,一般使用游标来逐行遍历数据,根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等待甚至死机。 更重要的是,对于非常大的数据模型而言,分页检索时,如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。     最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标,由于游标的局限性,所以这个方法并没有得到大家的普遍认可。 后来,网上有人改造了此存储过程,下面的存储过程就是结合我们的办公自动化实例写的分页存储过程: CREATE procedure pagination1 (@pagesize int,  --页面大小,如每页存储20条记录 @pageindex int   --当前页码 ) as set nocount on begin declare @indextable table(id int identity(1,1),nid int)  --定义表变量 declare @PageLowerBound int  --定义此页的底码 declare @PageUpperBound int  --定义此页的顶码 set @PageLowerBound=(@pageindex-1)*@pagesize set @PageUpperBound=@PageLowerBound+@pagesize set rowcount @PageUpperBound insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id end set nocount off 以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然,在这个过程中,您也可以把其中的表变量写成临时表:CREATE TABLE #Temp。但很明显,在SQL SERVER中,用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时,感觉非常的不错,速度也比原来的ADO的好。但后来,我又发现了比此方法更好的方法。 笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》,全文如下: 从publish 表中取出第 n 条到第 m 条的记录: SELECT TOP m-n+1 * FROM publish WHERE (id NOT IN     (SELECT TOP n-1 id      FROM publish)) id 为publish 表的关键字 我当时看到这篇文章的时候,真的是精神为之一振,觉得思路非常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#+SQL SERVER)的时候,忽然想起了这篇文章,我想如果把这个语句改造一下,这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储过程,这个存储过程也是目前较为流行的一种分页存储过程,我很后悔没有争先把这段文字改造成存储过程: CREATE PROCEDURE pagination2 (  @SQL nVARCHAR(4000),    --不带排序语句的SQL语句  @Page int,              --页码  @RecsPerPage int,       --每页容纳的记录数  @ID VARCHAR(255),       --需要排序的不重复的ID号  @Sort VARCHAR(255)      --排序字段及规则 ) AS DECLARE @Str nVARCHAR(4000) SET @Str='SELECT   TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN (SELECT   TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort PRINT @Str EXEC sp_ExecuteSql @Str GO 其实,以上语句可以简化为: SELECT TOP 页大小 * FROM Table1 WHERE (ID NOT IN           (SELECT TOP 页大小*页数 id          FROM 表          ORDER BY id)) ORDER BY ID 但这个存储过程有一个致命的缺点,就是它含有NOT IN字样。虽然我可以把它改造为: SELECT TOP 页大小 * FROM Table1 WHERE not exists (select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id ) order by id 即,用not exists来代替not in,但我们前面已经谈过了,二者的执行效率实际上是没有区别的。 既便如此,用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。 虽然用not exists并不能挽救上个存储过程的效率,但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集,而我们在前面也已经提到了TOP的优势,通过TOP 即可实现对数据量的控制。 在分页算法中,影响我们查询速度的关键因素有两点:TOP和NOT IN。TOP可以提高我们的查询速度,而NOT IN会减慢我们的查询速度,所以要提高我们整个分页算法的速度,就要彻底改造NOT IN,同其他方法来替代它。 我们知道,几乎任何字段,我们都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值,所以如果这个字段不重复,那么就可以利用这些不重复的字段的max或min作为分水岭,使其成为分页算法中分开每页的参照物。在这里,我们可以用操作符“>”或“<”号来完成这个使命,使查询语句符合SARG形式。如: Select top 10 * from table1 where id>200 于是就有了如下分页方案: select top 页大小 * from table1 where id>       (select max (id) from       (select top ((页码-1)*页大小) id from table1 order by id) as T        )       order by id 在选择即不重复值,又容易分辨大小的列时,我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GID(GID是主键,但并不是聚集索引。)为排序列、提取gid,fariqi,title字段,分别以第1、10、100、500、1000、1万、10万、25万、50万页为例,测试以上三种分页方案的执行速度:(单位:毫秒) 页  码  方案1  方案2  方案3   1  60  30  76   10  46  16  63   100  1076  720  130   500  540  12943  83   1000  17110  470  250   1万  24796  4500  140   10万  38326  42283  1553   25万  28140  128720  2330   50万  121686  127846  7168   从上表中,我们可以看出,三种存储过程在执行100页以下的分页命令时,都是可以信任的,速度都很好。但第一种方案在执行分页1000页以上后,速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势,后劲仍然很足。 在确定了第三种分页方案后,我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句,它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案,还会根据页面传来的参数来确定是否进行数据总数统计。 -- 获取指定页的数据 CREATE PROCEDURE pagination3 @tblName   varchar(255),       -- 表名 @strGetFields varchar(1000) = '*',  -- 需要返回的列 @fldName varchar(255)='',      -- 排序的字段名 @PageSize   int = 10,          -- 页尺寸 @PageIndex  int = 1,           -- 页码 @doCount  bit = 0,   -- 返回记录总数, 非 0 值则返回 @OrderType bit = 0,  -- 设置排序类型, 非 0 值则降序 @strWhere  varchar(1500) = ''  -- 查询条件 (注意: 不要加 where) AS declare @strSQL   varchar(5000)       -- 主语句 declare @strTmp   varchar(110)        -- 临时变量 declare @strOrder varchar(400)        -- 排序类型   if @doCount != 0   begin     if @strWhere !=''     set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere     else     set @strSQL = "select count(*) as Total from [" + @tblName + "]" end  --以上代码的意思是如果@doCount传递过来的不是0,就执行总数统计。以下的所有代码都是@doCount为0的情况 else begin   if @OrderType != 0 begin     set @strTmp = "<(select min" set @strOrder = " order by [" + @fldName +"] desc" --如果@OrderType不是0,就执行降序,这句很重要! end else begin     set @strTmp = ">(select max"     set @strOrder = " order by [" + @fldName +"] asc" end   if @PageIndex = 1 begin     if @strWhere != ''       set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from [" + @tblName + "] where " + @strWhere + " " + @strOrder      else      set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["+ @tblName + "] "+ @strOrder --如果是第一页就执行以上代码,这样会加快执行速度 end else begin --以下代码赋予了@strSQL以真正执行的SQL代码 set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["     + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder   if @strWhere != ''     set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "  from ["         + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["         + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["         + @fldName + "] from [" + @tblName + "] where " + @strWhere + " "         + @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder end end   exec (@strSQL) GO 上面的这个存储过程是一个通用的存储过程,其注释已写在其中了。 在大数据量的情况下,特别是在查询最后几页的时候,查询时间一般不会超过9秒;而用其他存储过程,在实践中就会导致超时,所以这个存储过程非常适用于大容量数据库的查询。 笔者希望能够通过对以上存储过程的解析,能给大家带来一定的启示,并给工作带来一定的效率提升,同时希望同行提出更优秀的实时数据分页算法。   四、聚集索引的重要性和如何选择聚集索引 在上一节的标题中,笔者写的是:实现小数据量和海量数据的通用分页显示存储过程。这是因为在将本存储过程应用于“办公自动化”系统的实践中时,笔者发现这第三种存储过程在小数据量的情况下,有如下现象: 1、分页速度一般维持在1秒和3秒之间。 2、在查询最后一页时,速度一般为5秒至8秒,哪怕分页总数只有3页或30万页。 虽然在超大容量情况下,这个分页的实现过程是很快的,但在分前几页时,这个1-3秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢,借用户的话说就是“还没有ACCESS数据库速度快”,这个认识足以导致用户放弃使用您开发的系统。 笔者就此分析了一下,原来产生这种现象的症结是如此的简单,但又如此的重要:排序的字段不是聚集索引! 本篇文章的题目是:“查询优化及分页算法方案”。笔者只所以把“查询优化”和“分页算法”这两个联系不是很大的论题放在一起,就是因为二者都需要一个非常重要的东西――聚集索引。 在前面的讨论中我们已经提到了,聚集索引有两个最大的优势: 1、以最快的速度缩小查询范围。 2、以最快的速度进行字段排序。 第1条多用在查询优化时,而第2条多用在进行分页时的数据排序。 而聚集索引在每个表内又只能建立一个,这使得聚集索引显得更加的重要。聚集索引的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。 但要既使聚集索引列既符合查询列的需要,又符合排序列的需要,这通常是一个矛盾。 笔者前面“索引”的讨论中,将fariqi,即用户发文日期作为了聚集索引的起始列,日期的精确度为“日”。这种作法的优点,前面已经提到了,在进行划时间段的快速查询中,比用ID主键列有很大的优势。 但在分页时,由于这个聚集索引列存在着重复记录,所以无法使用max或min来最为分页的参照物,进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引,那么聚集索引除了用以排序之外,没有任何用处,实际上是浪费了聚集索引这个宝贵的资源。 为解决这个矛盾,笔者后来又添加了一个日期列,其默认值为getdate()。用户在写入记录时,这个列自动写入当时的时间,时间精确到毫秒。即使这样,为了避免可能性很小的重合,还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。 有了这个时间型聚集索引列之后,用户就既可以用这个列查找用户在插入数据时的某个时间段的查询,又可以作为唯一列来实现max或min,成为分页算法的参照物。 经过这样的优化,笔者发现,无论是大数据量的情况下还是小数据量的情况下,分页速度一般都是几十毫秒,甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟钝。 聚集索引是如此的重要和珍贵,所以笔者总结了一下,一定要将聚集索引建立在: 1、您最频繁使用的、用以缩小查询范围的字段上; 2、您最频繁使用的、需要排序的字段上。   结束语: 本篇文章汇集了笔者近段在使用数据库方面的心得,是在做“办公自动化”系统时实践经验的积累。希望这篇文章不仅能够给大家的工作带来一定的帮助,也希望能让大家能够体会到分析问题的方法;最重要的是,希望这篇文章能够抛砖引玉,掀起大家的学习和讨论的兴趣,以共同促进,共同为公安科技强警事业和金盾工程做出自己最大的努力。 最后需要说明的是,在试验中,我发现用户在进行大数据量查询的时候,对数据库速度影响最大的不是内存大小,而是CPU。在我的P4 2.4机器上试验的时候,查看“资源管理器”,CPU经常出现持续到100%的现象,而内存用量却并没有改变或者说没有大的改变。即使在我们的HP ML 350 G3服务器上试验时,CPU峰值也能达到90%,一般持续在70%左右。   版权说明   如果标题未标有<转载、转>等字则属于作者原创,欢迎转载,其版权归作者和博客园共有。   作      者:温景良   文章出处:http://wenjl520.cnblogs.com/  或  http://www.cnblogs.com/ 分类: SQL Server 好文要顶 关注我 收藏该文 温景良(Jason)关注 - 32粉丝 - 161 +加关注 0 0 « 上一篇: Oracle Database 9i/10g安装后的基本环境与服务» 下一篇:解决oracle数据库监听器无法启动问题 posted @ 2009-05-04 23:48 温景良(Jason) Views(460) Comments(4) Edit 收藏 Post Comment    #1楼 2009-05-13 22:07 | Frank Xu Lei   写的够详细,呵呵。 索引是基于B+结构的。所有的叶节点 是数据行的记录位置。但是非聚集索引节点的值因有无聚集索引会有所不同。 支持(0)反对(0) http://pic.cnblogs.com/face/u47644.jpg    #2楼 2009-05-13 22:10 | Frank Xu Lei   这些查询优化,包括设计数据表。很多时候别人谈论数据库优化。说到自己做过数据库优化的工作。其实我很想知道除了上面的优化之外,还有什么比较深入的优化措施。硬件、软件,真正再算法或者底层机制上剖析的很深入的。 支持(0)反对(0) http://pic.cnblogs.com/face/u47644.jpg    #3楼[楼主] 2009-05-14 00:19 | 温景良(Jason)   呵呵,其实这些都是网上搜索的,说实话没有真正的实战 支持(0)反对(0) http://pic.cnblogs.com/face/u33118.jpg    #4楼[楼主]15333462009/5/20 20:45:08 2009-05-20 20:45 | 温景良(Jason)   呵呵,这东西就是看看,在做的时候可以修改 支持(0)反对(0) http://pic.cnblogs.com/face/u33118.jpg 刷新评论刷新页面返回顶部 注册用户登录后才能发表评论,请 登录 或 注册,访问网站首页。 【推荐】超50万VC++源码: 大型工控、组态\仿真、建模CAD源码2018!【推荐】腾讯云新用户域名抢购1元起,抓紧抢购 最新IT新闻: · 精准率首次超过人类!阿里巴巴机器阅读理解打破世界纪录 · 技术帖:每天被今日头条推送文章 背后的算法技术是什么? · 支付宝实体版老黄历问世:全球限量1000册 · 趣店被蚂蚁金服送上纽交所,现在是时候该独立了 · 蚂蚁宝卡升级:支付宝/微博即将免流 » 更多新闻... 最新知识库文章:· 步入云计算 · 以操作系统的角度述说线程与进程 · 软件测试转型之路 · 门内门外看招聘 · 大道至简,职场上做人做事做管理 » 更多知识库文章... 公告 hit counter dreamweaver 本文转自我的程序人生博客园博客,原文链接:http://www.cnblogs.com/wenjl520/archive/2009/05/04/1449187.html,如需转载请自行联系原作者    
文章
SQL  ·  存储  ·  算法  ·  数据库  ·  索引
2017-02-10
后端架构师技术图谱
数据结构 队列 集合 链表、数组 字典、关联数组 栈 树 二叉树 完全二叉树 平衡二叉树 二叉查找树(BST) 红黑树 B-,B+,B*树 LSM 树 BitSet 常用算法 排序、查找算法 选择排序 冒泡排序 插入排序 快速排序 归并排序 希尔排序 堆排序 计数排序 桶排序 基数排序 二分查找 Java 中的排序工具 布隆过滤器 字符串比较 KMP 算法 深度优先、广度优先 贪心算法 回溯算法 剪枝算法 动态规划 朴素贝叶斯 推荐算法 最小生成树算法 最短路径算法 并发 多线程 线程安全 一致性、事务 事务 ACID 特性 事务的隔离级别 MVCC 锁 Java中的锁和同步类 公平锁 & 非公平锁 悲观锁 乐观锁 & CAS ABA 问题 CopyOnWrite容器 RingBuffer 可重入锁 & 不可重入锁 互斥锁 & 共享锁 死锁 操作系统 计算机原理 CPU 多级缓存 进程 线程 协程 Linux 设计模式 设计模式的六大原则 23种常见设计模式 应用场景 单例模式 责任链模式 MVC IOC AOP UML 微服务思想 康威定律 运维 & 统计 & 技术支持 常规监控 APM 统计分析 持续集成(CI/CD) Jenkins 环境分离 自动化运维 Ansible puppet chef 测试 TDD 理论 单元测试 压力测试 全链路压测 A/B 、灰度、蓝绿测试 虚拟化 KVM Xen OpenVZ 容器技术 Docker 云技术 OpenStack DevOps 文档管理 中间件 Web Server Nginx OpenResty Apache Httpd Tomcat 架构原理 调优方案 Jetty 缓存 本地缓存 客户端缓存 服务端缓存 Memcached Redis 架构 回收策略 Tair 消息队列 消息总线 消息的顺序 RabbitMQ RocketMQ ActiveMQ Kafka Redis 消息推送 ZeroMQ 定时调度 单机定时调度 分布式定时调度 RPC Dubbo Thrift gRPC 数据库中间件 Sharding Jdbc 日志系统 日志搜集 配置中心 API 网关 网络 协议 OSI 七层协议 TCP/IP HTTP HTTP2.0 HTTPS 网络模型 Epoll Java NIO kqueue 连接和短连接 框架 零拷贝(Zero-copy) 序列化(二进制协议) Hessian Protobuf 数据库 基础理论 数据库设计的三大范式 MySQL 原理 InnoDB 优化 索引 聚集索引, 非聚集索引 复合索引 自适应哈希索引(AHI) explain NoSQL MongoDB Hbase 搜索引擎 搜索引擎原理 Lucene Elasticsearch Solr sphinx 性能 性能优化方法论 容量评估 CDN 网络 连接池 性能调优 大数据 流式计算 Storm Flink Kafka Stream 应用场景 Hadoop HDFS MapReduce Yarn Spark 安全 web 安全 XSS CSRF SQL 注入 Hash Dos 脚本注入 漏洞扫描工具 验证码 DDoS 防范 用户隐私信息保护 序列化漏洞 加密解密 对称加密 哈希算法 非对称加密 服务器安全 数据安全 数据备份 网络隔离 内外网分离 登录跳板机 授权、认证 RBAC OAuth2.0 双因素认证(2FA) 单点登录(SSO) 常用开源框架 开源协议 日志框架 Log4j、Log4j2 Logback ORM 网络框架 Web 框架 Spring 家族 工具框架 分布式设计 扩展性设计 稳定性 & 高可用 硬件负载均衡 软件负载均衡 限流 应用层容灾 跨机房容灾 容灾演练流程 平滑启动 数据库扩展 读写分离模式 分片模式 服务治理 服务注册与发现 服务路由控制 分布式一致 CAP 与 BASE 理论 分布式锁 分布式一致性算法 PAXOS Zab Raft Gossip 两阶段提交、多阶段提交 幂等 分布式一致方案 分布式 Leader 节点选举 TCC(Try/Confirm/Cancel) 柔性事务 分布式文件系统 唯一ID 生成 全局唯一ID 一致性Hash算法 设计思想 & 开发模式 DDD(Domain-driven Design - 领域驱动设计) 命令查询职责分离(CQRS) 贫血,充血模型 Actor 模式 响应式编程 Reactor RxJava Vert.x DODAF2.0 Serverless Service Mesh 项目管理 架构评审 重构 代码规范 代码 Review RUP 看板管理 SCRUM 敏捷开发 极限编程(XP) 结对编程 FMEA管理模式 通用业务术语 技术趋势 政策、法规 法律 严格遵守刑法253法条 架构师素质 团队管理 招聘 资讯 行业资讯 公众号列表 博客 团队博客 个人博客 综合门户、社区 问答、讨论类社区 行业数据分析 专项网站 其他类 推荐参考书 在线电子书 纸质书 开发方面 架构方面 技术管理方面 基础理论 工具方面 大数据方面 技术资源 开源资源 手册、文档、教程 在线课堂 会议、活动 常用APP 找工作 工具 代码托管 文件服务 综合云服务商 (Toc generated by simple-php-github-toc ) 数据结构 队列 《java队列——queue详细分析》 非阻塞队列:ConcurrentLinkedQueue(无界线程安全),采用CAS机制(compareAndSwapObject原子操作)。 阻塞队列:ArrayBlockingQueue(有界)、LinkedBlockingQueue(无界)、DelayQueue、PriorityBlockingQueue,采用锁机制;使用 ReentrantLock 锁。 《LinkedList、ConcurrentLinkedQueue、LinkedBlockingQueue对比分析》 集合 《Java Set集合的详解》 链表、数组 《Java集合详解--什么是List》 字典、关联数组 《Java map 详解 - 用法、遍历、排序、常用API等》 栈 《java数据结构与算法之栈(Stack)设计与实现》 《Java Stack 类》 《java stack的详细实现分析》 Stack 是线程安全的。 内部使用数组保存数据,不够时翻倍。 树 二叉树 每个节点最多有两个叶子节点。 《二叉树》 完全二叉树 《完全二叉树》 叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。 平衡二叉树 左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 《浅谈数据结构-平衡二叉树》 《浅谈算法和数据结构: 八 平衡查找树之2-3树》 二叉查找树(BST) 二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree)。 《浅谈算法和数据结构: 七 二叉查找树》 红黑树 《最容易懂得红黑树》 添加阶段后,左旋或者右旋从而再次达到平衡。 《浅谈算法和数据结构: 九 平衡查找树之红黑树》 B-,B+,B*树 MySQL是基于B+树聚集索引组织表 《B-树,B+树,B*树详解》 《B-树,B+树与B*树的优缺点比较》 B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 LSM 树 LSM(Log-Structured Merge-Trees)和 B+ 树相比,是牺牲了部分读的性能来换取写的性能(通过批量写入),实现读写之间的。 Hbase、LevelDB、Tair(Long DB)、nessDB 采用 LSM 树的结构。LSM可以快速建立索引。 《LSM树 VS B+树》 B+ 树读性能好,但由于需要有序结构,当key比较分散时,磁盘寻道频繁,造成写性能。 LSM 是将一个大树拆分成N棵小树,先写到内存(无寻道问题,性能高),在内存中构建一颗有序小树(有序树),随着小树越来越大,内存的小树会flush到磁盘上。当读时,由于不知道数据在哪棵小树上,因此必须遍历(二分查找)所有的小树,但在每颗小树内部数据是有序的。 《LSM树(Log-Structured Merge Tree)存储引擎》 极端的说,基于LSM树实现的HBase的写性能比MySQL高了一个数量级,读性能低了一个数量级。 优化方式:Bloom filter 替代二分查找;compact 小数位大树,提高查询性能。 Hbase 中,内存中达到一定阈值后,整体flush到磁盘上、形成一个文件(B+数),HDFS不支持update操作,所以Hbase做整体flush而不是merge update。flush到磁盘上的小树,定期会合并成一个大树。 BitSet 经常用于大规模数据的排重检查。 《Java Bitset类》 《Java BitSet(位集)》 常用算法 《常见排序算法及对应的时间复杂度和空间复杂度》 排序、查找算法 《常见排序算法及对应的时间复杂度和空间复杂度》 选择排序 《Java中的经典算法之选择排序(SelectionSort)》 每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。 冒泡排序 《冒泡排序的2种写法》 相邻元素前后交换、把最大的排到最后。 时间复杂度 O(n²) 插入排序 《排序算法总结之插入排序》 快速排序 《坐在马桶上看算法:快速排序》 一侧比另外一次都大或小。 归并排序 《图解排序算法(四)之归并排序》 分而治之,分成小份排序,在合并(重建一个新空间进行复制)。 希尔排序 TODO 堆排序 《图解排序算法(三)之堆排序》 排序过程就是构建最大堆的过程,最大堆:每个结点的值都大于或等于其左右孩子结点的值,堆顶元素是最大值。 计数排序 《计数排序和桶排序》 和桶排序过程比较像,差别在于桶的数量。 桶排序 《【啊哈!算法】最快最简单的排序——桶排序》 《排序算法(三):计数排序与桶排序》 桶排序将[0,1)区间划分为n个相同的大小的子区间,这些子区间被称为桶。 每个通单独进行排序,然后再遍历每个桶。 基数排序 按照个位、十位、百位、...依次来排。 《排序算法系列:基数排序》 《基数排序》 二分查找 《二分查找(java实现)》 要求待查找的序列有序。 时间复杂度 O(logN)。 《java实现二分查找-两种方式》 while + 递归。 Java 中的排序工具 《Arrays.sort和Collections.sort实现原理解析》 Collections.sort算法调用的是合并排序。 Arrays.sort() 采用了2种排序算法 -- 基本类型数据使用快速排序法,对象数组使用归并排序。 布隆过滤器 常用于大数据的排重,比如email,url 等。 核心原理:将每条数据通过计算产生一个指纹(一个字节或多个字节,但一定比原始数据要少很多),其中每一位都是通过随机计算获得,在将指纹映射到一个大的按位存储的空间中。注意:会有一定的错误率。 优点:空间和时间效率都很高。 缺点:随着存入的元素数量增加,误算率随之增加。 《布隆过滤器 -- 空间效率很高的数据结构》 《大量数据去重:Bitmap和布隆过滤器(Bloom Filter)》 《基于Redis的布隆过滤器的实现》 基于 Redis 的 Bitmap 数据结构。 《网络爬虫:URL去重策略之布隆过滤器(BloomFilter)的使用》 使用Java中的 BitSet 类 和 加权和hash算法。 字符串比较 KMP 算法 KMP:Knuth-Morris-Pratt算法(简称KMP) 核心原理是利用一个“部分匹配表”,跳过已经匹配过的元素。 《字符串匹配的KMP算法》 深度优先、广度优先 《广度优先搜索BFS和深度优先搜索DFS》 贪心算法 《算法:贪婪算法基础》 《常见算法及问题场景——贪心算法》 回溯算法 《 五大常用算法之四:回溯法》 剪枝算法 《α-β剪枝算法》 动态规划 《详解动态规划——邹博讲动态规划》 《动态规划算法的个人理解》 朴素贝叶斯 《带你搞懂朴素贝叶斯分类算法》 P(B|A)=P(A|B)P(B)/P(A) 《贝叶斯推断及其互联网应用1》 《贝叶斯推断及其互联网应用2》 推荐算法 《推荐算法综述》 《TOP 10 开源的推荐系统简介》 最小生成树算法 《算法导论--最小生成树(Kruskal和Prim算法)》 最短路径算法 《Dijkstra算法详解》 并发 多线程 《40个Java多线程问题总结》 线程安全 《Java并发编程——线程安全及解决机制简介》 一致性、事务 事务 ACID 特性 《数据库事务ACID特性》 事务的隔离级别 未提交读:一个事务可以读取另一个未提交的数据,容易出现脏读的情况。 读提交:一个事务等另外一个事务提交之后才可以读取数据,但会出现不可重复读的情况(多次读取的数据不一致),读取过程中出现UPDATE操作,会多。(大多数数据库默认级别是RC,比如SQL Server,Oracle),读取的时候不可以修改。 可重复读: 同一个事务里确保每次读取的时候,获得的是同样的数据,但不保障原始数据被其他事务更新(幻读),Mysql InnoDB 就是这个级别。 序列化:所有事物串行处理(牺牲了效率) 《理解事务的4种隔离级别》 数据库事务的四大特性及事务隔离级别 《MySQL的InnoDB的幻读问题 》 幻读的例子非常清楚。 通过 SELECT ... FOR UPDATE 解决。 《一篇文章带你读懂MySQL和InnoDB》 图解脏读、不可重复读、幻读问题。 MVCC 《【mysql】关于innodb中MVCC的一些理解》 innodb 中 MVCC 用在 Repeatable-Read 隔离级别。 MVCC 会产生幻读问题(更新时异常。) 《轻松理解MYSQL MVCC 实现机制》 通过隐藏版本列来实现 MVCC 控制,一列记录创建时间、一列记录删除时间,这里的时间 每次只操作比当前版本小(或等于)的 行。 锁 Java中的锁和同步类 《Java中的锁分类》 主要包括 synchronized、ReentrantLock、和 ReadWriteLock。 《Java并发之AQS详解》 《Java中信号量 Semaphore》 有数量控制 申请用 acquire,申请不要则阻塞;释放用 release。 《java开发中的Mutex vs Semaphore》 简单的说 就是Mutex是排它的,只有一个可以获取到资源, Semaphore也具有排它性,但可以定义多个可以获取的资源的对象。 公平锁 & 非公平锁 公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。 《公平锁与非公平锁》 默认情况下 ReentrantLock 和 synchronized 都是非公平锁。ReentrantLock 可以设置成公平锁。 悲观锁 悲观锁如果使用不当(锁的条数过多),会引起服务大面积等待。推荐优先使用乐观锁+重试。 《【MySQL】悲观锁&乐观锁》 乐观锁的方式:版本号+重试方式 悲观锁:通过 select ... for update 进行行锁(不可读、不可写,share 锁可读不可写)。 《Mysql查询语句使用select.. for update导致的数据库死锁分析》 mysql的innodb存储引擎实务锁虽然是锁行,但它内部是锁索引的。 锁相同数据的不同索引条件可能会引起死锁。 《Mysql并发时经典常见的死锁原因及解决方法》 乐观锁 & CAS 《乐观锁的一种实现方式——CAS》 和MySQL乐观锁方式相似,只不过是通过和原值进行比较。 ABA 问题 由于高并发,在CAS下,更新后可能此A非彼A。通过版本号可以解决,类似于上文Mysql 中提到的的乐观锁。 《Java CAS 和ABA问题》 《Java 中 ABA问题及避免》 AtomicStampedReference 和 AtomicStampedReference。 CopyOnWrite容器 可以对CopyOnWrite容器进行并发的读,而不需要加锁。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,不适合需要数据强一致性的场景。 《JAVA中写时复制(Copy-On-Write)Map实现》 实现读写分离,读取发生在原始数据上,写入发生在副本上。 不用加锁,通过最终一致实现一致性。 《聊聊并发-Java中的Copy-On-Write容器》 RingBuffer 《线程安全的无锁RingBuffer的实现【一个读线程,一个写线程】》 可重入锁 & 不可重入锁 《可重入锁和不可重入锁》 通过简单代码举例说明可重入锁和不可重入锁。 可重入锁指同一个线程可以再次获得之前已经获得的锁。 可重入锁可以用户避免死锁。 Java中的可重入锁:synchronized 和 java.util.concurrent.locks.ReentrantLock 《ReenTrantLock可重入锁(和synchronized的区别)总结》 synchronized 使用方便,编译器来加锁,是非公平锁。 ReenTrantLock 使用灵活,锁的公平性可以定制。 相同加锁场景下,推荐使用 synchronized。 互斥锁 & 共享锁 互斥锁:同时只能有一个线程获得锁。比如,ReentrantLock 是互斥锁,ReadWriteLock 中的写锁是互斥锁。 共享锁:可以有多个线程同时或的锁。比如,Semaphore、CountDownLatch 是共享锁,ReadWriteLock 中的读锁是共享锁。 《ReadWriteLock场景应用》 死锁 《“死锁”四个必要条件的合理解释》 互斥、持有、不可剥夺、不可剥夺。 Java如何查看死锁? JConsole 可以识别死锁。 java多线程系列:死锁及检测 jstack 可以显示死锁。 操作系统 计算机原理 《操作系统基础知识——操作系统的原理,类型和结构》 CPU 多级缓存 典型的 CPU 有三级缓存,距离核心越近,速度越快,空间越小。L1 一般 32k,L2 一般 256k,L3 一般12M。内存速度需要200个 CPU 周期,CPU 缓存需要1个CPU周期。 《从Java视角理解CPU缓存和伪共享》 进程 TODO 线程 《线程的生命周期及状态转换详解》 协程 《终结python协程----从yield到actor模型的实现》 线程的调度是由操作系统负责,协程调度是程序自行负责 与线程相比,协程减少了无谓的操作系统切换. 实际上当遇到IO操作时做切换才更有意义,(因为IO操作不用占用CPU),如果没遇到IO操作,按照时间片切换. Linux 《Linux 命令大全》 设计模式 设计模式的六大原则 《设计模式的六大原则》 开闭原则:对扩展开放,对修改关闭,多使用抽象类和接口。 里氏代换原则:基类可以被子类替换,使用抽象类继承,不使用具体类继承。 依赖倒转原则:要依赖于抽象,不要依赖于具体,针对接口编程,不针对实现编程。 接口隔离原则:使用多个隔离的接口,比使用单个接口好,建立最小的接口。 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用,通过中间类建立联系。 合成复用原则:尽量使用合成/聚合,而不是使用继承。 23种常见设计模式 《设计模式》 《23种设计模式全解析》 应用场景 《细数JDK里的设计模式》 结构型模式: 适配器:用来把一个接口转化成另一个接口,如 java.util.Arrays#asList()。 桥接模式:这个模式将抽象和抽象操作的实现进行了解耦,这样使得抽象和实现可以独立地变化,如JDBC; 组合模式:使得客户端看来单个对象和对象的组合是同等的。换句话说,某个类型的方法同时也接受自身类型作为参数,如 Map.putAll,List.addAll、Set.addAll。 装饰者模式:动态的给一个对象附加额外的功能,这也是子类的一种替代方式,如 java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap。 享元模式:使用缓存来加速大量小对象的访问时间,如 valueOf(int)。 代理模式:代理模式是用一个简单的对象来代替一个复杂的或者创建耗时的对象,如 java.lang.reflect.Proxy 创建模式: 抽象工厂模式:抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型,如 java.util.Calendar#getInstance()。 建造模式(Builder):定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建,如:java.lang.StringBuilder#append()。 工厂方法:就是 一个返* 回具体对象的方法,而不是多个,如 java.lang.Object#toString()、java.lang.Class#newInstance()。 原型模式:使得类的实例能够生成自身的拷贝、如:java.lang.Object#clone()。 单例模式:全局只有一个实例,如 java.lang.Runtime#getRuntime()。 行为模式: 责任链模式:通过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象间的解耦。如 javax.servlet.Filter#doFilter()。 命令模式:将操作封装到对象内,以便存储,传递和返回,如:java.lang.Runnable。 解释器模式:定义了一个语言的语法,然后解析相应语法的语句,如,java.text.Format,java.text.Normalizer。 迭代器模式:提供一个一致的方法来顺序访问集合中的对象,如 java.util.Iterator。 中介者模式:通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖,java.lang.reflect.Method#invoke()。 空对象模式:如 java.util.Collections#emptyList()。 观察者模式:它使得一个对象可以灵活的将消息发送给感兴趣的对象,如 java.util.EventListener。 模板方法模式:让子类可以重写方法的一部分,而不是整个重写,如 java.util.Collections#sort()。 《Spring-涉及到的设计模式汇总》 《Mybatis使用的设计模式》 单例模式 《单例模式的三种实现 以及各自的优缺点》 《单例模式--反射--防止序列化破坏单例模式》 使用枚举类型。 责任链模式 TODO MVC 《MVC 模式》 模型(model)-视图(view)-控制器(controller) IOC 《理解 IOC》 《IOC 的理解与解释》 正向控制:传统通过new的方式。反向控制,通过容器注入对象。 作用:用于模块解耦。 DI:Dependency Injection,即依赖注入,只关心资源使用,不关心资源来源。 AOP 《轻松理解AOP(面向切面编程)》 《Spring AOP详解》 《Spring AOP的实现原理》 Spring AOP使用的动态代理,主要有两种方式:JDK动态代理和CGLIB动态代理。 《Spring AOP 实现原理与 CGLIB 应用》 Spring AOP 框架对 AOP 代理类的处理原则是:如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类 UML 《UML教程》 微服务思想 《微服务架构设计》 《微服务架构技术栈选型手册》 康威定律 《微服务架构的理论基础 - 康威定律》 定律一:组织沟通方式会通过系统设计表达出来,就是说架构的布局和组织结构会有相似。 定律二:时间再多一件事情也不可能做的完美,但总有时间做完一件事情。一口气吃不成胖子,先搞定能搞定的。 定律三:线型系统和线型组织架构间有潜在的异质同态特性。种瓜得瓜,做独立自治的子系统减少沟通成本。 定律四:大的系统组织总是比小系统更倾向于分解。合久必分,分而治之。 《微服务架构核⼼20讲》 运维 & 统计 & 技术支持 常规监控 《腾讯业务系统监控的修炼之路》 监控的方式:主动、被动、旁路(比如舆情监控) 监控类型: 基础监控、服务端监控、客户端监控、 监控、用户端监控 监控的目标:全、块、准 核心指标:请求量、成功率、耗时 《开源还是商用?十大云运维监控工具横评》 Zabbix、Nagios、Ganglia、Zenoss、Open-falcon、监控宝、 360网站服务监控、阿里云监控、百度云观测、小蜜蜂网站监测等。 《监控报警系统搭建及二次开发经验》 命令行监控工具 《常用命令行监控工具》 top、sar、tsar、nload 《20个命令行工具监控 Linux 系统性能》 《JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解》 APM APM — Application Performance Management 《Dapper,大规模分布式系统的跟踪系统》 CNCF OpenTracing,中文版 主要开源软件,按字母排序 Apache SkyWalking CAT CNCF jaeger Pinpoint Zipkin 《开源APM技术选型与实战》 主要基于 Google的Dapper(大规模分布式系统的跟踪系统) 思想。 统计分析 《流量统计的基础:埋点》 常用指标:访问与访客、停留时长、跳出率、退出率、转化率、参与度 《APP埋点常用的统计工具、埋点目标和埋点内容》 第三方统计:友盟、百度移动、魔方、App Annie、talking data、神策数据等。 《美团点评前端无痕埋点实践》 所谓无痕、即通过可视化工具配置采集节点,在前端自动解析配置并上报埋点数据,而非硬编码。 持续集成(CI/CD) 《持续集成是什么?》 《8个流行的持续集成工具》 Jenkins 《使用Jenkins进行持续集成》 环境分离 开发、测试、生成环境分离。 《开发环境、生产环境、测试环境的基本理解和区》 自动化运维 Ansible 《Ansible中文权威指南》 《Ansible基础配置和企业级项目实用案例》 puppet 《自动化运维工具——puppet详解》 chef 《Chef 的安装与使用》 测试 TDD 理论 《深度解读 - TDD(测试驱动开发)》 基于测试用例编码功能代码,XP(Extreme Programming)的核心实践. 好处:一次关注一个点,降低思维负担;迎接需求变化或改善代码的设计;提前澄清需求;快速反馈; 单元测试 《Java单元测试之JUnit篇》 《JUnit 4 与 TestNG 对比》 TestNG 覆盖 JUnit 功能,适用于更复杂的场景。 《单元测试主要的测试功能点》 模块接口测试、局部数据结构测试、路径测试 、错误处理测试、边界条件测试 。 压力测试 《Apache ab 测试使用指南》 《大型网站压力测试及优化方案》 《10大主流压力/负载/性能测试工具推荐》 《真实流量压测工具 tcpcopy应用浅析》 《nGrinder 简易使用教程》 全链路压测 《京东618:升级全链路压测方案,打造军演机器人ForceBot》 《饿了么全链路压测的探索与实践》 《四大语言,八大框架|滴滴全链路压测解决之道》 《全链路压测经验》 A/B 、灰度、蓝绿测试 《技术干货 | AB 测试和灰度发布探索及实践》 《nginx 根据IP 进行灰度发布》 《蓝绿部署、A/B 测试以及灰度发布》 虚拟化 《VPS的三种虚拟技术OpenVZ、Xen、KVM优缺点比较》 KVM 《KVM详解,太详细太深入了,经典》 《【图文】KVM 虚拟机安装详解》 Xen 《Xen虚拟化基本原理详解》 OpenVZ 《开源Linux容器 OpenVZ 快速上手指南》 容器技术 Docker 《几张图帮你理解 docker 基本原理及快速入门》 《Docker 核心技术与实现原理》 《Docker 教程》 云技术 OpenStack 《OpenStack构架知识梳理》 DevOps 《一分钟告诉你究竟DevOps是什么鬼?》 《DevOps详解》 文档管理 Confluence-收费文档管理系统 GitLab? Wiki 中间件 Web Server Nginx 《Ngnix的基本学习-多进程和Apache的比较》 Nginx 通过异步非阻塞的事件处理机制实现高并发。Apache 每个请求独占一个线程,非常消耗系统资源。 事件驱动适合于IO密集型服务(Nginx),多进程或线程适合于CPU密集型服务(Apache),所以Nginx适合做反向代理,而非web服务器使用。 《nginx与Apache的对比以及优缺点》 nginx只适合静态和反向代理,不适合处理动态请求。 OpenResty 官方网站 《浅谈 OpenResty》 通过 Lua 模块可以在Nginx上进行开发。 Apache Httpd 官方网站 Tomcat 架构原理 《TOMCAT原理详解及请求过程》 《Tomcat服务器原理详解》 《Tomcat 系统架构与设计模式,第 1 部分: 工作原理》 《四张图带你了解Tomcat系统架构》 《JBoss vs. Tomcat: Choosing A Java Application Server》 Tomcat 是轻量级的 Serverlet 容器,没有实现全部 JEE 特性(比如持久化和事务处理),但可以通过其他组件代替,比如Srping。 Jboss 实现全部了JEE特性,软件开源免费、文档收费。 调优方案 《Tomcat 调优方案》 启动NIO模式(或者APR);调整线程池;禁用AJP连接器(Nginx+tomcat的架构,不需要AJP); 《tomcat http协议与ajp协议》 《AJP与HTTP比较和分析》 AJP 协议(8009端口)用于降低和前端Server(如Apache,而且需要支持AJP协议)的连接数(前端),通过长连接提高性能。 并发高时,AJP协议优于HTTP协议。 Jetty 《Jetty 的工作原理以及与 Tomcat 的比较》 《jetty和tomcat优势比较》 架构比较:Jetty的架构比Tomcat的更为简单。 性能比较:Jetty和Tomcat性能方面差异不大,Jetty默认采用NIO结束在处理I/O请求上更占优势,Tomcat默认采用BIO处理I/O请求,Tomcat适合处理少数非常繁忙的链接,处理静态资源时性能较差。 其他方面:Jetty的应用更加快速,修改简单,对新的Servlet规范的支持较好;Tomcat 对JEE和Servlet 支持更加全面。 缓存 《缓存失效策略(FIFO 、LRU、LFU三种算法的区别)》 本地缓存 《HashMap本地缓存》 《EhCache本地缓存》 堆内、堆外、磁盘三级缓存。 可按照缓存空间容量进行设置。 按照时间、次数等过期策略。 《Guava Cache》 简单轻量、无堆外、磁盘缓存。 《Nginx本地缓存》 《Pagespeed—懒人工具,服务器端加速》 客户端缓存 《浏览器端缓存》 主要是利用 Cache-Control 参数。 《H5 和移动端 WebView 缓存机制解析与实战》 服务端缓存 Memcached 《Memcached 教程》 《深入理解Memcached原理》 采用多路复用技术提高并发性。 slab分配算法: memcached给Slab分配内存空间,默认是1MB。分配给Slab之后 把slab的切分成大小相同的chunk,Chunk是用于缓存记录的内存空间,Chunk 的大小默认按照1.25倍的速度递增。好处是不会频繁申请内存,提高IO效率,坏处是会有一定的内存浪费。 《Memcached软件工作原理》 《Memcache技术分享:介绍、使用、存储、算法、优化、命中率》 《memcache 中 add 、 set 、replace 的区别》 区别在于当key存在还是不存在时,返回值是true和false的。 《memcached全面剖析》 Redis 《Redis 教程》 《redis底层原理》 使用 ziplist 存储链表,ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。 使用 skiplist(跳跃表)来存储有序集合对象、查找上先从高Level查起、时间复杂度和红黑树相当,实现容易,无锁、并发性好。 《Redis持久化方式》 RDB方式:定期备份快照,常用于灾难恢复。优点:通过fork出的进程进行备份,不影响主进程、RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。缺点:会丢数据。 AOF方式:保存操作日志方式。优点:恢复时数据丢失少,缺点:文件大,回复慢。 也可以两者结合使用。 《分布式缓存--序列3--原子操作与CAS乐观锁》 架构 《Redis单线程架构》 回收策略 《redis的回收策略》 Tair 官方网站 《Tair和Redis的对比》 特点:可以配置备份节点数目,通过异步同步到备份节点 一致性Hash算法。 架构:和Hadoop 的设计思想类似,有Configserver,DataServer,Configserver 通过心跳来检测,Configserver也有主备关系。 几种存储引擎: MDB,完全内存性,可以用来存储Session等数据。 Rdb(类似于Redis),轻量化,去除了aof之类的操作,支持Restfull操作 LDB(LevelDB存储引擎),持久化存储,LDB 作为rdb的持久化,google实现,比较高效,理论基础是LSM(Log-Structured-Merge Tree)算法,现在内存中修改数据,达到一定量时(和内存汇总的旧数据一同写入磁盘)再写入磁盘,存储更加高效,县比喻Hash算法。 Tair采用共享内存来存储数据,如果服务挂掉(非服务器),重启服务之后,数据亦然还在。 消息队列 《消息队列-推/拉模式学习 & ActiveMQ及JMS学习》 RabbitMQ 消费者默认是推模式(也支持拉模式)。 Kafka 默认是拉模式。 Push方式:优点是可以尽可能快地将消息发送给消费者,缺点是如果消费者处理能力跟不上,消费者的缓冲区可能会溢出。 Pull方式:优点是消费端可以按处理能力进行拉去,缺点是会增加消息延迟。 《Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和区别》 消息总线 消息总线相当于在消息队列之上做了一层封装,统一入口,统一管控、简化接入成本。 《消息总线VS消息队列》 消息的顺序 《如何保证消费者接收消息的顺序》 RabbitMQ 支持事务,推拉模式都是支持、适合需要可靠性消息传输的场景。 《RabbitMQ的应用场景以及基本原理介绍》 《消息队列之 RabbitMQ》 《RabbitMQ之消息确认机制(事务+Confirm)》 RocketMQ Java实现,推拉模式都是支持,吞吐量逊于Kafka。可以保证消息顺序。 《RocketMQ 实战之快速入门》 ActiveMQ 纯Java实现,兼容JMS,可以内嵌于Java应用中。 《ActiveMQ消息队列介绍》 Kafka 高吞吐量、采用拉模式。适合高IO场景,比如日志同步。 官方网站 《各消息队列对比,Kafka深度解析,众人推荐,精彩好文!》 《Kafka分区机制介绍与示例》 Redis 消息推送 生产者、消费者模式完全是客户端行为,list 和 拉模式实现,阻塞等待采用 blpop 指令。 《Redis学习笔记之十:Redis用作消息队列》 ZeroMQ TODO 定时调度 单机定时调度 《linux定时任务cron配置》 《Linux cron运行原理》 fork 进程 + sleep 轮询 《Quartz使用总结》 《Quartz源码解析 ---- 触发器按时启动原理》 《quartz原理揭秘和源码解读》 定时调度在 QuartzSchedulerThread 代码中,while()无限循环,每次循环取出时间将到的trigger,触发对应的job,直到调度器线程被关闭。 分布式定时调度 《这些优秀的国产分布式任务调度系统,你用过几个?》 opencron、LTS、XXL-JOB、Elastic-Job、Uncode-Schedule、Antares 《Quartz任务调度的基本实现原理》 Quartz集群中,独立的Quartz节点并不与另一其的节点或是管理节点通信,而是通过相同的数据库表来感知到另一Quartz应用的 RPC 《从零开始实现RPC框架 - RPC原理及实现》 核心角色:Server: 暴露服务的服务提供方、Client: 调用远程服务的服务消费方、Registry: 服务注册与发现的注册中心。 《分布式RPC框架性能大比拼 dubbo、motan、rpcx、gRPC、thrift的性能比较》 Dubbo 官方网站 dubbo实现原理简单介绍 ** SPI ** TODO Thrift 官方网站 《Thrift RPC详解》 支持多语言,通过中间语言定义接口。 gRPC 服务端可以认证加密,在外网环境下,可以保证数据安全。 官方网站 《你应该知道的RPC原理》 数据库中间件 Sharding Jdbc 官网 日志系统 日志搜集 《从零开始搭建一个ELKB日志收集系统》 《用ELK搭建简单的日志收集分析系统》 《日志收集系统-探究》 配置中心 Apollo - 携程开源的配置中心应用 Spring Boot 和 Spring Cloud 支持推、拉模式更新配置 支持多种语言 《基于zookeeper实现统一配置管理》 《 Spring Cloud Config 分布式配置中心使用教程》 servlet 3.0 异步特性可用于配置中心的客户端 《servlet3.0 新特性——异步处理》 API 网关 主要职责:请求转发、安全认证、协议转换、容灾。 《API网关那些儿》 《谈API网关的背景、架构以及落地方案》 《使用Zuul构建API Gateway》 《HTTP API网关选择之一Kong介绍》 网络 协议 OSI 七层协议 《OSI七层协议模型、TCP/IP四层模型学习笔记》 TCP/IP 《深入浅出 TCP/IP 协议》 《TCP协议中的三次握手和四次挥手》 HTTP 《http协议详解(超详细)》 HTTP2.0 《HTTP 2.0 原理详细分析》 《HTTP2.0的基本单位为二进制帧》 利用二进制帧负责传输。 多路复用。 HTTPS 《https原理通俗了解》 使用非对称加密协商加密算法 使用对称加密方式传输数据 使用第三方机构签发的证书,来加密公钥,用于公钥的安全传输、防止被中间人串改。 《八大免费SSL证书-给你的网站免费添加Https安全加密》 网络模型 《web优化必须了解的原理之I/o的五种模型和web的三种工作模式》 五种I/O模型:阻塞I/O,非阻塞I/O,I/O复用、事件(信号)驱动I/O、异步I/O,前四种I/O属于同步操作,I/O的第一阶段不同、第二阶段相同,最后的一种则属于异步操作。 三种 Web Server 工作方式:Prefork(多进程)、Worker方式(线程方式)、Event方式。 《select、poll、epoll之间的区别总结》 select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。 select 有打开文件描述符数量限制,默认1024(2048 for x64),100万并发,就要用1000个进程、切换开销大;poll采用链表结构,没有数量限制。 select,poll “醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,通过回调机制节省大量CPU时间;select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,而epoll只要一次拷贝。 poll会随着并发增加,性能逐渐下降,epoll采用红黑树结构,性能稳定,不会随着连接数增加而降低。 《select,poll,epoll比较 》 在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。 《深入理解Java NIO》 NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务 《BIO与NIO、AIO的区别》 《两种高效的服务器设计模型:Reactor和Proactor模型》 Epoll 《epoll使用详解(精髓)》 Java NIO 《深入理解Java NIO》 《Java NIO编写Socket服务器的一个例子》 kqueue 《kqueue用法简介》 连接和短连接 《TCP/IP系列——长连接与短连接的区别》 框架 《Netty原理剖析》 Reactor 模式介绍。 Netty 是 Reactor 模式的一种实现。 零拷贝(Zero-copy) 《对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解》 多个物理分离的buffer,通过逻辑上合并成为一个,从而避免了数据在内存之间的拷贝。 序列化(二进制协议) Hessian 《Hessian原理分析》 Binary-RPC;不仅仅是序列化 Protobuf 《Protobuf协议的Java应用例子》 Goolge出品、占用空间和效率完胜其他序列化类库,如Hessian;需要编写 .proto 文件。 《Protocol Buffers序列化协议及应用》 关于协议的解释;缺点:可读性差; 《简单的使用 protobuf 和 protostuff》 protostuff 的好处是不用写 .proto 文件,Java 对象直接就可以序列化。 数据库 基础理论 数据库设计的三大范式 《数据库的三大范式以及五大约束》 第一范式:数据表中的每一列(每个字段)必须是不可拆分的最小单元,也就是确保每一列的原子性; 第二范式(2NF):满足1NF后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情; 第三范式:必须先满足第二范式(2NF),要求:表中的每一列只与主键直接相关而不是间接相关,(表中的每一列只能依赖于主键); MySQL 原理 《MySQL的InnoDB索引原理详解》 《MySQL存储引擎--MyISAM与InnoDB区别》 两种类型最主要的差别就是Innodb 支持事务处理与外键和行级锁 《myisam和innodb索引实现的不同》 InnoDB 《一篇文章带你读懂Mysql和InnoDB》 优化 《MySQL36条军规》 《MYSQL性能优化的最佳20+条经验》 《SQL优化之道》 《mysql数据库死锁的产生原因及解决办法》 《导致索引失效的可能情况》 《 MYSQL分页limit速度太慢优化方法》 原则上就是缩小扫描范围。 索引 聚集索引, 非聚集索引 《MySQL 聚集索引/非聚集索引简述》 《MyISAM和InnoDB的索引实现》 MyISAM 是非聚集,InnoDB 是聚集 复合索引 《复合索引的优点和注意事项》 自适应哈希索引(AHI) 《InnoDB存储引擎——自适应哈希索引》 explain 《MySQL 性能优化神器 Explain 使用分析》 NoSQL MongoDB MongoDB 教程 《Mongodb相对于关系型数据库的优缺点》 优点:弱一致性(最终一致),更能保证用户的访问速度;内置GridFS,支持大容量的存储;Schema-less 数据库,不用预先定义结构;内置Sharding;相比于其他NoSQL,第三方支持丰富;性能优越; 缺点:mongodb不支持事务操作;mongodb占用空间过大;MongoDB没有如MySQL那样成熟的维护工具,这对于开发和IT运营都是个值得注意的地方; Hbase 《简明 HBase 入门教程(开篇)》 《深入学习HBase架构原理》 《传统的行存储和(HBase)列存储的区别》 《Hbase与传统数据库的区别》 空数据不存储,节省空间,且适用于并发。 《HBase Rowkey设计》 rowkey 按照字典顺序排列,便于批量扫描。 通过散列可以避免热点。 搜索引擎 搜索引擎原理 《倒排索引--搜索引擎入门》 Lucene 《Lucene入门简介》 Elasticsearch 《Elasticsearch学习,请先看这一篇!》 《Elasticsearch索引原理》 Solr 《 Apache Solr入门教程》 《elasticsearch与solr比较》 sphinx 《Sphinx 的介绍和原理探索》 性能 性能优化方法论 《15天的性能优化工作,5方面的调优经验》 代码层面、业务层面、数据库层面、服务器层面、前端优化。 《系统性能优化的几个方面》 容量评估 《联网性能与容量评估的方法论和典型案例》 《互联网架构,如何进行容量设计?》 评估总访问量、评估平均访问量QPS、评估高峰QPS、评估系统、单机极限QPS CDN 网络 《CDN加速原理》 《国内有哪些比较好的 CDN?》 连接池 《主流Java数据库连接池比较与开发配置实战》 性能调优 《九大Java性能调试工具,必备至少一款》 大数据 流式计算 Storm 官方网站 《最详细的Storm入门教程》 Flink 《Flink之一 Flink基本原理介绍》 Kafka Stream 《Kafka Stream调研:一种轻量级流计算模式》 应用场景 例如: 广告相关实时统计; 推荐系统用户画像标签实时更新; 线上服务健康状况实时监测; 实时榜单; 实时数据统计。 Hadoop 《用通俗易懂的话说下hadoop是什么,能做什么》 《史上最详细的Hadoop环境搭建》 HDFS 《【Hadoop学习】HDFS基本原理》 MapReduce 《用通俗易懂的大白话讲解Map/Reduce原理》 《 简单的map-reduce的java例子》 Yarn 《初步掌握Yarn的架构及原理》 Spark 《Spark(一): 基本架构及原理》 安全 web 安全 XSS 《xss攻击原理与解决方法》 CSRF 《CSRF原理及防范》 SQL 注入 《SQL注入》 Hash Dos 《邪恶的JAVA HASH DOS攻击》 利用JsonObjet 上传大Json,JsonObject 底层使用HashMap;不同的数据产生相同的hash值,使得构建Hash速度变慢,耗尽CPU。 《一种高级的DoS攻击-Hash碰撞攻击》 《关于Hash Collision DoS漏洞:解析与解决方案》 脚本注入 《上传文件漏洞原理及防范》 漏洞扫描工具 《DVWA》 W3af OpenVAS详解 验证码 《验证码原理分析及实现》 《详解滑动验证码的实现原理》 滑动验证码是根据人在滑动滑块的响应时间,拖拽速度,时间,位置,轨迹,重试次数等来评估风险。 《淘宝滑动验证码研究》 DDoS 防范 《学习手册:DDoS的攻击方式及防御手段》 《免费DDoS攻击测试工具大合集》 用户隐私信息保护 用户密码非明文保存,加动态slat。 身份证号,手机号如果要显示,用 “*” 替代部分字符。 联系方式在的显示与否由用户自己控制。 TODO 《个人隐私包括哪些》 《在互联网上,隐私的范围包括哪些?》 《用户密码保存》 序列化漏洞 《Lib之过?Java反序列化漏洞通用利用分析》 加密解密 对称加密 《常见对称加密算法》 DES、3DES、Blowfish、AES DES 采用 56位秘钥,Blowfish 采用1到448位变长秘钥,AES 128,192和256位长度的秘钥。 DES 秘钥太短(只有56位)算法目前已经被 AES 取代,并且 AES 有硬件加速,性能很好。 哈希算法 《常用的哈希算法》 MD5 和 SHA-1 已经不再安全,已被弃用。 目前 SHA-256 是比较安全的。 《基于Hash摘要签名的公网URL签名验证设计方案》 非对称加密 《常见非对称加密算法》 RSA、DSA、ECDSA(螺旋曲线加密算法) 和 RSA 不同的是 DSA 仅能用于数字签名,不能进行数据加密解密,其安全性和RSA相当,但其性能要比RSA快。 256位的ECC秘钥的安全性等同于3072位的RSA秘钥。 《区块链的加密技术》 服务器安全 《Linux强化论:15步打造一个安全的Linux服务器》 数据安全 数据备份 TODO 网络隔离 内外网分离 TODO 登录跳板机 在内外环境中通过跳板机登录到线上主机。 《搭建简易堡垒机》 授权、认证 RBAC 《基于组织角色的权限设计》 《权限系统与RBAC模型概述》 《Spring整合Shiro做权限控制模块详细案例分析》 OAuth2.0 《理解OAuth 2.0》 双因素认证(2FA) 2FA - Two-factor authentication,用于加强登录验证 常用做法是 登录密码 + 手机验证码(或者令牌Key,类似于与网银的 USB key) 【《双因素认证(2FA)教程》】(http://www.ruanyifeng.com/blog/2017/11/2fa-tutorial.html) 单点登录(SSO) 《单点登录原理与简单实现》 CAS单点登录框架 常用开源框架 开源协议 《开源协议的选择》 如何选择一个开源软件协议 日志框架 Log4j、Log4j2 《log4j 详细讲解》 《log4j2 实际使用详解》 《Log4j1,Logback以及Log4j2性能测试对比》 Log4J 异步日志性能优异。 Logback 《最全LogBack 详解、含java案例和配置说明》 ORM 《ORM框架使用优缺点》 主要目的是为了提高开发效率。 MyBatis: 《mybatis缓存机制详解》 一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效 二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的;使用 LRU 机制清理缓存,通过 cacheEnabled 参数开启。 《MyBatis学习之代码生成器Generator》 网络框架 TODO Web 框架 Spring 家族 Spring Spring 简明教程 Spring Boot 官方网站 《Spring Boot基础教程》 Spring Cloud Spring Boot 中文索引站 Spring Cloud 中文文档 《Spring Cloud基础教程》 工具框架 《Apache Commons 工具类介绍及简单使用》 《Google guava 中文教程》 分布式设计 扩展性设计 《架构师不可不知的十大可扩展架构》 总结下来,通用的套路就是分布、缓存及异步处理。 《可扩展性设计之数据切分》 水平切分+垂直切分 利用中间件进行分片如,MySQL Proxy。 利用分片策略进行切分,如按照ID取模。 《说说如何实现可扩展性的大型网站架构》 分布式服务+消息队列。 《大型网站技术架构(七)--网站的可扩展性架构》 稳定性 & 高可用 《系统设计:关于高可用系统的一些技术方案》 可扩展:水平扩展、垂直扩展。 通过冗余部署,避免单点故障。 隔离:避免单一业务占用全部资源。避免业务之间的相互影响 2. 机房隔离避免单点故障。 解耦:降低维护成本,降低耦合风险。减少依赖,减少相互间的影响。 限流:滑动窗口计数法、漏桶算法、令牌桶算法等算法。遇到突发流量时,保证系统稳定。 降级:紧急情况下释放非核心功能的资源。牺牲非核心业务,保证核心业务的高可用。 熔断:异常情况超出阈值进入熔断状态,快速失败。减少不稳定的外部依赖对核心服务的影响。 自动化测试:通过完善的测试,减少发布引起的故障。 灰度发布:灰度发布是速度与安全性作为妥协,能够有效减少发布故障。 《关于高可用的系统》 设计原则:数据不丢(持久化);服务高可用(服务副本);绝对的100%高可用很难,目标是做到尽可能多的9,如99.999%(全年累计只有5分钟)。 硬件负载均衡 《转!!负载均衡器技术Nginx和F5的优缺点对比》 主要是和F5对比。 《软/硬件负载均衡产品 你知多少?》 软件负载均衡 《几种负载均衡算法》 轮寻、权重、负载、最少连接、QoS 《DNS负载均衡》 配置简单,更新速度慢。 《Nginx负载均衡》 简单轻量、学习成本低;主要适用于web应用。 《借助LVS+Keepalived实现负载均衡 》 配置比较负载、只支持到4层,性能较高。 《HAProxy用法详解 全网最详细中文文档》 支持到七层(比如HTTP)、功能比较全面,性能也不错。 《Haproxy+Keepalived+MySQL实现读均衡负载》 主要是用户读请求的负载均衡。 《rabbitmq+haproxy+keepalived实现高可用集群搭建》 限流 《谈谈高并发系统的限流》 计数器:通过滑动窗口计数器,控制单位时间内的请求次数,简单粗暴。 漏桶算法:固定容量的漏桶,漏桶满了就丢弃请求,比较常用。 令牌桶算法:固定容量的令牌桶,按照一定速率添加令牌,处理请求前需要拿到令牌,拿不到令牌则丢弃请求,或进入丢队列,可以通过控制添加令牌的速率,来控制整体速度。Guava 中的 RateLimiter 是令牌桶的实现。 Nginx 限流:通过 limit_req 等模块限制并发连接数。 应用层容灾 《防雪崩利器:熔断器 Hystrix 的原理与使用》 雪崩效应原因:硬件故障、硬件故障、程序Bug、重试加大流量、用户大量请求。 雪崩的对策:限流、改进缓存模式(缓存预加载、同步调用改异步)、自动扩容、降级。 Hystrix设计原则: 资源隔离:Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩。 熔断开关:服务的健康状况 = 请求失败数 / 请求总数,通过阈值设定和滑动窗口控制开关。 命令模式:通过继承 HystrixCommand 来包装服务调用逻辑。 《缓存穿透,缓存击穿,缓存雪崩解决方案分析》 《缓存击穿、失效以及热点key问题》 主要策略:失效瞬间:单机使用锁;使用分布式锁;不过期; 热点数据:热点数据单独存储;使用本地缓存;分成多个子key; 跨机房容灾 《“异地多活”多机房部署经验谈》 通过自研中间件进行数据同步。 《异地多活(异地双活)实践经验》 注意延迟问题,多次跨机房调用会将延时放大数倍。 建房间专线很大概率会出现问题,做好运维和程序层面的容错。 不能依赖于程序端数据双写,要有自动同步方案。 数据永不在高延迟和较差网络质量下,考虑同步质量问题。 核心业务和次要业务分而治之,甚至只考虑核心业务。 异地多活监控部署、测试也要跟上。 业务允许的情况下考虑用户分区,尤其是游戏、邮箱业务。 控制跨机房消息体大小,越小越好。 考虑使用docker容器虚拟化技术,提高动态调度能力。 容灾技术及建设经验介绍 容灾演练流程 《依赖治理、灰度发布、故障演练,阿里电商故障演练系统的设计与实战经验》 常见故障画像 案例:预案有效性、预案有效性、故障复现、架构容灾测试、参数调优、参数调优、故障突袭、联合演练。 平滑启动 平滑重启应用思路 1.端流量(如vip层)、2. flush 数据(如果有)、3, 重启应用 《JVM安全退出(如何优雅的关闭java服务)》 推荐推出方式:System.exit,Kill SIGTERM;不推荐 kill-9;用 Runtime.addShutdownHook 注册钩子。 《常见Java应用如何优雅关闭》 Java、Srping、Dubbo 优雅关闭方式。 数据库扩展 读写分离模式 《Mysql主从方案的实现》 《搭建MySQL主从复制经典架构》 《Haproxy+多台MySQL从服务器(Slave) 实现负载均衡》 《DRBD+Heartbeat+Mysql高可用读写分离架构》 DRDB 进行磁盘复制,避免单点问题。 《MySQL Cluster 方式》 分片模式 《分库分表需要考虑的问题及方案》 中间件: 轻量级:sharding-jdbc、TSharding;重量级:Atlas、MyCAT、Vitess等。 问题:事务、Join、迁移、扩容、ID、分页等。 事务补偿:对数据进行对帐检查;基于日志进行比对;定期同标准数据来源进行同步等。 分库策略:数值范围;取模;日期等。 分库数量:通常 MySQL 单库 5千万条、Oracle 单库一亿条需要分库。 《MySql分表和表分区详解》 分区:是MySQL内部机制,对客户端透明,数据存储在不同文件中,表面上看是同一个表。 分表:物理上创建不同的表、客户端需要管理分表路由。 服务治理 服务注册与发现 《永不失联!如何实现微服务架构中的服务发现?》 客户端服务发现模式:客户端直接查询注册表,同时自己负责负载均衡。Eureka 采用这种方式。 服务器端服务发现模式:客户端通过负载均衡查询服务实例。 《SpringCloud服务注册中心比较:Consul vs Zookeeper vs Etcd vs Eureka》 CAP支持:Consul(CA)、zookeeper(cp)、etcd(cp) 、euerka(ap) 作者认为目前 Consul 对 Spring cloud 的支持比较好。 《基于Zookeeper的服务注册与发现》 优点:API简单、Pinterest,Airbnb 在用、多语言、通过watcher机制来实现配置PUSH,能快速响应配置变化。 服务路由控制 《分布式服务框架学习笔记4 服务路由》 原则:透明化路由 负载均衡策略:随机、轮询、服务调用延迟、一致性哈希、粘滞连接 本地路由有限策略:injvm(优先调用jvm内部的服务),innative(优先使用相同物理机的服务),原则上找距离最近的服务。 配置方式:统一注册表;本地配置;动态下发。 分布式一致 CAP 与 BASE 理论 《从分布式一致性谈到CAP理论、BASE理论》 一致性分类:强一致(立即一致);弱一致(可在单位时间内实现一致,比如秒级);最终一致(弱一致的一种,一定时间内最终一致) CAP:一致性、可用性、分区容错性(网络故障引起) BASE:Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性) BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 分布式锁 《分布式锁的几种实现方式》 基于数据库的分布式锁:优点:操作简单、容易理解。缺点:存在单点问题、数据库性能够开销较大、不可重入; 基于缓存的分布式锁:优点:非阻塞、性能好。缺点:操作不好容易造成锁无法释放的情况。 Zookeeper 分布式锁:通过有序临时节点实现锁机制,自己对应的节点需要最小,则被认为是获得了锁。优点:集群可以透明解决单点问题,避免锁不被释放问题,同时锁可以重入。缺点:性能不如缓存方式,吞吐量会随着zk集群规模变大而下降。 《基于Zookeeper的分布式锁》 清楚的原理描述 + Java 代码示例。 《jedisLock—redis分布式锁实现》 基于 setnx(set if ont exists),有则返回false,否则返回true。并支持过期时间。 《Memcached 和 Redis 分布式锁方案》 利用 memcached 的 add(有别于set)操作,当key存在时,返回false。 分布式一致性算法 PAXOS 《分布式系列文章——Paxos算法原理与推导》 《Paxos-->Fast Paxos-->Zookeeper分析》 《【分布式】Zookeeper与Paxos》 Zab 《Zab:Zookeeper 中的分布式一致性协议介绍》 Raft 《Raft 为什么是更易理解的分布式一致性算法》 三种角色:Leader(领袖)、Follower(群众)、Candidate(候选人) 通过随机等待的方式发出投票,得票多的获胜。 Gossip 《Gossip算法》 两阶段提交、多阶段提交 《关于分布式事务、两阶段提交协议、三阶提交协议》 幂等 《分布式系统---幂等性设计》 幂等特性的作用:该资源具备幂等性,请求方无需担心重复调用会产生错误。 常见保证幂等的手段:MVCC(类似于乐观锁)、去重表(唯一索引)、悲观锁、一次性token、序列号方式。 分布式一致方案 《分布式系统事务一致性解决方案》 《保证分布式系统数据一致性的6种方案》 分布式 Leader 节点选举 《利用zookeeper实现分布式leader节点选举》 TCC(Try/Confirm/Cancel) 柔性事务 《传统事务与柔性事务》 基于BASE理论:基本可用、柔性状态、最终一致。 解决方案:记录日志+补偿(正向补充或者回滚)、消息重试(要求程序要幂等);“无锁设计”、采用乐观锁机制。 分布式文件系统 说说分布式文件存储系统-基本架构 ? 《各种分布式文件系统的比较》 ? HDFS:大批量数据读写,用于高吞吐量的场景,不适合小文件。 FastDFS:轻量级、适合小文件。 唯一ID 生成 全局唯一ID 《高并发分布式系统中生成全局唯一Id汇总》 Twitter 方案(Snowflake 算法):41位时间戳+10位机器标识(比如IP,服务器名称等)+12位序列号(本地计数器) Flicker 方案:MySQL自增ID + "REPLACE INTO XXX:SELECT LAST_INSERT_ID();" UUID:缺点,无序,字符串过长,占用空间,影响检索性能。 MongoDB 方案:利用 ObjectId。缺点:不能自增。 《TDDL 在分布式下的SEQUENCE原理》 在数据库中创建 sequence 表,用于记录,当前已被占用的id最大值。 每台客户端主机取一个id区间(比如 1000~2000)缓存在本地,并更新 sequence 表中的id最大值记录。 客户端主机之间取不同的id区间,用完再取,使用乐观锁机制控制并发。 一致性Hash算法 《一致性哈希算法》 设计思想 & 开发模式 DDD(Domain-driven Design - 领域驱动设计) 《浅谈我对DDD领域驱动设计的理解》 概念:DDD 主要对传统软件开发流程(分析-设计-编码)中各阶段的割裂问题而提出,避免由于一开始分析不明或在软件开发过程中的信息流转不一致而造成软件无法交付(和需求方设想不一致)的问题。DDD 强调一切以领域(Domain)为中心,强调领域专家(Domain Expert)的作用,强调先定义好领域模型之后在进行开发,并且领域模型可以指导开发(所谓的驱动)。 过程:理解领域、拆分领域、细化领域,模型的准确性取决于模型的理解深度。 设计:DDD 中提出了建模工具,比如聚合、实体、值对象、工厂、仓储、领域服务、领域事件来帮助领域建模。 《领域驱动设计的基础知识总结》 领域(Doamin)本质上就是问题域,比如一个电商系统,一个论坛系统等。 界限上下文(Bounded Context):阐述子域之间的关系,可以简单理解成一个子系统或组件模块。 领域模型(Domain Model):DDD的核心是建立(用通用描述语言、工具—领域通用语言)正确的领域模型;反应业务需求的本质,包括实体和过程;其贯穿软件分析、设计、开发 的整个过程;常用表达领域模型的方式:图、代码或文字; 领域通用语言:领域专家、开发设计人员都能立即的语言或工具。 经典分层架构:用户界面/展示层、应用层、领域层、基础设施层,是四层架构模式。 使用的模式: 关联尽量少,尽量单项,尽量降低整体复杂度。 实体(Entity):领域中的唯一标示,一个实体的属性尽量少,少则清晰。 值对象(Value Object):没有唯一标识,且属性值不可变,小二简单的对象,比如Date。 领域服务(Domain Service): 协调多个领域对象,只有方法没有状态(不存数据);可以分为应用层服务,领域层服务、基础层服务。 聚合及聚合根(Aggregate,Aggregate Root):聚合定义了一组具有内聚关系的相关对象的集合;聚合根是对聚合引用的唯一元素;当修改一个聚合时,必须在事务级别;大部分领域模型中,有70%的聚合通常只有一个实体,30%只有2~3个实体;如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,那么我们可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互; 工厂(Factory):类似于设计模式中的工厂模式。 仓储(Repository):持久化到DB,管理对象,且只对聚合设计仓储。 《领域驱动设计(DDD)实现之路》 聚合:比如一辆汽车(Car)包含了引擎(Engine)、车轮(Wheel)和油箱(Tank)等组件,缺一不可。 《领域驱动设计系列(2)浅析VO、DTO、DO、PO的概念、区别和用处》 命令查询职责分离(CQRS) CQRS — Command Query Responsibility Seperation 《领域驱动设计系列 (六):CQRS》 核心思想:读写分离(查询和更新在不同的方法中),不同的流程只是不同的设计方式,CQ代码分离,分布式环境中会有明显体现(有冗余数据的情况下),目的是为了高性能。 《DDD CQRS架构和传统架构的优缺点比较》 最终一致的设计理念;依赖于高可用消息中间件。 《CQRS架构简介》 一个实现 CQRS 的抽象案例。 《深度长文:我对CQRS/EventSourcing架构的思考》 CQRS 模式分析 + 12306 抢票案例 贫血,充血模型 《贫血,充血模型的解释以及一些经验》 失血模型:老子和儿子分别定义,相互不知道,二者实体定义中完全没有业务逻辑,通过外部Service进行关联。 贫血模型:老子知道儿子,儿子也知道老子;部分业务逻辑放到实体中;优点:各层单项依赖,结构清楚,易于维护;缺点:不符合OO思想,相比于充血模式,Service层较为厚重; 充血模型:和贫血模型类似,区别在于如何划分业务逻辑。优点:Service层比较薄,只充当Facade的角色,不和DAO打交道、复合OO思想;缺点:非单项依赖,DO和DAO之间双向依赖、和Service层的逻辑划分容易造成混乱。 肿胀模式:是一种极端情况,取消Service层、全部业务逻辑放在DO中;优点:符合OO思想、简化了分层;缺点:暴露信息过多、很多非DO逻辑也会强行并入DO。这种模式应该避免。 作者主张使用贫血模式。 Actor 模式 TODO 响应式编程 Reactor TODO RxJava TODO Vert.x TODO DODAF2.0 《DODAF2.0方法论》 《DODAF2.0之能力视角如何落地》 Serverless TODO Service Mesh TODO 《什么是Service Mesh?》 项目管理 架构评审 《架构设计之如何评审架构设计说明书》 《人人都是架构师:非功能性需求》 重构 《架构之重构的12条军规》 代码规范 TODO 代码 Review 制度还是制度! 另外,每个公司需要根据自己的需求和目标制定自己的 check list 《为什么你做不好 Code Review?》 代码 review 做的好,在于制度建设。 《从零开始Code Review》 《Code Review Checklist》 《Java Code Review Checklist》 《如何用 gitlab 做 code review》 RUP 《运用RUP 4+1视图方法进行软件架构设计》 看板管理 《说说看板在项目中的应用》 SCRUM SCRUM - 争球 3个角色:Product Owner(PO) 产品负责人;Scrum Master(SM),推动Scrum执行;Team 开发团队。 3个工件:Product Backlog 产品TODOLIST,含优先级;Sprint Backlog 功能开发 TODO LIST;燃尽图; 五个价值观:专注、勇气、公开、承诺、尊重。 《敏捷项目管理流程-Scrum框架最全总结!》 《敏捷其实很简单3---敏捷方法之scrum》 敏捷开发 TODO 极限编程(XP) XP - eXtreme Programming 《主流敏捷开发方法:极限编程XP》 是一种指导开发人员的方法论。 4大价值: 沟通:鼓励口头沟通,提高效率。 简单:够用就好。 反馈:及时反馈、通知相关人。 勇气:提倡拥抱变化,敢于重构。 5个原则:快速反馈、简单性假设、逐步修改、提倡更改(小步快跑)、优质工作(保证质量的前提下保证小步快跑)。 5个工作:阶段性冲刺;冲刺计划会议;每日站立会议;冲刺后review;回顾会议。 结对编程 边写码,边review。能够增强代码质量、减少bug。 《结对编程》 FMEA管理模式 TODO 通用业务术语 TODO 技术趋势 TODO 政策、法规 TODO 法律 严格遵守刑法253法条 我国刑法第253条之一规定: 国家机关或者金融、电信、交通、教育、医疗等单位的工作人员,违反国家规定,将本单位在履行职责或者提供服务过程中获得的公民个人信息,出售或者非法提供给他人,情节严重的,处3年以下有期徒刑或者拘役,并处或者单处罚金。 窃取或者以其他方法非法获取上述信息,情节严重的,依照前款的规定处罚。 单位犯前两款罪的,对单位判处罚金,并对其直接负责的主管人员和其他直接责任人员,依照各该款的规定处罚。 最高人民法院、最高人民检察院关于执行《中华人民共和国刑法》确定罪名的补充规定(四)规定:触犯刑法第253条之一第1款之规定,构成“出售、非法提供公民个人信息罪”;触犯刑法第253条之一第2款之规定,构成“非法获取公民个人信息罪” 《非法获取公民个人信息罪》 架构师素质 《架构师画像》 业务理解和抽象能力 NB的代码能力 全面:1. 在面对业务问题上,架构师脑海里是否会浮现出多种技术方案;2. 在做系统设计时是否考虑到了足够多的方方面面;3. 在做系统设计时是否考虑到了足够多的方方面面; 全局:是否考虑到了对上下游的系统的影响。 权衡:权衡投入产出比;优先级和节奏控制; 《关于架构优化和设计,架构师必须知道的事情》 要去考虑的细节:模块化、轻耦合、无共享架构;减少各个组件之前的依懒、注意服务之间依懒所有造成的链式失败及影响等。 基础设施、配置、测试、开发、运维综合考虑。 考虑人、团队、和组织的影响。 《如何才能真正的提高自己,成为一名出色的架构师?》 《架构师的必备素质和成长途径》 素质:业务理解、技术广度、技术深度、丰富经验、沟通能力、动手能力、美学素养。 成长路径:2年积累知识、4年积累技能和组内影响力、7年积累部门内影响力、7年以上积累跨部门影响力。 《架构设计师—你在哪层楼?》 第一层的架构师看到的只是产品本身 第二层的架构师不仅看到自己的产品,还看到了整体的方案 第三层的架构师看到的是商业价值 团队管理 TODO 招聘 资讯 行业资讯 36kr Techweb 公众号列表 TODO 博客 团队博客 阿里中间件博客 美团点评技术团队博客 个人博客 阮一峰的网络日志 酷壳 - COOLSHELL-陈皓 hellojava-阿里毕玄 Cm's Blog 程序猿DD-翟永超-《Spring Cloud微服务实战》作者 综合门户、社区 国内: CSDN 老牌技术社区、不必解释。 51cto.com ITeye 偏 Java 方向 博客园 ChinaUnix 偏 Linux 方向 开源中国社区 深度开源 伯乐在线 涵盖 IT职场、Web前端、后端、移动端、数据库等方面内容,偏技术端。 ITPUB 腾讯云— 云+社区 阿里云— 云栖社区 IBM DeveloperWorks 开发者头条 LinkedKeeper 国外: DZone Reddit 问答、讨论类社区 segmentfault 问答+专栏 知乎 stackoverflow 行业数据分析 艾瑞网 QUEST MOBILE 国家数据 专项网站 测试: 领测国际 测试窝 TesterHome 运维: 运维派 Abcdocker Java: ImportNew 专注于 Java 技术分享 HowToDoInJava 英文博客 安全 红黑联盟 FreeBuf 大数据 中国大数据 其他专题网站: DockerInfo 专注于 Docker 应用及咨询、教程的网站。 Linux公社 Linux 主题社区 其他类 程序员技能图谱 推荐参考书 在线电子书 《深入理解Spring Cloud与微服务构建》 《阿里技术参考图册-研发篇》 《阿里技术参考图册-算法篇》 《2018美团点评技术年货(合辑)》70M InfoQ《架构师》月刊 《架构师之路》 纸质书 开发方面 《阿里巴巴Java开发手册》京东 淘宝 架构方面 《软件架构师的12项修炼:技术技能篇》京东 淘宝 《架构之美》京东 淘宝 《分布式服务架构》京东 淘宝 《聊聊架构》 京东 淘宝 《云原生应用架构实践》京东 淘宝 《亿级流量网站架构核心技术》京东 淘宝 《淘宝技术这十年》京东 淘宝 《企业IT架构转型之道-中台战略思想与架构实战》 京东 淘宝 技术管理方面 《CTO说》京东 淘宝 《技术管理之巅》京东 淘宝 《网易一千零一夜:互联网产品项目管理实战》京东 淘宝 基础理论 《数学之美》京东 淘宝 《编程珠玑》京东 淘宝 工具方面 TODO 大数据方面 技术资源 开源资源 github Apache 软件基金会 手册、文档、教程 国内: W3Cschool Runoob.com HTML 、 CSS、XML、Java、Python、PHP、设计模式等入门手册。 Love2.io 很多很多中文在线电子书,是一个全新的开源技术文档分享平台。 gitbook.cn 付费电子书。 ApacheCN AI、大数据方面系列中文文档。 国外: Quick Code 免费在线技术教程。 gitbook.com 有部分中文电子书。 Cheatography Cheat Sheets 大全,单页文档网站。 在线课堂 学徒无忧 极客时间 segmentfault 斯达克学院 牛客网 极客学院 51CTO学院 会议、活动 QCon ArchSummit GITC全球互联网技术大会 活动发布平台: 活动行 常用APP 极客时间 得到 找工作 Boss直聘 拉勾网 猎聘 100Offer 工具 极客搜索 技术文章搜索引擎。 代码托管 Coding 码云 文件服务 七牛 又拍云 综合云服务商 阿里云 腾讯云 百度云 新浪云 金山云 亚马逊云(AWS) 谷歌云 微软云 VPS Linode
文章
算法  ·  Java  ·  大数据  ·  测试技术  ·  数据库  ·  Spring
2018-05-07
优秀后端架构师必会知识:史上最全MySQL大表优化方案总结
1、引言 MySQL作为开源技术的代表作之一,是互联网得以广泛流行的重要基础技术之一。 国外 GitHub、Airbnb、Yelp、Coursera 均在使用 MySQL 数据库,国内阿里巴巴、去哪儿网、腾讯、魅族、京东等等的部分关键业务同样使用了 MySQL 数据库。同时,MySQL 也是众多数据库排行榜单的第一名,丛多国内一线互联网企业都在用的开源数据库。 MySQL在互联网项目中如此流行,众多的开发者们在各种应用场景下总结了许多MySQL的最佳实践。本文将总结和分享当MySQL单表记录数过大时,增删改查性能急剧下降问题的优化思路,这也是资深后端架构师、程序员所必备的知识内容之一,希望本文对你有用。 (本文同步发布于:http://www.52im.net/thread-2157-1-1.html) 2、关于MySQL 2.1 MySQL之父 ▲ MySQL之父:Ulf Michael “Monty” Widenius Michael “Monty” Widenius, 1962年3月3日出生于芬兰赫尔辛基。开源 MySQL数据库的创始成员、MySQL AB公司的首席技术官、MySQL数据库第一行代码的作者、MySQL数据库命名人、MariaDB创始人兼首席技术官;独自完成撰写MySQL数据库服务器端95%的代码。 Monty是MySQL第一行代码的作者,后来与两位好友一起成立了MySQL AB,开始正式商业化运作MySQL,出任CTO,一直到MySQL AB被卖给Sun。之后Monty没有加入Sun,而是离职创立了Monty Program AB,接过MySQL的代码继续开发新的分支——MariaDB,自己担任CEO。 在2014年,Monty Program AB与SkySQL AB合并,成立了MariaDB Corporation,开始商业化运作MariaDB,Monty继续担任新公司的CTO。同时他还兼任MariaDB基金会的CTO。 关于MariaDB、MySQL、MaxDB名字的由来: Monty有一个女儿,名叫My,因此他将自己开发的数据库命名为MySQL。Monty还有一个儿子,名为Max,因此在2003年,SAP公司与MySQL公司建立合作伙伴关系后,Monty又将与SAP合作开发的数据库命名为MaxDB。而现在的MariaDB中的Maria是Monty小孙女的名字。 2.2 MySQL历史 MySQL的海豚标志的名字叫“sakila”,它是由MySQL AB的创始人从用户在“海豚命名”的竞赛中建议的大量的名字表中选出的。获胜的名字是由来自非洲斯威士兰的开源软件开发者Ambrose Twebaze提供。根据Ambrose所说,Sakila来自一种叫SiSwati的斯威士兰方言,也是在Ambrose的家乡乌干达附近的坦桑尼亚的Arusha的一个小镇的名字。 MySQL的历史可以追溯到1979年。当时Allan Larsson和Michael Widenius(Monty)开了一家自己的咨询公司,取名TcX,名字的由来已无从考证。有道是"前世尽付真情,今生亦现福缘积厚"。那年一个夜黑风高的晚上,Michael基于BASIC语言写出了他的第一款数据库报表工具UNIREG。 有当年的天气记录为证,Michael写完该工具时极光异常明亮,炫彩无比。大凡重大事情的发生,后来的著述人都会记录有一些类似的怪现象。比如刮风、下雨、冒仙气什么的,还有天上星星异常闪烁等等,反正就是说明这种事情很不简单。 最初的UNIREG是运行在瑞典人制造的ABC800计算机上的。ABC800的内存只有32KB,CPU是频率只有4MHz的Z80。在1983年Monty遇到了David Axmark,两人相见恨晚,开始合作运营TcX,Monty负责技术,David搞管理。后来TcX将UNIREG移植到其他更加强大的硬件平台,主要是Sun的平台。 ▲ ABC800计算机 1995年5月23日,MySQL的第一个内部版本发行了,并在第二年对外公布了MySQL官方正式发行版(3.11.1)。有趣的是,第一个MySQL正式版恰巧只能运行在Sun Solaris上,仿佛昭示了它日后被Sun收购的命运。 在接下来的两年中,MySQL被移植到不同的平台,同时加入了不少新的特性。到1998时,MySQL能够运行在10多种操作系统之上,其中包括应用非常广泛的 FreeBSD、Linux、Windows 95和Windows NT等。很快MySQL 3.22也发布了,但它仍然存在很多问题--如不支持事务操作、子查询、外键、存储过程和视图等功能。正因为这些缺陷,当时许多Oracle和SQL Server的用户对MySQL根本不屑一顾。 大概在1999的冬天,下了很大一场雪。然后独立的商业公司MySQL AB就在瑞典的中部城市Uppsala成立了。并于同年发布了包含事务型存储引擎BDB的MySQL 3.23。在集成BDB存储引擎的过程中,MySQL开发团队得到了很好的锻炼,为后来能将InnoDB整合以及开发开放插件式的存储引擎架构打下了坚实的基础。 MySQL从诞生之初就提供了双重的授权标准:个人使用是免费的,如果用于商业网站搭建或者Windows平台下就必须购买商业许可证。在2000年的时候MySQL做了一个重大的决定,改换成了GPL许可模式,也就是说商业用户也无需再购买许可证,但必须把他们的源码公开。虽然MySQL AB因此在收入上遭受了巨大的打击,损失了将近80%的收入,但他们依然坚持了GPL许可模式。 与此同时,芬兰公司Heikki开始接触MySQL AB,讨论将Heikki的存储引擎InnoDB整合到MySQL数据库中的可行性。双方的合作非常顺利,并于2001年推出MySQL 4.0 Alpha版本。经过两年的公开测试和应用,到了2003年,包含InnoDB的MySQL已经变得非常稳定了。随即在同一年,MySQL推出4.1版,第一次使得MySQL支持子查询,支持Unicode和预编译SQL等功能。 MySQL 4.1还在Alpha版时,公司已决定并行开发5.0版。因为他们打算加快MySQL的开发速度以适应日益苛刻的市场需求。这个新版本是有史以来MySQL最大的变化,添加了存储过程、服务端游标、触发器、查询优化以及分布式事务等在大家看来一个"正常数据库管理系统"应当拥有的一整套功能。 2008年2月,当时的业界开源老大Sun Microsystems动用10亿美元收购了MySQL,造就了开源软件的收购最高价。这次交易给开源交易设立了一个新的基准。在此之前的交易金额(JBoss、Zimbra、XenSource、Gluecode)从没接近过10亿美元,全部加起来才差不多与Sun Microsystems购买MySQL的花费持平。MySQL被收购之后,MySQL图标停止使用,取而代之的是Sun/MySQL图标。 MySQL和Sun合并之后,推出了MySQL 5.1GA版和MySQL 5.4 Beta版。5.4的推出照搬了4.1和5.0当时的开发模式,让5.4和6.0并行处于Beta开发阶段。 螳螂捕蝉,黄雀在后。2009年,数据库老大Oracle大笔一挥,开出74亿美元的支票,将Sun Microsystems和MySQL通盘收于旗下。 ▲ SUN被Oracle收购了 2.3 MySQL大事记 1999年,MySQL AB在瑞典正式宣布成立。 2000年,ISAM华丽转身MyISAM存储引擎。同年MySQL开放了自己的源代码,并且基于GPL许可协议。同年9月innoDB推出。 2003年,MySQL4.0发布,正式集成innodb 2005年,MySQL 5.0发布。同年Oracle把InnoDB引擎的开发公司innobase收购完成。MySQL明确地表现出迈向高性能数据库的发展步伐。 2006年,sun公司收购了MySQL公司,出价10亿美元。 2009年,Oracle公司收购sun,将MySQL纳入囊中。 2010年,MySQL 5.5正式版发布,Oracle完成了大量改进,并将innodb改成默认引擎。 2013年,MySQL 5.6 GA版本发布。 近期 - MySQL 5.7 GA版本横空出世,其性能、新特性、性能分析带来了质的改变。 2.4 MySQL现状及应用 ▲ 全球数据库排行(截止2017年) ▲ 全球最大网站Top20的数据库使用情况 以下是全球最大网站Top20列表: Facebook.com Google.com YouTube.com Yahoo.com WIKipedia.org - 维基百科 Live.com – 微软新的电子邮件服务 qq.com – 腾讯 Microsoft.com – 微软产品/更新/下载 Baidu.com – 百度 Msn.com – 微软自有互联网信息 Blogger.com – 博客平台 ASK.com -  搜索引擎 Taobao.com 淘宝 Twiter.com – 实时通讯平台 Bing.com – 必应 Sohu.com – 搜狐 Apple.com – 苹果 WrodPress.com – 成行经历 Sina.com – 新浪 Amazon.com-亚马逊 ▲ 国内MySQL行业应用 3、MySQL的单表优化干货总结 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量。 3.1 “字段”优化总结 1)尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED; 2)VARCHAR的长度只分配真正需要的空间; 3)使用枚举或整数代替字符串类型; 4)尽量使用TIMESTAMP而非DATETIME; 5)单表不要有太多字段,建议在20以内; 6)避免使用NULL字段,很难查询优化且占用额外索引空间; 7)用整型来存IP。 3.2 “索引”优化总结 1)索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描; 2)应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描。 3)值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段。 4)字符字段只建前缀索引。 5)字符字段最好不要做主键。 6)不用外键,由程序保证约束。 7)尽量不用UNIQUE,由程序保证约束。 8)使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引。 3.3 “查询SQL”优化总结 1)可通过开启慢查询日志来找出较慢的SQL; 2)不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边; 3)sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库; 4)不用SELECT *; 5)OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内; 6)不用函数和触发器,在应用程序实现; 7)避免%xxx式查询; 8)少用JOIN; 9)使用同类型进行比较,比如用'123'和'123'比,123和123比; 10)尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描; 11)对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5; 12)列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大。 3.4 “引擎”的选择 目前广泛使用的是MyISAM和InnoDB两种引擎。 【MyISAM】: MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是: 1)不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁; 2)不支持事务; 3)不支持外键; 4)不支持崩溃后的安全恢复; 5)在表有读取查询的同时,支持往表中插入新纪录; 6)支持BLOB和TEXT的前500个字符索引,支持全文索引; 7)支持延迟更新索引,极大提升写入性能; 8)对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用。 【InnoDB】: InnoDB在MySQL 5.5后成为默认索引,它的特点是: 1)支持行锁,采用MVCC来支持高并发; 2)支持事务; 3)支持外键; 4)支持崩溃后的安全恢复; 5)不支持全文索引。 总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表。 3.5 系统调优参数 可以使用下面几个工具来做基准测试: sysbench:一个模块化,跨平台以及多线程的性能测试工具; iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具; tpcc-mysql:Percona开发的TPC-C测试工具。 具体的调优参数内容较多,具体可参考官方文档,这里介绍一些比较重要的参数: 1)back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500; 2)wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时; 3)max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限; 4)thread_concurrency:并发线程数,设为CPU核数的两倍; 5)skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问; 6)key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好; 7)innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好; 8)innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小; 9)innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB; 10)query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大; 11)可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小; 12)read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能; 13)sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小; 14)read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大; 15)record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值; 16)thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的; 17)table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM。 3.6 升级硬件 Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能。 4、读写分离 也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离 5、缓存 缓存可以发生在这些层次: 1)MySQL内部:在系统调优参数介绍了相关设置; 2)数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object; 3)应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object; 4)Web层:针对web页面做缓存; 5)浏览器客户端:用户端的缓存。 可以根据实际情况在一个层次或多个层次结合加入缓存。 这里重点介绍下服务层的缓存实现,目前主要有两种方式: 1)直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般; 2)回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。 6、表分区 MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码。 对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引 用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,如下图5条记录落在两个分区上: 分区的好处是: 1)可以让单表存储更多的数据; 2)分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作; 3)部分查询能够从查询条件确定只落在少数分区上,速度会很快; 4)分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备; 5)可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争; 6)可以备份和恢复单个分区。 分区的限制和缺点: 1)一个表最多只能有1024个分区; 2)如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来; 3)分区表无法使用外键约束; 4)NULL值会使分区过滤无效; 5)所有分区必须使用相同的存储引擎。 分区的类型: 1)RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区; 2)LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择; 3)HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式; 4)KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。 分区最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示: 查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。 如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存。 另外MySQL有一种早期的简单的分区实现 - 合并表(merge table),限制较多且缺乏优化,不建议使用,应该用新的分区机制来替代。 7、垂直拆分 垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联。 比如原始的用户表是: 垂直拆分后是: 垂直拆分的优点是: 1)可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少); 2)可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起; 3)数据维护简单。 缺点是: 1)主键出现冗余,需要管理冗余列; 2)会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力; 3)依然存在单表数据量过大的问题(需要水平拆分); 4)事务处理复杂。 8、水平拆分 8.1 概述 水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的MySQL表或库,达到分布式的效果,能够支持非常大的数据量。前面的表分区本质上也是一种特殊的库内分表。 库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决。 前面垂直拆分的用户表如果进行水平拆分,结果是: 实际情况中往往会是垂直拆分和水平拆分的结合,即将Users_A_M和Users_N_Z再拆成Users和UserExtras,这样一共四张表。 水平拆分的优点是: 1)不存在单库大数据和高并发的性能瓶颈; 2)应用端改造较少; 3)提高了系统的稳定性和负载能力。 缺点是: 1)分片事务一致性难以解决; 2)跨节点Join性能差,逻辑复杂; 3)数据多次扩展难度跟维护量极大。 8.2 分片原则 1)能不分就不分,参考“单表优化”; 2)分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量; 3)分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容; 4)尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题; 5)查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引; 6)通过数据冗余和表分区赖降低跨库Join的可能。 这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。 总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。 8.3 解决方案 由于水平拆分牵涉的逻辑比较复杂,当前也有了不少比较成熟的解决方案。这些方案分为两大类:客户端架构和代理架构。 【客户端架构】: 通过修改数据访问层,如JDBC、Data Source、MyBatis,通过配置来管理多个数据源,直连数据库,并在模块内完成数据的分片整合,一般以Jar包的方式呈现。 这是一个客户端架构的例子: 可以看到分片的实现是和应用服务器在一起的,通过修改Spring JDBC层来实现 客户端架构的优点是: 1)应用直连数据库,降低外围系统依赖所带来的宕机风险; 2)集成成本低,无需额外运维的组件。 缺点是: 1)限于只能在数据库访问层上做文章,扩展性一般,对于比较复杂的系统可能会力不从心; 2)将分片逻辑的压力放在应用服务器上,造成额外风险。 【代理架构】: 通过独立的中间件来统一管理所有数据源和数据分片整合,后端数据库集群对前端应用程序透明,需要独立部署和运维代理组件。 这是一个代理架构的例子: 代理组件为了分流和防止单点,一般以集群形式存在,同时可能需要Zookeeper之类的服务组件来管理。 代理架构的优点是: 能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强; 对于应用服务器透明且没有增加任何额外负载。 缺点是: 需部署和运维独立的代理中间件,成本高; 应用需经过代理来连接数据库,网络上多了一跳,性能有损失且有额外风险。 8.4 各方案比较 如此多的方案,如何进行选择?可以按以下思路来考虑: 1)确定是使用代理架构还是客户端架构。中小型规模或是比较简单的场景倾向于选择客户端架构,复杂场景或大规模系统倾向选择代理架构; 2)具体功能是否满足,比如需要跨节点ORDER BY,那么支持该功能的优先考虑; 3)不考虑一年内没有更新的产品,说明开发停滞,甚至无人维护和技术支持; 4)最好按大公司->社区->小公司->个人这样的出品方顺序来选择; 5)选择口碑较好的,比如github星数、使用者数量质量和使用者反馈; 6)开源的优先,往往项目有特殊需求可能需要改动源代码。 按照上述思路,推荐以下选择: 1)客户端架构:ShardingJDBC; 2)代理架构:MyCat或者Atlas。 9、兼容MySQL且可水平扩展的数据库 目前也有一些开源数据库兼容MySQL协议,如: 1)TiDB; 2)Cubrid。 但其工业品质和MySQL尚有差距,且需要较大的运维投入。 如果想将原始的MySQL迁移到可水平扩展的新数据库中,可以考虑一些云数据库: 1)阿里云PetaData; 2)阿里云OceanBase; 3)腾讯云DCDB。 10、NoSQL 在MySQL上做Sharding是一种戴着镣铐的跳舞,事实上很多大表本身对MySQL这种RDBMS的需求并不大,并不要求ACID,可以考虑将这些表迁移到NoSQL,彻底解决水平扩展问题。 例如: 1)日志类、监控类、统计类数据; 2)非结构化或弱结构化数据; 3)对事务要求不强,且无太多关联操作的数据。 附录:更多架构设计方面的文章汇总 [1] 有关IM架构设计的文章: 《浅谈IM系统的架构设计》 《简述移动端IM开发的那些坑:架构设计、通信协议和客户端》 《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》 《一套原创分布式即时通讯(IM)系统理论架构方案》 《从零到卓越:京东客服即时通讯系统的技术架构演进历程》 《蘑菇街即时通讯/IM服务器开发之架构选择》 《腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT》 《微信后台基于时间序的海量数据冷热分级架构设计实践》 《微信技术总监谈架构:微信之道——大道至简(演讲全文)》 《如何解读《微信技术总监谈架构:微信之道——大道至简》》 《快速裂变:见证微信强大后台架构从0到1的演进历程(一)》 《17年的实践:腾讯海量产品的技术方法论》 《移动端IM中大规模群消息的推送如何保证效率、实时性?》 《现代IM系统中聊天消息的同步和存储方案探讨》 《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》 《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》 《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》 《WhatsApp技术实践分享:32人工程团队创造的技术神话》 《微信朋友圈千亿访问量背后的技术挑战和实践总结》 《王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》 《IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?》 《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》 《以微博类应用场景为例,总结海量社交系统的架构设计步骤》 《快速理解高性能HTTP服务端的负载均衡技术原理》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》 《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》 《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》 《一套高可用、易伸缩、高并发的IM群聊架构方案设计实践》 《阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》 《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》 >> 更多同类文章 …… [2] 更多其它架构设计相关文章: 《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》 《快速理解高性能HTTP服务端的负载均衡技术原理》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》 《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》 《阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》 《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》 《达达O2O后台架构演进实践:从0到4000高并发请求背后的努力》 《优秀后端架构师必会知识:史上最全MySQL大表优化方案总结》 >> 更多同类文章 …… (本文同步发布于:http://www.52im.net/thread-2157-1-1.html)
文章
SQL  ·  存储  ·  关系型数据库  ·  数据库  ·  索引
2018-12-17
数据库
252936 人关注 | 52042 讨论 | 98851 内容
+ 订阅
  • 【大数据开发运维解决方案】记一次同事不慎用root起动weblogic以及启动日志卡在The server started in RUNNING mode 问题解决过程
  • 【大数据开发运维解决方案】ogg(GoldenGate)三大进程常用参数
  • 【大数据开发运维解决方案】GoldenGate replicat进程延迟分析步骤
查看更多 >
人工智能
2863 人关注 | 12303 讨论 | 102299 内容
+ 订阅
  • 单片机基础
  • 【大数据实时数据同步】GoldenGate实时同步异常:OGG-03533:character ‘c2 a0‘ at offset 0 that is not available报错解决
  • 机器学习算法(九): 基于线性判别模型的LDA手写数字分类识别
查看更多 >
微服务
23043 人关注 | 11685 讨论 | 35023 内容
+ 订阅
  • 【大数据开发运维解决方案】hadoop+kylin安装及官方cube/steam cube案例文档
  • 【大数据开发运维解决方案】Kylin消费Kafka数据流式构建cube
  • 【大数据开发运维解决方案】Hadoop2.7.6+Spark2.4.4+Scala2.11.12+Hudi0.5.2单机伪分布式安装
查看更多 >
开发与运维
5766 人关注 | 133244 讨论 | 318450 内容
+ 订阅
  • 【大数据开发运维解决方案】记一次同事不慎用root起动weblogic以及启动日志卡在The server started in RUNNING mode 问题解决过程
  • 单片机基础
  • 【大数据开发运维解决方案】ogg(GoldenGate)三大进程常用参数
查看更多 >
大数据
188703 人关注 | 30735 讨论 | 83736 内容
+ 订阅
  • 【大数据开发运维解决方案】记一次同事不慎用root起动weblogic以及启动日志卡在The server started in RUNNING mode 问题解决过程
  • 【大数据开发运维解决方案】ogg(GoldenGate)三大进程常用参数
  • 【大数据开发运维解决方案】GoldenGate replicat进程延迟分析步骤
查看更多 >