结合业务,精炼SQL

  现代网站,性能的瓶颈都围绕着数据库的性能来谈。数据库是存储的核心部件,在日益增长的流量中会凸显数据库的性能瓶颈。从《淘宝技术十年》书中来看,淘宝发展历程中从MYSQL换成了ORACLE又换成了MYSQL集群,每次变换数据库都是因为业务的增长,使得数据库的读写效率达到了瓶颈。作为互联网公司最受欢迎的数据库MYSQL,它有自己独特的魅力:轻便、社区版免费、可做集群等,即便是它拥有这么多特性和优势,我们开发人员也只是觉得它的优势只有:便于使用与学习。

  如果不是身为架构师的开发人员,最经常写到的就是SQL了,一条好的SQL抵得过几百行优化代码。因为程序的各处都在执行SQL,数据库如果执行太多的复杂的SQL,则它可能会卡死,这就意味着你的系统会死掉,有可能丢失大量用户的现在操作,这是灾难性的:别人觉得你的软件不好,丢失了客户。所以,掌握高效SQL的技巧是开发人员的必修课。(虽然我觉得我每天都在写SQL,但是其实在写EXCEL)

  持久层存储大概有内存和硬盘两种(我自己分的),内存级存储严格来说不算持久化存储,它们大体上使用的是NOSQL,存储的格式是BigTable,key,value型存储,存储的数据为非关系型的,现有的产品是Memcached,Redis,MongoDB。推测是使用哈希表数据结构。而硬盘级别的我认为属于严格的持久化存储了,它们使用结构化查询语言SQL,存储的数据为关系型的,现有的产品为MYSQL,ORACLE,DB2等等。

  总之,业务程序员(没有贬低,233)写业务的时候,基本上一个需求就是一个数据查询任务,写出慢的SQL,自然有让它变快的能力。唯一的原则就是:不要让数据库做多余的事情

  • CRUD,日常操作

  作为一个excel工作者(呸),当然是开发出可以供用户使用的excel页面啦。举个栗子,比如用户管理里面的,按不同的条件来查询用户,以及和另一张表共同过滤某些用户数据等。当你的系统做大了,你需要分析用户的行为以促进你的网站业务的增长,比如某种特性的用户(比如购买某种VIP)增长得很快,那么你需要去查询他们最多购买的产品、感兴趣的产品、以及消费区间等等。这其实都是查询语句的应用场景,而且在互联网企业中,查的操作是十分频繁的。

  对于SQL的语句来一个范式,那大概是:insert/delete/select/update 字段操作 from 表 where 筛选条件。虽然字段操作部分各有区别,但是重点在于where子句的编写,因为它的编写决定你SQL的执行时间。

  insert

insert into 表名(字段) values(字段值)

  写操作的执行应该是不算频繁的,对于大多数CRM或者ERP来说,这条语句已经足够了。如果插入多条,那就执行多次吧。

  delete

delete from 表名 where 条件

  删除操作是一种微妙复杂的操作,它不会影响系统性能,但是多表关联的时候,删除需要格外小心,程序中有可能根据某表的某些字段去查这个表的某列数据,你删除掉了,就可能报空异常。所以在设计表和程序的时候,需要考虑一种弱耦合的策略。

  update

update 表名 set 字段1=值1,字段2=值2... where 筛选条件

  更新操作也是一种微妙复杂的操作,系统性能的影响是有可能的,如果高并发写操作,由于博主缺少经验,不是很能想象这种场景处理方式(12306好像就是高读写)。更新的关键是,关联数据字段id不能随意更改,类似于指针丢失的概念。

  select

