解决方案之网站大数据高并发
大数据处理
1、数据库
垂直拆分:根据业务把表放到不同的数据库,解决表之间的io竞争
水平拆分:根据某种规则把单表数据分成多张表存储,解决单表数据量大的问题
索引:根据业务场景创建合理的索引,如果数据量很小建议使用索引(300条以内)
索引使用场景:
动作描述 |
聚集索引 |
非聚集索引 |
主键列 |
是 |
是 |
外键列 |
是 |
是 |
列经常被分组排序 |
是 |
是 |
返回某范围内的数据 |
是 |
否 |
一个或极少不同值 |
否 |
否 |
小数目的不同值 |
是 |
否 |
大数目的不同值 |
否 |
是 |
频繁更新的列 |
否 |
是 |
频繁修改索引列 |
否 |
是 |
分离活跃数据:把常用的数据和不常用的数据分离,例如活跃用户和不活跃用户
读写分离:写用主库,读用从库
其它:表结构优化、sql语句优化
2、缓存
读数据时先从缓存中取,命中不到再读库
3、集群
使用hadoop做集群,可以同时处理相同的数据
高并发处理
1、硬件
集群:多台服务器分流 ,静态资源集群和动态资源集群,需要考虑session同步问题
cdn:使用cdn存放静态资源
反向代理:用别的服务器代理处理
2、软件
动静分离:静态资源和动态资源分离,把静态资源放到单独的服务器
页面缓存:经常访问的页面可以做成静态页面放到缓存里
缩短请求路径:缩短请求服务的路径,便于解析
批量读取:多个请求合并到一起请求,减少数据库访问次数
延迟修改:把多次修改请求先放到缓存中,然后同步到库
sql语句优化:
- where语句中不要出现null、or、!=、<>、in、not in否则会避开索引进行全表扫描
-
尽量避免在索引过的字符数据中,使用非打头字母搜索,否则会避开索引进行全表扫如:select * from t1 where name like ‘%l%’
select * from t1 where substing(name,2,1)=’l’
-
必要时强制查询优化器使用某个索引,如在 where 子句中使用参数,也会导致全表扫描如:select id from t where
可以改为强制查询使用索引:
select id from t with(index(索引名)) where
-
应尽量避免在 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())
即:任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。
-
应尽量避免在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' - 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
-
很多时候用 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)
三种写法都可以得到同样正确的结果,但是效率依次降低。
- 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
- 避免频繁创建和删除临时表,以减少系统表资源的消耗。
- 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
- 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
- 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
- 在所有的存储过程和触发器的开始处设置 set nocount on ,在结束时设置 set nocount off 。无需在执行存储过程和触发器的每个语句后向客户端发送 done_in_proc 消息。
- 尽量避免大事务操作,提高系统并发能力。
- 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
-
避免使用不兼容的数据类型。例如float和int、char和varchar、binary和varbinary是不兼容的。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如:
select name from employee where salary > 60000
在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。 -
充分利用连接条件,在某种情况下,两个表之间可能不只一个的连接条件,这时在 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
第二句将比第一句执行快得多。 -
使用视图加速查询
把表的一个子集进行排序并创建视图,有时能加速查询。它有助于避免多重排序 操作,而且在其他方面还能简化优化器的工作 - 能用distinct的就不用group by
- 能用union all就不要用union
-
尽量不要用select into语句。
select inot 语句会导致表锁定,阻止其他用户访问该表。 - 尽量避免使用游标,因为游标的效率较差
上一篇: 泡沫下的缩影:月薪2万
下一篇: java关于字符串是否存