大数据量高并发的数据库优化

只因有你

贡献于2013-06-19

字数:0 关键词:

大数据量高并发的数据库优化(一) - [技术研究] 术研究] 一、数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将 会影响系统实际运行的性能。所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的。 在一个系统分析、设计阶段,因为数据量较小,负荷较低。我们往往只注意到功能的实现,而很难注意 到性能的薄弱之处,等到系统投入实际运行一段时间后,才发现系统的性能在降低,这时再来考虑提高系 统性能则要花费更多的人力物力,而整个系统也不可避免的形成了一个打补丁工程。 所以在考虑整个系统的流程的时候,我们必须要考虑,在高并发大数据量的访问情况下,我们的系统会 不会出现极端的情况。(例如:对外统计系统在 7 月 16 日出现的数据异常的情况,并发大数据量的的访 问造成,数据库的响应时间不能跟上数据刷新的速度造成。具体情况是:在日期临界时(00:00:00), 判断数据库中是否有当前日期的记录,没有则插入一条当前日期的记录。在低并发访问的情况下,不会发 生问题,但是当日期临界时的访问量相当大的时候,在做这一判断的时候,会出现多次条件成立,则数据 库里会被插入多条当前日期的记录,从而造成数据错误。),数据库的模型确定下来之后,我们有必要做 一个系统内数据流向图,分析可能出现的瓶颈。 为了保证数据库的一致性和完整性,在逻辑设计的时候往往会设计过多的表间关联,尽可能的降低数据 的冗余。(例如用户表的地区,我们可以把地区另外存放到一个地区表中)如果数据冗余低,数据的完整 性容易得到保证,提高了数据吞吐速度,保证了数据的完整性,清楚地表达数据元素之间的关系。而对于 多表之间的关联查询(尤其是大数据表)时,其性能将会降低,同时也提高了客户端程序的编程难度,因 此,物理设计需折衷考虑,根据业务规则,确定对关联表的数据量大小、数据项的访问频度,对此类数据 表频繁的关联查询应适当提高数据冗余设计但增加了表间连接查询的操作,也使得程序的变得复杂,为了 提高系统的响应时间,合理的数据冗余也是必要的。设计人员在设计阶段应根据系统操作的类型、频度加 以均衡考虑。 另外,最好不要用自增属性字段作为主键与子表关联。不便于系统的迁移和数据恢复。对外统计系统映 射关系丢失(******************)。 原来的表格必须可以通过由它分离出去的表格重新构建。使用这个规定的好处是,你可以确保不会在分 离的表格中引入多余的列,所有你创建的表格结构都与它们的实际需要一样大。应用这条规定是一个好习 惯,不过除非你要处理一个非常大型的数据,否则你将不需要用到它。(例如一个通行证系统,我可以将 USERID,USERNAME,USERPASSWORD,单独出来作个表,再把 USERID 作为其他表的外键) 表的设计具体注意的问题: 1、数据行的长度不要超过 8020 字节,如果超过这个长度的话在物理页中这条数据会占用两行从而造成 存储碎片,降低查询效率。 2、能够用数字类型的字段尽量选择数字类型而不用字符串类型的(电话号码),这会降低查询和连接 的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数 字型而言只需要比较一次就够了。 3、对于不可变字符类型 char 和可变字符类型 varchar 都是 8000 字节,char 查询快,但是耗存储空间, varchar 查询相对慢一些但是节省存储空间。在设计字段的时候可以灵活选择,例如用户名、密码等长度变 化不大的字段可以选择 CHAR,对于评论等长度变化大的字段可以选择 VARCHAR。 4、字段的长度在最大限度的满足可能的需要的前提下,应该尽可能的设得短一些,这样可以提高查询 的效率,而且在建立索引的时候也可以减少资源的消耗。 二、查询的优化 保证在实现功能的基础上,尽量减少对数据库的访问次数;通过搜索参数,尽量减少对表的访问行数,最小 化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度;在数据窗口使用 SQL 时,尽量把使用的索引放在选择的首列;算法的结构尽量简单;在查询时,不要过多地使用通配符如 SELECT * FROM T1 语句,要用到几列就选择几列如:SELECT COL1,COL2 FROM T1;在可能的情况下尽量限制 尽量结果集行数如:SELECT TOP 300 COL1,COL2,COL3 FROM T1,因为某些情况下用户是不需要那么 多的数据的。 在没有建索引的情况下,数据库查找某一条数据,就必须进行全表扫描了,对所有数据进行一次遍历,查 找出符合条件的记录。在数据量比较小的情况下,也许看不出明显的差别,但是当数据量大的情况下,这 种情况就是极为糟糕的了。 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 形式的表达式来说是 无用的。 所以,优化查询最重要的就是,尽量使语句符合查询优化器的规则避免全表扫描而使用索引查询。 具体要注意的: 1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描, 如: select id from t where num is null 可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询: select id from t where num=0 2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。优化器将无 法通过索引来确定将要命中的行数,因此需要搜索该表的所有行。 3.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20 4.in 和 not in 也要慎用,因为 IN 会使系统无法使用索引,而只能直接搜索表中的数据。如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 5.尽量避免在索引过的字符数据中,使用非打头字母搜索。这也使得引擎无法利用索引。 见如下例子: SELECT * FROM T1 WHERE NAME LIKE „%L%‟ SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=‟L‟ SELECT * FROM T1 WHERE NAME LIKE „L%‟ 即使 NAME 字段建有索引,前两个查询依然无法利用索引完成加快操作,引擎不得不对全表所有数据逐条 操作来完成任务。而第三个查询能够使用索引来加快操作。 6.必要时强制查询优化器使用某个索引,如在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只 有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选 择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面 语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num 7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: SELECT * FROM T1 WHERE F1/2=100 应改为: SELECT * FROM T1 WHERE F1=100*2 SELECT * FROM RECORD WHERE SUBSTRING(CARD_NO,1,4)=‟5378‟ 应改为: SELECT * FROM RECORD WHERE CARD_NO LIKE „5378%‟ SELECT member_number, first_name, last_name FROM members WHERE DATEDIFF(yy,datofbirth,GETDATE()) > 21 应改为: SELECT member_number, first_name, last_name FROM members WHERE dateofbirth < DATEADD(yy,-21,GETDATE()) 即:任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至 等号右边。 8.应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where substring(name,1,3)='abc'--name 以 abc 开头的 id select id from t where datediff(day,createdate,'2005-11-30')=0--„2005-11-30‟生成的 id 应改为: select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1' 9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用 索引。 10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件 时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 11.很多时候用 exists 是一个好的选择: elect num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num) SELECT SUM(T1.C1)FROM T1 WHERE( (SELECT COUNT(*)FROM T2 WHERE T2.C2=T1.C2>0) SELECT SUM(T1.C1) FROM T1WHERE EXISTS( SELECT * FROM T2 WHERE T2.C2=T1.C2) 两者产生相同的结果,但是后者的效率显然要高于前者。因为后者不会产生大量锁定的表扫描或是索引扫 描。 如果你想校验表里是否存在某条纪录,不要用 count(*)那样效率很低,而且浪费服务器资源。可以用 EXISTS 代替。如: IF (SELECT COUNT(*) FROM table_name WHERE column_name = 'xxx') 可以写成: IF EXISTS (SELECT * FROM table_name WHERE column_name = 'xxx') 经常需要写一个 T_SQL 语句比较一个父结果集和子结果集,从而找到是否存在在父结果集中有而在子结 果集中没有的记录,如: SELECT a.hdr_key FROM hdr_tbl a---- tbl a 表示 tbl 用别名 a 代替 WHERE NOT EXISTS (SELECT * FROM dtl_tbl b WHERE a.hdr_key = b.hdr_key) SELECT a.hdr_key FROM hdr_tbl a LEFT JOIN dtl_tbl b ON a.hdr_key = b.hdr_key WHERE b.hdr_key IS NULL SELECT hdr_key FROM hdr_tbl WHERE hdr_key NOT IN (SELECT hdr_key FROM dtl_tbl) 三种写法都可以得到同样正确的结果,但是效率依次降低。 12.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。 13.避免频繁创建和删除临时表,以减少系统表资源的消耗。 14.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用 表中的某个数据集时。但是,对于一次性事件,最好使用导出表。 15.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成 大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先 create table,然后 insert。 16.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 17.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 18.尽量避免大事务操作,提高系统并发能力。 19.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 20. 避免使用不兼容的数据类型。例如 float 和 int、char 和 varchar、binary 和 varbinary 是不兼容的。数 据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如: SELECT name FROM employee WHERE salary > 60000 在这条语句中,如 salary 字段是 money 型的,则优化器很难对其进行优化,因为 60000 是个整型数。我们应 当在编程时将整型转化成为钱币型,而不要等到运行时转化。 21.充分利用连接条件,在某种情况下,两个表之间可能不只一个的连接条件,这时在 WHERE 子句中将 连接条件完整的写上,有可能大大提高查询速度。 例: SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO AND A.ACCOUNT_NO=B.ACCOUNT_NO 第二句将比第一句执行快得多。 22、使用视图加速查询 把表的一个子集进行排序并创建视图,有时能加速查询。它有助于避免多重排序 操作,而且在其他方面还 能简化优化器的工作。例如: SELECT cust.name,rcvbles.balance,……other columns FROM cust,rcvbles WHERE cust.customer_id = rcvlbes.customer_id AND rcvblls.balance>0 AND cust.postcode>“98000” ORDER BY cust.name 如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个视图中,并按客户的名 字进行排序: CREATE VIEW DBO.V_CUST_RCVLBES AS SELECT cust.name,rcvbles.balance,……other columns FROM cust,rcvbles WHERE cust.customer_id = rcvlbes.customer_id AND rcvblls.balance>0 ORDER BY cust.name 然后以下面的方式在视图中查询: SELECT * FROM V_CUST_RCVLBES WHERE postcode>“98000” 视图中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘 I/O,所以查询工作量可以得 到大幅减少。 23、能用 DISTINCT 的就不用 GROUP BY SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID 可改为: SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10 24.能用 UNION ALL 就不要用 UNION UNION ALL 不执行 SELECT DISTINCT 函数,这样就会减少很多不必要的资源 35.尽量不要用 SELECT INTO 语句。 SELECT INOT 语句会导致表锁定,阻止其他用户访问该表。 上面我们提到的是一些基本的提高查询速度的注意事项,但是在更多的情况下,往往需要反复试验比较不 同的语句以得到最佳方案。最好的方法当然是测试,看实现相同功能的 SQL 语句哪个执行时间最少,但是 数据库中如果数据量很少,是比较不出来的,这时可以用查看执行计划,即:把实现相同功能的多条 SQL 语句考到查询分析器,按 CTRL+L 看查所利用的索引,表扫描次数(这两个对性能影响最大),总体上看 询成本百分比即可。 三、算法的优化 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1 万行,那么就应该考虑改写。.使用 基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处 理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游 标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的 效果更好。 游标提供了对特定集合中逐行扫描的手段,一般使用游标逐行遍历数据,根据取出的数据不同条件进 行不同的操作。尤其对多表和大表定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等特甚 至死机。 在有些场合,有时也非得使用游标,此时也可考虑将符合条件的数据行转入临时表中,再对临时表定义 游标进行操作,可时性能得到明显提高。 (例如:对内统计第一版) 封装存储过程 四、建立高效的索引 创建索引一般有以下两个目的:维护被索引列的唯一性和提供快速访问表中数据的策略。大型数据库有 两种索引即簇索引和非簇索引,一个没有簇索引的表是按堆结构存储数据,所有的数据均添加在表的尾部, 而建立了簇索引的表,其数据在物理上会按照簇索引键的顺序存储,一个表只允许有一个簇索引,因此, 根据 B 树结构,可以理解添加任何一种索引均能提高按索引列查询的速度,但会降低插入、更新、删除操 作的性能,尤其是当填充因子(Fill Factor)较大时。所以对索引较多的表进行频繁的插入、更新、删除操 作,建表和索引时因设置较小的填充因子,以便在各数据页中留下较多的自由空间,减少页分割及重新组 织的工作。 索引是从数据库中获取数据的最高效方式之一。95% 的数据库性能问题都可以采用索引技术得到解决。作 为一条规则,我通常对逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引, 对任何外键列[字段]采用非成组索引。不过,索引就象是盐,太多了菜就咸了。你得考虑数据库的空间有多 大,表如何进行访问,还有这些访问是否主要用作读写。 实际上,您可以把索引理解为一种特殊的目录。微软的 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 倍,甚至更快。 2、只要建立索引就能显著提高查询速度 事实上,我们可以发现上面的例子中,第 2、3 条语句完全相同,且建立索引的字段也相同;不同的仅是前 者在 fariqi 字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。 所以,并非是在任何字段上简单地建立索引就能提高查询速度。 从建表的语句中,我们可以看到这个有着 1000 万数据的表中 fariqi 字段有 5003 个不同记录。在此字段上 建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这 完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我 们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。 3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度 上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是 如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。 很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引 字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是 25 万条数据):(日期列 fariqi 首先排在复合聚集索引的起始列,用户名 neibuyonghu 排在后列) 我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几 乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合 聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句 1、2 的查询速度一样是 因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”, 因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使 用最频繁的列。 (三)其他注意事项 “水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。 因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。 所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库 能得到高性能的发挥 解决方案:建立中间表,通过 DTS 调度每天共步数据。 中间表设计原则 记录数同原表一样,减少多表连接,保存运算好的值,如果记录修改,根据修改日志,重新计算中间值 增量同步数据(DTS) 直接从每天的数据库更改日志读取记录,更新中间表,根据服务器空间程度合理调度 DTS,减少数据同步 时间。 对中间数据进行运算 查询不作字段运行,所以运算在生成中间表的过程中已经计算 根据查询,优化索引设计 根据数据查询特性,对 where ,GROUP BY 等操作字段进行索引设计,提高查询速度 优化数据类型 大量采用 Int 提高查询、统计速度 优化中间表关键字 采用 Int,提高插入速度 数据文件优化设计,一个主要业务,一个数据文件,建数据文件时,估计数据量,一次建一个比较大的文 件,这样所分配的文件就是一个连续文件块, sql server 设置区别大小写。初始内存调到一个比较大的内存。 使用我们的 Toolkit 开发简单分页,相关压力测试, 测试服务器配制 2 个至强 3.0CPU 2G 内存 150G 硬盘 Window 2000 Advance Server 中文版+SP4 测试数据 ENTRY_WORKFLOW 表,数据量 2,473,437 页数 界面显示时间 CPU Reads I/O Writes I/O Duration 第 1 页 2-3 S 642 10689 0 390 第 100 页 3-4S 626 128001 0 423 ....后页业数太多,没有必要 压力测试 并发数 平均每秒请求数 未字节响应毫秒数 50 45.28 20,095.65 25 45.41 10,043.12 索引优化测试, 对分量值小的数据建索引测试,测试语句,GROUP BY 分量值 一个字段,大概有 6 个分量值,没有建索引,4S,建索引<1s 两个分量,不建索引,3S,建索引,<1S 一般来说,对分量小的字段,不建索引,但是我们对性能要求太高,根据我们的测试,数据对分量范围小 的也要建索引。 因为一个统计,有一个很多组合的 WHERE,比如有十个指标这样会有十次访问原表,这样性能太低,所 以我们把 where 后的数据作 用中间数据, 十个指标对中间数据作查询,中间数据,我们使用临时表, 经测试,10 万条记录,插入操作,临时表需要,16s,表变量需要,40S select ... into #temp from ....... 速度极快,2,500,000 条记录,16S 高并发高负载数据库架构策略 在 WEB 网站的规模从小到大不断扩展的过程中,数据库的访问压力也不断的增加,数据库的架构也需要 动态扩展,在数据库的扩展过程基本上包含如下几步,每一个扩展都可以比上一步骤的部署方式的性能得 到数量级的提升。 1、WEB 应用和数据库部署在同一台服务器上 一般的小规模的网站采用这种方式,用 户量、数据量、并发访问量都比较小,否则单台服务器无法承受,并且在遇到性能瓶颈的时候升级硬件所 需要的费用非常高昂,在访问量增加的时候,应用程序和数据库都来抢占有限的系统资源,很快就又会遇 到性能问题。 2、WEB 应用和数据库部署在各自独立的服务器上 web 应用和数据库分开部署,WEB 应 用服务器和数据库服务器各司其职,在系统访问量增加的时候可以分别升级应用服务器和数据库服务器, 这种部署方式是一般小规模网站的典型部署方式。在将应用程序进行性能优化并且使用数据库对象缓存策 略的情况下,可以承载较大的访问量,比如 2000 用户,200 个并发,百万级别的数据量。 3、数据库服 务器采用集群方式部署(比如 Oracle 的一个数据库多个实例的情况) 数据库集群方式能承担的负载是比 较大的,数据库物理介质为一个磁盘阵列,多个数据库实例以虚拟 IP 方式向外部应用服务器提供数据库连 接服务。这种部署方式基本上可以满足绝大多数的常见 WEB 应用,但是还是不能满足大用户量、高负载、 数据库读写访问非常频繁的应用。 4、数据库采用主从部署方式 在面向大众用户的博客、论谈、交友、 CMS 等系统中,有上百万的用户,有上千万的数据量,存在众多的数据库查询操作,也有较多的数据库写 操作,并且在多数情况下都是读操作远大于写操作的。在这个时候,假如能将数据库的读写操作分离的话, 对于系统来讲是一个很大的提高啦。数据库的主从部署方式就走到我们面前啦。 主从复制: 几乎所有的 主流数据库都支持复制,这是进行数据库简单扩展的基本手段。下面以 Mysql 为例来说明,它支持主从复 制,配置也并不复杂,只需要开启主服务器上的二进制日志以及在主服务器和从服务器上分别进行简单的 配置和授权。Mysql 的主从复制是一句主服务器的二进制日志文件进行的,主服务器日志中记录的操作会 在从服务器上重放,从而实现复制,所以主服务器必须开启二进制日志,自动记录所有对于主数据库的更 新操作,从服务器再定时到主服务器取得二进制日志文件进行重放则完成了数据的复制。主从复制也用于 自动备份。 读写分离: 为保证数据库数据的一致性,我们要求所有对于数据库的更新操作都是针对主数 据库的,但是读操作是可以针对从数据库来进行。大多数站点的数据库读操作比写操作更加密集,而且查 询条件相对复杂,数据库的大部分性能消耗在查询操作上了。 主从复制数据是异步完成的,这就导致主从 数据库中的数据有一定的延迟,在读写分离的设计中必须要考虑这一点。以博客为例,用户登录后发表了 一篇文章,他需要马上看到自己的文章,但是对于其它用户来讲可以允许延迟一段时间(1 分钟/5 分钟/30 分钟),不会造成什么问题。这时对于当前用户就需要读主数据库,对于其他访问量更大的外部用户就可以 读从数据库。 数据库反向代理: 在读写分离的方式使用主从部署方式的数据库的时候,会遇到一个问题, 一个主数据库对应多台从服务器,对于写操作是针对主数据库的,数据库个数是唯一的,但是对于从服务 器的读操作就需要使用适当的算法来分配请求啦,尤其对于多个从服务器的配置不一样的时候甚至需要读 操作按照权重来分配。 对于上述问题可以使用数据库方向代理来实现。就像 WEB 方向代理服务器一样, MYsql Proxy 同样可以在 SQL 语句转发到后端的 Mysql 服务器之前对它进行修改。 5、数据库垂直分割 主 从部署数据库中,当写操作占了主数据库的 CPU 消耗的 50%以上的时候,我们再增加从服务器的意义就 不是很大了,因为所有的从服务器的写操作也将占到 CPU 消耗的 50%以上,一台从服务器提供出来查询 的资源非常有限。数据库就需要重新架构了,我们需要采用数据库垂直分区技术啦。 最简单的垂直分区方 式是将原来的数据库中独立的业务进行分拆(被分拆出来的部分与其它部分不需要进行 Join 连接查询操作), 比如 WEB 站点的 BLOG 和论坛,是相对独立的,与其它的数据的关联性不是很强,这时可以将原来的的 数据库拆分为一个 BLog 库,一个论坛库,以及剩余的表所组成的库。这三个库再各自进行主从数据库方 式部署,这样整个数据库的压力就分担啦。 另外查询扩展性也是采用数据库分区最主要的原因之一。将一 个大的数据库分成多个小的数据库可以提高查询的性能,因为每个数据库分区拥有自己的一小部分数据。 假设您想扫描 1 亿条记录,对一个单一分区的数据库来讲,该扫描操作需要数据库管理器独立扫描一亿条 记录,如果您将数据库系统做成 50 个分区,并将这 1 亿条记录平均分配到这 50 个分区上,那么每个数据 库分区的数据库管理器将只扫描 200 万记录。 6、数据库水平分割 在数据库的垂直分区之后,假如我们 的 BLOG 库又再次无法承担写操作的时候,我们又该怎么办呢?数据库垂直分区这种扩展方式又无能为力 了,我们需要的是水平分区。 水平分区意味着我们可以将同一个数据库表中的记录通过特定的算法进行分 离,分别保存在不同的数据库表中,从而可以部署在不同的数据库服务器上。很多的大规模的站点基本上 都是主从复制+垂直分区+水平分区这样的架构。水平分区并不依赖什么特定的技术,完全是逻辑村面的规 划,需要的是经验和业务的细分。如何分区呢?对于大型的 WEB 站点来说,必须分区,并且对于分区我 们没有选择的余地,对于那些频繁访问导致站点接近崩溃的热点数据,我们必须分区。在对数据分区的时 候,我们必须要存在一个分区索引字段,比如 USER_ID,它必须和所有的记录都存在关系,是分区数据库 中的核心表的主键,在其它表中作为外键,并且在使用主键的时候,该主键不能是自增长的,必须是业务 主键才可以。 余数分区: 我们可以将 User_ID%10 后的值为依据存入到不同的分区数据库中,该算法简 单高效,但是在分区数据库个数有变动的时候,整个系统的数据需要重新分布。 范围分区: 我们可以将 User_ID 的范围进行分区,比如 1-100000 范围为一个分区数据库,100001-200000 范围为一个分区数据 库,该算法在分区数据库个数有变动的时候,系统非常有利于扩展,但是容易导致不同分区之间的压力不 同,比如老用户所在的分区数据库的压力很大,但是新用户的分区数据库的压力偏小。 映射关系分区: 将 对分区索引字段的每个可能的结果创建一个分区映射关系,这个映射关系非常庞大,需要将它们写入数据 库中。比如当应用程序需要知道 User_id 为 10 的用户的 BLOG 内容在那个分区时,它必须查询数据库获 取答案,当然,我们可以使用缓存来提高性能。 这种方式详细保存了每一个记录的分区对应关系,所以各 个分区有非常强的可伸缩性,可以灵活的控制,并且将数据库从一个分区迁移到另一个分区也很简单,也 可以使各个分区通过灵活的动态调节来保持压力的分布平衡。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档