select 字段名 from 表名 where 筛选条件

  查操作十分的花式,你的产品经理会让你各种方式去筛选数据,因为互联网公司比较在意的是某些条件的数据的价值。再举个栗子,我们需要知道哪些用户是付费的、哪些用户是免费的、那些用户是忠实的、那些用户是活跃的等等,这些条件导致where字句变化多端。在系统有十分多查操作的时候,你的数据量很大,select语句有可能就会卡死数据库。所以,在写select的时候,把需要的字段拿出来就好了,避免写select *,如果你经常select *,那我不知道你会不会被主管打死。(其实我设想,数据库在筛选数据的时候应该是根据where筛选,耗时的是where的计算,而select * 大概是消耗传输带宽,不知道设想得对不对)。提高查询效率大概就只有合理应用索引这个方法了。如果数据量再大,就分库分表什么的了。

  • 源数据的处理

  假设查询一张表,到前台时候需要把数据包装一下,在不需要二次查询的时候,不想让前台看到字段名为key的json数据,那么有可能需要遍历后重新put进Map中,这样就有些无谓操作了。

  1.as能帮到你

    如果你的表是,什么USERNAME,PASSWORD这样全大写的字段,那你在select的时候可以as 别名。

select USERNAME as username,PASSWORD as password,EMAIL as email from USER;

  如果不写as,后面跟着另一个字符串的话默认也会变成别名。别名的作用在联合查询中也很有用处。

select a.字段1 as 字段1别名,b.字段2 as 字段2别名 from 表A as a ,表B as b where 筛选条件

  联合查询中用别名来区分一个SQL中的字段是属于哪个表中的。

 

  2.活用常用数据库函数

  有些表需要记录最后操作时间,如果在服务器语言中执行插入时候获取当前时间,是不是感觉有些别扭。可以使用now()函数。数据库会将当前数据库时间作为datetime类型写入。

  像某些计算查到内存里计算显得有些别扭,所以记得使用sum()求和和avg()求平均值。(是不是真的像做excel一样)

  count()这个函数是十分常用了,计算总共多少条数据。

  max()min()也十分实用。

  Date_Format()日期转换,少了我在内存中转换的一步,拿来直接用,不过要记得这个返回的会是字符串类型,如果你要使用Date类型,那得在服务器中转换。

  cast(表达式 as decimal)算是很实用的取整函数了,有了它可以少一步在内存中转换。

  所以写一条SQL可以长得很恶心,为了从源数据中拿到马上能用的数据。

select sum(字段1) as 字段1别名,cast(avg(字段2) as decimal) as 字段2别名,Date_Format(字段3,"%Y-%m-%d") as 字段3别名 from 表名 where 筛选条件

  但是从程序上来说,拿到的数据会很爽。

  3.多条数据处理

  博主一度陷入多条数据不会处理的境地,比如一张订单表,需要你分页查询(按用户,按日期,都可以),这回需要什么鬼实现方式?刚开始我想的是查到内存里然后进行计算,某些数据被剔除掉,这会造成你limit语句是个假分页……也就是查出10条,过滤掉了一些,页面上每页显示都不到10条,并且参差不齐。

  这时候我想起大学时候某位前辈说的,一行SQL胜过千万行程序。使用group by就好了。

select sum(订单金额) ,用户编号  from 订单表 where 条件 group by 用户编号 limit n,m

  这样就是聚合翻页了,按照用户编号分组的。

  如果你要筛选订单金额大于某个值的数据呢?头疼,又去内存里遍历?使用having

select sum(订单金额) as 订单金额别名,用户编号  from 订单表 where 条件 group by 用户编号 having 订单金额别名 > a and 订单金额别名 < b limit n,m

  4.升序、降序

  小儿科,order by 某字段 (desc)。这个主要还有一个,比如我想计算用户的购买记录,需要带上时间的。

select sum(购买总额),时间 from 订单表 where 条件 order by 字段 desc(降序 不填为升序)

  这样你出来的时间,会是最早那条。(反正不准)

  为了保证统计的总数的时候,同时拿到最新的那条购买时间,这么写就好了。

select sum(购买总额),max(时间) from 订单表 where 条件 order by 字段

 

  • 联合查询

  用得很少,因为现在的架构比较提倡单表弱耦合查询,因为联合查询的SQL很难读,并且一次join就要多算一个表,大概是连乘的复杂度。

  A left join B,左联合,表示以左表为基准,无论B有没有数据,A都会按照where条件查询完毕。结果相当于B集合除去A与B的交集。(图我就不画了)

  A right join B,右联合,表示以右表为基准。集合里相当是A除去A与B的交集。

  A inner join ,交集。

  join都可以后面加个on表示集合运算的条件。

    举个例子

select a.字段1,b.字段2 from 表A as a inner/left/right join 表B as b on(a.id=b.id)

  就是以a为中心,连接b表查询。

  在联合查询中,一般会以某表为中心表,散发式连接多表进行信息筛选。

  模拟一个业务环节,有一张用户表,用户会员信息表,用户订单表。可以查什么数据呢?这就很复杂了,比如查询还在会员期内的会员购买订单的金额、非会员的购买金额、未付款的订单金额等等。CRM系统的需求十分的变化多端,为的就是分析用户的行为从而增加软件的收入。

CREATE TABLE `USER` (
  `GMT_CREATE` datetime NOT NULL,
  `GMT_MODIFIED` datetime NOT NULL,
  `USER_ID` varchar(64) CHARACTER SET latin1 NOT NULL,
  `USER_NAME` varchar(64) CHARACTER SET latin1 DEFAULT NULL,
  `USER_PASSWORD` varchar(64) CHARACTER SET latin1 DEFAULT NULL,
  `USER_EMAIL` varchar(64) CHARACTER SET latin1 DEFAULT NULL,
  PRIMARY KEY (`USER_ID`),
  UNIQUE KEY `USER_ID_UNIQUE` (`USER_ID`),
  KEY `USER_ID` (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `USER_MEMBER` (
  `GMT_CREATE` datetime DEFAULT NULL,
  `USER_ID` varchar(64) NOT NULL COMMENT '会员id',
  `MEMBER_START` datetime NOT NULL COMMENT '会员开始时间',
  `MEMBER_END` datetime NOT NULL COMMENT '会员结束时间',
  `MEMBER_RANK` varchar(16) DEFAULT NULL COMMENT '会员等级',
  PRIMARY KEY (`USER_ID`),
  UNIQUE KEY `USER_ID_UNIQUE` (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='会员信息';
CREATE TABLE `USER_ORDER` (
  `GMT_CREATE` datetime DEFAULT NULL,
  `ID` int(20) NOT NULL AUTO_INCREMENT,
  `USER_ID` varchar(64) DEFAULT NULL COMMENT '用户id',
  `ORDER_PRICE` varchar(45) DEFAULT NULL COMMENT '订单金额',
  `STATUS` varchar(45) DEFAULT NULL COMMENT '订单支付状态',
  PRIMARY KEY (`ID`),
  KEY `uid` (`USER_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='用户订单';

  

  添加了一些数据。

 

 

 

  举个例子,我想查在期内的用户消费量,说着简单,写着就复杂了。

select a.USER_ID ,a.USER_NAME ,sum(c.ORDER_PRICE) from USER as a inner join USER_MEMBER as b on(a.USER_ID = b.USER_ID and MEMBER_END > now()) inner join USER_ORDER as c on(a.USER_ID = c.USER_ID and c.STATUS = 'yes') group by a.USER_ID

  

  • 结语

    经过实际开发的洗礼,深深的认识到写SQL的重要性,高效的SQL使得程序也变得简洁,对于SQL结果集的数据处理也是要小心的,一不注意就会报空指针异常。作为一名走向资深EXCEL开发的工程师来说(呸),确实,SQL是基础中的基础,在此做一个阶段性的总结。

posted @ 2017-07-09 17:09  天目山电鳗  阅读(1017)  评论(0编辑  收藏  举报