首页> 搜索结果页
"sql查询排除某条数据库" 检索
共 100 条结果
MySQL数据库上云四年打磨,五大经典案例让你不再“蓝瘦”
通过以往的经验分析得出,数据库上云问题可能有以下几种情况: 1.         数据库跨平台迁移(PG->MySQL、Oracle->MySQL),淘宝以前就有大量的Oracle迁到MySQL,也是发生过很多问题。 2.         跨版本升级(MySQL:5.1->5.5、5.5->5.6),导致了性能问题。 3.         数据库的执行计划、优化器、参数配置和硬件配置。 4.         云上较明显就是网络延迟(跨可用区域访问、公网延迟、网卡饱满)。   应用场景一:一个参数引发的血案 某个客户正在将本地系统迁移上云,在RDS上运行时间明显要比线下自建数据库运行时间要慢1倍,导致客户系统割接延期的风险。 根据经验的沉淀,我们可以分析确认数据库从云上迁移到云下,MySQL没有更改,所以不是跨平台迁移和跨版本升级的原因。 优化器版本 接下来,对比优化器版本,可以看到用户的数据库版本也没有问题,故而排除优化器问题。 SQL执行计划 再看SQL执行计划,对比线下和云上的SQL执行计划,通过观察rows可以得出,没有太大变化,排查到这,似乎已经到了山穷水尽的地步。 参数配置 接下来检查参数配置,发现用户手工更改了重要的三个参数。 测试验证 将三个参数一一对比调试,经过测试验证,是tmp_table_size的问题,云上默认是256K,本地是128M,云上实际上是一种朴实型的运行环境,参数值如果调的很大,其他用户的内存消耗可能就会变大,将tmp_table_size调到128M后,性能有所提升,从18秒降低到7秒,基本与本地持平。 总结排查思路 如果线下环境的SQL低于云上SQL。第一,检查执行计划;第二,检查数据库版本和优化器;第三,对比参数和硬件配置;第四,查看网络延迟。   应用场景二:上云版本升级带来性能下降 某手机客户端上云,第一次系统切割失败,数据库CPU 100%,需要在第二次割接前排除原因。 问题排除——跨版本升级 数据库CPU100%是比较容易排查的, 可以查询数据库中的SQL为什么慢,发现用户本地的MySQL版本是5.5,云上RDS版本是5.6,用户的一条SQL在本地5.5执行只需要零点几秒,而在RDS上需要十多秒,导致所有的线程都堆积起来了。 数据库版本发生了变化,最核心的一点就是优化器发生了变化,看图中执行计划中的rows,访问第一个表需要25万行,并且对25万行进行排序,工作量巨大,这就是问题所在。 为了更加确定问题,对比优化器在本地正常的情况下的SQL执行计划,它的rows是非常小的,所以可以推断是block_nested_loop优化器的优化,导致SQL执行计划的转变,进而导致SQL性能下降。 字符串存储时间导致隐士转换 这是开发习惯导致的问题,gmt_create用字符串存时间, 5.5版本加个索引,它能够利用索引,SQL是没有问题的。 5.6版本之后,全表扫描280万数据,所以这条SQL肯定慢,这就是导致CPU100%的根源。 总结排查思路 分析SQL执行计划,对比数据库版本和优化器规则。 最佳实践经验:保持数据库版本一致,功能和性能测试缺一不可。   应用场景三:数据库上云后性能下降紧急救援 某APP应用上云后数据库CPU100%,系统回滚会出现数据丢失;弹性升级需要时间较长,要在白天业务高峰来临之际消除障碍。 问题排除 对比发现规格配置较小,用户本地物理机的配置是云上RDS的规格两倍,导致慢SQL出现堆积。具体如下: 1.         本地物理机配置:2U机箱,2*Intel E5-2609 v2 4核,内存:64G;磁盘ssd,Raid5; 2.         RDS配置:逻辑CPU8核,内存32G,最大IOPS:12000。 优化SQL SQL的执行计划性能较低,走了两个索引的intersect,需要计算大量的数据rows:30137696。第一种解决办法是控制优化器的策略;第二种办法让表走index_finish Time’(‘finish Time)。 采取第二种办法将idx_delStatus索引删除,索引删除后执行计划恢复正常,性能急速提升。rows只需要40行。 总结排查思路 当性能变慢了,要对比数据库资源配置,分析SQL执行计划。 最佳实践经验:保持数据库资源配置一致,功能和性能测试缺一不可。   应用场景四:去“O”上云的护航故事 某大型系统从Oracle迁移到RDS MySQL,迁移到RDS后出现CPU100%,需要紧急解决。 改写子查询 在淘宝去“O”过程中也遇到过类似的问题,排查过程中发现是MySQL的子查询诟病,Oracle开发人员会经常使用这样的SQL做子查询,这个SQL可能就是查薪水为5000块人的名字,正常的思维逻辑为先查子查询的结果集,再反带到外面的表去关联,子查询中的结果集是非常小的,循环的次数较少,对性能不会有太大影响。但是在MySQL中就不一样了,MySQL只有一个嵌套循环,MySQL的处理逻辑是遍历employees表中的每一条记录,代入到子查询中去,如果employees表太大,就会导致循环的次数太多,使SQL性能受影响。 最佳经验实践:子查询在5.1、5.5版本中都存在较大风险,将子查询改为关联,使用MySQL5.6的版本可以避免子查询的问题。   应用场景五:网络延迟造成的性能下降 某电商系统迁移上云测试过程中发现性能较低,应用代码、数据库配置完全一样。 网络延迟放大 通过将SQL日志一行行看,应用日志一行行的对照,发现原来架构应用和DB实在一台服务器上,应用和DB没有网络交互,更致命的是,原来的系统架构一个订单要访问数据库200多次,到云上的效应就扩大了,所以性能自然下降了,只能更改代码,优化系统调用。 最佳实践经验:需要考虑上云后网络延迟对性能的影响,优化应用与数据库的访问;应用和数据库尽量保持在同一个可用区内访问。   全部总结来看,系统配置要保持一致,包括版本、参数、规格等;还有考虑网络延迟(带宽、跨机房等)。   本文根据阿里云技术专家玄惭于10月14日在2016年杭州云栖大会上的题为“数据库上云经典案例分析”的演讲整理而成。
文章
SQL · 关系型数据库 · MySQL · 数据库 · 索引
2016-11-15
2017双11技术揭秘—TDDL/DRDS 的类 KV 查询优化实践
作者:励强(君瑜) 场景介绍 性能优化是企业级应用永恒的话题,关系型数据库查询优化更是如此。在前台核心业务场景中,类 KeyValue 查询(以下简称类 KV 查询)是非常常见的(例如,SELECT id, name FROM users WHERE id=1002),并且在应用总 SQL 流量占比很高,例如,天猫某核心业务的类 KV 查询占比近90%,商品某系统中占比近80%,交易订单系统中占比也有50%左右,菜鸟等其他核心业务场景中这个现象也是相当普遍。 这类 SQL 已经非常简单,如果仅在SQL层面进行进一步优化会非常困难,因此针对这类场景,TDDL/DRDS 配合 AliSQL 提出了全新的解决方案。 产品简介 在进入正题前,简单介绍下 TDDL/DRDS 产品,TDDL 是阿里巴巴集团为了解决淘宝电商数据库单机瓶颈,在2008年研制的中间件产品,以分库分表为核心理念,基于 MySQL 存储简单有效解决数据存储和访问容量问题,该产品支撑了历届天猫双十一核心交易链路的数据库流量,并且在此期间逐步成长为阿里巴巴集团访问关系型数据库的标准。 2014年,TDDL 团队和阿里云 RDS 团队合作,在云上输出这款产品,取名DRDS(Distributed Relational Database Service),专注于解决单机关系型数据库扩展性问题,目前该产品在公共云上具有超过 1000 家企业用户,并且在私有云输出,支撑多家大型企业和政府部门的核心业务,并且随着业务的扩大和业界技术的进展,DRDS 产品也会逐步给大家带来更加高效和务实的分布式数据库功能和解决方案。 新的思路 TDDL/DRDS 的类 KV 查询优化是怎么做的?这得从寻找基于 MySQL 的新优化思路说起。2015年,我们注意到社区版 MySQL 在5.6支持了 InnoDB memcached 插件,该插件允许应用的类 KV 查询走 Memcached 协议来直接访问 MySQL InnoDB 引擎的Buffer(走 Memcached 协议与走 MySQL SQL 协议都能访问 InnoDB 上的同一份数据)。这样让类 KV 查询直接绕开 MySQL Server 层的解析器、优化器与执行器等过程,从而大大降低应用类 KV 查询的 MySQL CPU 开销,扩大类似双十一极端场景下数据库容量,并且有效降低数据库响应时间。 MySQL Memcahced Plugin 的类KV查询容量之所以能做到大幅度提升,是因为查询完全绕开了 SQL 在 MySQL Server 层的各项开销,查询链路被极致缩短,事实上,这样的优化思路对 TDDL/DRDS 也同样适用。 TDDL/DRDS 目前作为阿里巴巴集团关系型数据库的接入标准,为应用屏蔽了底层众多的水平拆分及主备库技术细节,然而,为业务带来便捷的分布式 SQL 入口同时,付出的代价也是有的。在 TDDL/DRDS 中,每一条 SQL,从入口到返回结果,需要经过 SQL 语法解析、查询优化、分布式执行计划生成,以及分布式执行、连接处理、类型处理等一系列过程,这些动作需要消耗大量应用端 CPU ( TDDL 客户端模式),因此如果类 KV 查询能在执行过程中完全绕开上述处理过程,直接走 Memcached 协议去查 MySQL 数据,那么整个链路将被进一步精简,从而提升应用的业务吞吐量和 DB 查询容量。 沿着这个优化思路,TDDL/DRDS 在阿里巴巴集团内提供了 KV 功能,专门针对此类查询场景实现极致的性能优化。 压测验证效果 为了专门验证 TDDL/DRDS 的这一项优化在具体业务场景中的实际效果,我们与天猫某核心业务团队共同在今年双11的全链路压测中进行 SQL 与 KV 的流量切换验证。 KV场景 TDDL-KV QPS TDDL-SQL QPS 提升情况 备注说明 PK查询 1.7万 0.75万 PK吞吐提升124% PK类型是整数 UK查询 1.6万 0.7万 UK吞吐提升131% UK类型是字符串 二级索引查询 1.6万 0.7万 二级索引吞吐提升132% 平均每个二级索引的KV结果集是2行 在这次压测的过程中,应用层通过开关将集群QPS稳定在30w/s左右。然后,我们在 t1 时刻,将业务流量从走 KV 协议切回到走 SQL 协议,应用集群的 CPU 从 t1 时刻之后开始出现飙升,CPU从 46% 迅速升高到 63%,然后在 t2 时刻前后,业务再将流量从SQL切回KV,应用的 CPU 开始下降,整个过程持续5分钟,对比切换前后,同等QPS的流量,走 KV 比走 SQL 能节省 17% 左右的CPU,这个对于动则以万台来计算节点数量的核心应用而言,节省成本是明显的。 此外,TDDL/DRDS还做了更为纯粹的 KV 基准性能测试。在单纯的 KV 查询场景下,由于排除了业务处理逻辑的 CPU 开销,类 KV 查询走 KV 协议比走 SQL 协议吞吐提升会更为明显。 技术的创新点 在技术原理上,TDDL/DRDS 的类 KV 查询优化实现需要要依赖于 MySQL InnoDB Memcached 插件的特性。目前阿里巴巴集团 AliSQL 5.6 基于开源的 Memcached 插件代码支持了这一特性。 在 TDDL/DRDS 中,一个类 KV 查询走 SQL 接口与走 KV 接口却有着本质的不同,它们分别使用不同的端口来与MySQL进行通信。因此,这使TDDL在内部要维护两套不同的连接池,以及要处理两种不同的查询链路。 动态的分布式 KV 连接池 TDDL/DRDS 为保证 SQL 执行的稳定可靠,沉淀了各种成熟的保障机制,包括FailFast、主备切换、备库分流与连接池动态管理等等。这些机制为 TDDL/DRDS 的稳定性发挥着不可替代的作用。 同样为了保障 KV 优化功能在双11核心业务场景中稳定可靠,TDDL/DRDS 引入分布式 KV 连接池以及动态管理机制。 该机制的核心实现思想是 KV 连接池管理器会定时拉取相关配置信息,然后核对配置信息,如果发现有变更,自动对池中各个KV连接状态的进行相应的调整操作,例如完成KV的主备切换、备库分流、替换DB机器IP等等等。 TDDL/DRDS 采用这样的实现方案,一方面是为了保证 KV 连接池与 SQL 连接池的相互独立,另一方面是为保证 KV 连接池的变更能够与 SQL 连接池的变更保持协同。这样一旦 KV 连接池存在稳定性的风险,允许应用将流量及时切回 SQL 连接池并做到快速恢复,从而很好地管控风险。 此外,TDDL/DRDS 为 KV功能在稳定性上还做其它一些很有用的工作,例如,支持按分库灰度 KV ,这个特性允许单独对某个分库的查询流量在 SQL 协议与 KV 协议之间进行对应用透明的动态切换,这非常适合在 TDDL/DRDS 这种管理众多数据分片的场景下做流量的灰度验证。 优化的KV通信协议 原生的Memcached协议的查询结果默认使用“|”符号对一行记录的各个列进行分隔,使用这样的方式虽然简单,但缺点也显而易见。假如用户记录中含有“|”这种字符串或者因为中文乱码导致一些奇怪的字符,Memcached协议的结果的传输就会错乱,导致查询结果不正确。 TDDL/DRDS 为了解决这个问题,在原生 Memcached 协议的基础上进行了优化,设计了新的 KV 协议。新 KV 协议采用了更加普遍的通信协议设计方案,不再使用分隔符,而是改为固定长度字节的header描述一行记录中各个列值的长度,有效解决原生协议存在的问题。 KV 协议本身很简单,返回的数据包中只有数据本身,协议开销很低,并不像SQL协议,返回的数据包中除了含有结果集的数据外,还有相当部分是含有查询结果对应Meta信息(如每列的数据类型、列名、别名、表名和库名等等)。这些Meta信息会给SQL协议带来额外的CPU开销与网络开销,更严重的是,这些开销在KV查询的场景下会被放大,因为KV查询的返回结果通常是1~2条的记录,Meta的数据包在返回的数据包中的比重会明显增大,这并不太适全 KV 查询场景。因此,KV 协议更适合 KV 查询场景,这也是 TDDL/DRDS 的KV查询能做到吞吐优化的原因之一。 KV结果的自动类型转换 TDDL/DRDS 通过 KV 协议获取的数据都是字符串类型,直接返回给业务字符串类型数据不符合需求。因此,TDDL/DRDS 必须具备对查询结果各个列的字符串值进行自动类型转换的能力。与此同时,这个类型转换过程,必须严格遵循 MySQL 规范,才能良好适配 JDBC ResultSet 接口规范。 但是 KV 协议返回的数据包里并不含有列的元信息。因此,TDDL/DRDS 在解析 KV 返回结果之前,需要自己去获取表相关的Meta信息并进行缓存,这样,在解析过程中,就可以对结果按Meta进行类型转换。 后续的规划 TDDL/DRDS 目前还未在阿里云公共云或者私有云产品上输出这一特性,后续随着产品发展,我们慢慢会开放这种能力。另外产品层面,我们将会使用类Plan Cached方案,进一步优化性能,从而达到使用SQL转KV的链路如同直接使用KV一样损耗的效果。
文章
SQL · 关系型数据库 · 数据库连接 · 分布式数据库 · 数据库 · MySQL · 双11 · 测试技术 · 索引 · AliSQL
2017-12-27
【阿里在线技术峰会】罗龙九:云数据库十大经典案例分析
本文根据阿里云资深DBA专家罗龙九在首届阿里巴巴在线峰会的《云数据库十大经典案例分析》的分享整理而成。罗龙九以MySQL数据库为例,分析了自RDS成立至今,用户在使用RDS过程中最常见的问题,包括:索引、SQL优化、锁、延迟、参数优化、连接数、CPU、Iops、磁盘、内存等。罗龙九通过对十大经典案例的总结,还原问题原貌,给出分析问题的思路,旨在帮助用户在使用RDS的路上少一些弯路,多一些从容。 直播视频 (点击图片查看视频) 幻灯片下载:点此进入 以下为整理内容。 案例一:索引 今天之所以将索引放在第一位进行分享,是因为索引的问题出现频率是最高的。常见的索引问题包括:无索引、隐式转换两类。其中隐式转换是由SQL传入的值和表结构定义的数据类型不一致引起;或者是表字段定义的collation不一致导致多表join的时候出现隐式转换。无索引的情况会导致全表扫描;隐式转换会导致索引无法正常使用。 在使用索引时,我们可以通过explain(extended)查看SQL的执行计划,判断是否使用了索引以及发生了隐式转换。由于常见的隐式转换是由字段数据类型以及collation定义不当导致,因此我们在设计开发阶段,要避免数据库字段定义,避免出现隐式转换。此外,由于MySQL不支持函数索引,在开发时要避免在查询条件加入函数,例如date(gmt_create)。最后,所有上线的SQL都要经过严格的审核,创建合适的索引。   案例二:SQL优化 SQL优化是很多使用者都需要面对的问题。我们在不断地优化、调试过程中总结了三类SQL优化的最佳实践,分别是分页优化、子查询优化、查询需要的字段。 分页优化 这条语句是普通的Limit M、N的翻页写法,在越往后翻页的过程中速度越慢,这是由于MySQL会读取表M+N条数据,M越大,性能越差。 我们通过采用高效的Limit写法,可以将上述语句改写成: select t1.* from buyer t1, (select id from buyer sellerid=100 limit 100000,5000) t2 where t1.id=t2.id; 从而避免分页查询给数据库带来性能影响。需要注意一点是,这里需要在t表的sellerid字段上创建索引,id为表的主键。 子查询优化 子查询在MySQL5.1、5.5版本中都存在较大的风险。这是一段典型子查询SQL代码: SELECT first_name FROM employees WHERE emp_no IN (SELECT emp_no FROM salaries_2000 WHERE salary = 5000); 由于MySQL的处理逻辑是遍历employees表中的每一条记录,代入到子查询中去 。所以当外层employees表越大时,循环次数也随之增多,从而导致数据库性能的下降。 这是我们改写子查询之后的SQL代码: SELECT first_name FROM employees emp, (SELECT emp_no FROM salaries_2000 WHERE salary = 5000) sal WHERE emp.emp_no = sal.emp_no; 首先将子查询的结果放到临时表内,再去和employees表做关联。此外,使用者也可以选择使用Mysql 5.6的版本,避免麻烦的子查询改写。 查询需要的字段 在访问数据库时,应该尽量避免使用SELECT *查询所有字段数据,只查询需要的字段数据。   案例三:锁 在使用数据库时,每个人或多或少都会碰到锁的问题。在设计开发阶段,我们需要注意这三点问题:一是避免使用myisam存储引擎,改用innodb引擎;二是注意避免大事务,这是因为长事务导致事务在数据库中的运行时间加长,造成锁等待;三是选择将数据库升级到支持online ddl的MySQL 5.6版本。 在管理运维阶段,我们可以从四点出发搞定锁的问题: 在业务低峰期执行上述操作,比如创建索引,添加字段; 在结构变更前,观察数据库中是否存在长SQL,大事务; 结构变更期间,监控数据库的线程状态是否存在lock wait; RDS支持在DDL变更中加入 wait timeout。 案例四:延迟 由于数据库架构大多是主备的方式,延迟便成了一个常见的问题。产生延迟的原因有很多,例如在只读实例架构中,主备节点间MySQL原生复制实现数据同步方式会天然导致延迟的产生。此外,create index、repair等常见DDL操作、大事务、MDL锁以及资源问题都会导致延迟的出现。 处理延迟问题,需要具有清晰的排除思路:一看资源是否达到瓶颈;二看线程状态是否有锁;三判断是否存在大事务。同时我们还可以通过使用innodb存储引擎、将大事务拆分为小事务、DDL变更期间观察是否有大查询等具体最佳实践降低延迟。   案例五:参数优化 我们曾经遇到这样一个案例,某金融客户在将本地的业务系统迁移上云后,在最高配置的RDS上运行时间明显要比线下自建数据库运行时间慢1倍,进而导致客户系统出现割接延期的风险。对于这类案例的分析,根据以往的经验,可以从以下三点出发: 首先查看数据库是否是跨平台迁移(PG->MySQL、ORALCE->MySQL); 其次查看是否是跨版本升级(MySQL:5.1->5.5、5.5->5.6),不同的版本之间是有差异的; 如果上述两点都不存在,则需要查看具体的执行计划、优化器、参数配置、硬件配置。 如果SQL从云下迁移到云上或者从一个版本迁移到另一个版本的过程中出现性能问题时,要保持清晰的排查思路:从SQL执行计划到数据库版本和优化器规则,再到参数(包括Query_cache_size、Temp_table_size)配置和硬件配置等一一进行排查。曾经看到这样一个案例,一个用户使用默认的mysql配置跑线上应用,db所在的主机的内存有500G,但是分配给MySQL的内存确是默认的128M,导致了整个系统的性能下降。   案例六:CPU 100%最佳实践 导致CPU 100%的三大因素分别是:慢SQL、锁和资源。对于慢SQL问题:我们可以通过优化索引或者通过避免子查询、隐式转换以及进行分页改写等措施从根上解决该问题。对于锁等待问题:可以通过设计开发和管理运维优化锁等待。对于资源问题:可以通过参数优化、弹性升级、读写分离、数据库拆分等方式优化。 案例七:Conm 100% 导致Conm 100%的三大因素分别是慢SQL、锁、配置。对于慢SQL问题:解决方案类似于处理CPU 100%,同样是通过优化索引或者通过避免子查询、隐式转换以及进行分页改写等措施从根上解决该问题。对于锁等待问题:同样可以通过设计开发和管理运维优化锁等待。对于配置问题:我们需要合理规划数据库上的连接数的使用,避免客户端连接池参数配置超过实例最大连接数的情况出现。此外,还可以通过弹性升级RDS的规格配置来满足客户端需要的连接数。   案例八:Iops 100% Iops 100%也是一个很常见的问题。导致Iops 100%的原因也可以分为慢SQL问题、DDL、配置问题三类。对于慢SQL问题:解决方案同样类似于处理CPU 100%问题,通过优化索引或者通过避免子查询、隐式转换以及进行分页改写等措施从根上解决该问题。对于DDL问题:一定要避免并发进行create index、optimze table、alter table add column等操作;同时这些操作最好在业务低峰期进行。对于配置问题:可以通过弹性升级RDS的规格配置解决。   案例九:disk 100% 磁盘空间由数据文件、日志文件和临时文件组成。对于数据空间问题:由于数据文件的索引和数据是放在一起的,当对表删除数据后可以采用optimize table收缩表空间,同时删除不必要的索引;对于写多读少的应用,可以使用tokudb压缩引擎进行表压缩。对于日志空间问题:首先我们需要减少大字段的使用;其次可以使用truncate替代delete from。对于临时空间问题:一是可以适当地调大sort_buffer_size;二是可以创建合适索引避免排序。   案例十:mem 100% 当内存使用率达到100%时,操作系统会kill掉MySQL进程,从而导致业务的中断。因此,我们需要明确地了解数据库的内存使用详情。数据库内存主要由Buffer pool size 、Dictionary memory、Thread cost memory三部分组成。对于Buffer pool size问题:首先,我们可以通过创建合适的索引,避免大量的数据扫描;其次,我们需要去除不必要的索引,降低内存的消耗。对于Thread cost memory问题:一方面,我们可以通过创建合适的索引避免排序;另一方面,在查询数据时,我们只查询应用所需的数据,避免所有数据的查询。对于Dictionary memory问题:当表被访问打开后其元数据信息是存储在Dictionary memory之中的,过度的分表会导致内存的大量占用,因此分表时要注意把握分寸,不多过度分表,曾经看到一个数据库中创建了十几万张表。 关于分享嘉宾: 罗龙九,阿里云资深DBA专家,有着丰厚的DBA经验,经历阿里历年双11考验,负责阿里云RDS线上稳定以及专家服务团队,积累了6年对阿里云数据库用户的运维、调优、诊断等丰富的经验。
文章
SQL · 关系型数据库 · MySQL · 数据库 · 索引 · RDS · 运维 · 数据库管理 · 存储 · 双11
2016-07-21
Go database/sql 教程
Go使用SQL与类SQL数据库的惯例是通过标准库database/sql。这是一个对关系型数据库的通用抽象,它提供了标准的、轻量的、面向行的接口。不过database/sql的包文档只讲它做了什么,却对如何使用只字未提。快速指南远比堆砌事实有用,本文讲述了database/sql的使用方法及其注意事项。 1. 顶层抽象 在Go中访问数据库需要用到sql.DB接口:它可以创建语句(statement)和事务(transaction),执行查询,获取结果。 sql.DB并不是数据库连接,也并未在概念上映射到特定的数据库(Database)或模式(schema)。它只是一个抽象的接口,不同的具体驱动有着不同的实现方式。通常而言,sql.DB会处理一些重要而麻烦的事情,例如操作具体的驱动打开/关闭实际底层数据库的连接,按需管理连接池。 sql.DB这一抽象让用户不必考虑如何管理并发访问底层数据库的问题。当一个连接在执行任务时会被标记为正在使用。用完之后会放回连接池中。不过用户如果用完连接后忘记释放,就会产生大量的连接,极可能导致资源耗尽(建立太多连接,打开太多文件,缺少可用网络端口)。 2. 导入驱动 使用数据库时,除了database/sql包本身,还需要引入想使用的特定数据库驱动。 尽管有时候一些数据库特有的功能必需通过驱动的Ad Hoc接口来实现,但通常只要有可能,还是应当尽量只用database/sql中定义的类型。这可以减小用户代码与驱动的耦合,使切换驱动时代码改动最小化,也尽可能地使用户遵循Go的惯用法。本文使用PostgreSQL为例,PostgreSQL的著名的驱动有: github.com/lib/pq github.com/go-pg/pg github.com/jackc/pgx。 这里以pgx为例,它性能表现不俗,并对PostgreSQL诸多特性与类型有着良好的支持。既可使用Ad-Hoc API,也提供了标准数据库接口的实现:github.com/jackc/pgx/stdlib。 import ( "database/sql" _ "github.com/jackx/pgx/stdlib" ) 使用_别名来匿名导入驱动,驱动的导出名字不会出现在当前作用域中。导入时,驱动的初始化函数会调用sql.Register将自己注册在database/sql包的全局变量sql.drivers中,以便以后通过sql.Open访问。 3. 访问数据 加载驱动包后,需要使用sql.Open()来创建sql.DB: func main() { db, err := sql.Open("pgx","postgres://localhost:5432/postgres") if err != nil { log.Fatal(err) } defer db.Close() } sql.Open有两个参数: 第一个参数是驱动名称,字符串类型。为避免混淆,一般与包名相同,这里是pgx。 第二个参数也是字符串,内容依赖于特定驱动的语法。通常是URL的形式,例如postgres://localhost:5432。 绝大多数情况下都应当检查database/sql操作所返回的错误。 一般而言,程序需要在退出时通过sql.DB的Close()方法释放数据库连接资源。如果其生命周期不超过函数的范围,则应当使用defer db.Close() 执行sql.Open()并未实际建立起到数据库的连接,也不会验证驱动参数。第一个实际的连接会惰性求值,延迟到第一次需要时建立。用户应该通过db.Ping()来检查数据库是否实际可用。 if err = db.Ping(); err != nil { // do something about db error } sql.DB对象是为了长连接而设计的,不要频繁Open()和Close()数据库。而应该为每个待访问的数据库创建一个sql.DB实例,并在用完前一直保留它。需要时可将其作为参数传递,或注册为全局对象。 如果没有按照database/sql设计的意图,不把sql.DB当成长期对象来用而频繁开关启停,就可能遭遇各式各样的错误:无法复用和共享连接,耗尽网络资源,由于TCP连接保持在TIME_WAIT状态而间断性的失败等…… 4. 获取结果 有了sql.DB实例之后就可以开始执行查询语句了。 Go将数据库操作分为两类:Query与Exec。两者的区别在于前者会返回结果,而后者不会。 Query表示查询,它会从数据库获取查询结果(一系列行,可能为空)。 Exec表示执行语句,它不会返回行。 此外还有两种常见的数据库操作模式: QueryRow表示只返回一行的查询,作为Query的一个常见特例。 Prepare表示准备一个需要多次使用的语句,供后续执行用。 4.1 获取数据 让我们看一个如何查询数据库并且处理结果的例子:利用数据库计算从1到10的自然数之和。 func example() { var sum, n int32 // invoke query rows, err := db.Query("SELECT generate_series(1,$1)", 10) // handle query error if err != nil { fmt.Println(err) } // defer close result set defer rows.Close() // Iter results for rows.Next() { if err = rows.Scan(&n); err != nil { fmt.Println(err) // Handle scan error } sum += n // Use result } // check iteration error if rows.Err() != nil { fmt.Println(err) } fmt.Println(sum) } 整体工作流程如下: 使用db.Query()来发送查询到数据库,获取结果集Rows,并检查错误。 使用rows.Next()作为循环条件,迭代读取结果集。 使用rows.Scan从结果集中获取一行结果。 使用rows.Err()在退出迭代后检查错误。 使用rows.Close()关闭结果集,释放连接。 一些需要详细说明的地方: db.Query会返回结果集*Rows和错误。每个驱动返回的错误都不一样,用错误字符串来判断错误类型并不是明智的做法,更好的方法是对抽象的错误做Type Assertion,利用驱动提供的更具体的信息来处理错误。当然类型断言也可能产生错误,这也是需要处理的。 if err.(pgx.PgError).Code == "0A000" { // Do something with that type or error } rows.Next()会指明是否还有未读取的数据记录,通常用于迭代结果集。迭代中的错误会导致rows.Next()返回false。 rows.Scan()用于在迭代中获取一行结果。数据库会使用wire protocal通过TCP/UnixSocket传输数据,对Pg而言,每一行实际上对应一条DataRow消息。Scan接受变量地址,解析DataRow消息并填入相应变量中。因为Go语言是强类型的,所以用户需要创建相应类型的变量并在rows.Scan中传入其指针,Scan函数会根据目标变量的类型执行相应转换。 例如某查询返回一个单列string结果集,用户可以传入[]byte或string类型变量的地址,Go会将原始二进制数据或其字符串形式填入其中。但如果用户知道这一列始终存储着数字字面值,那么相比传入string地址后手动使用strconv.ParseInt()解析,更推荐的做法是直接传入一个整型变量的地址(如上面所示),Go会替用户完成解析工作。如果解析出错,Scan会返回相应的错误。 rows.Err()用于在退出迭代后检查错误。正常情况下迭代退出是因为内部产生的EOF错误,使得下一次rows.Next() == false,从而终止循环;在迭代结束后要检查错误,以确保迭代是因为数据读取完毕,而非其他“真正”错误而结束的。遍历结果集的过程实际上是网络IO的过程,可能出现各种错误。健壮的程序应当考虑这些可能,而不能总是假设一切正常。 rows.Close()用于关闭结果集。结果集引用了数据库连接,并会从中读取结果。读取完之后必须关闭它才能避免资源泄露。只要结果集仍然打开着,相应的底层连接就处于忙碌状态,不能被其他查询使用。 因错误(包括EOF)导致的迭代退出会自动调用rows.Close()关闭结果集(和释放底层连接)。但如果程序自行意外地退出了循环,例如中途break & return,结果集就不会被关闭,产生资源泄露。rows.Close方法是幂等的,重复调用不会产生副作用,因此建议使用 defer rows.Close()来关闭结果集。 以上就是在Go中使用数据库的标准方式。 4.2 单行查询 如果一个查询每次最多返回一行,那么可以用快捷的单行查询来替代冗长的标准查询,例如上例可改写为: var sum int err := db.QueryRow("SELECT sum(n) FROM (SELECT generate_series(1,$1) as n) a;", 10).Scan(&sum) if err != nil { fmt.Println(err) } fmt.Println(sum) 不同于Query,如果查询发生错误,错误会延迟到调用Scan()时统一返回,减少了一次错误处理判断。同时QueryRow也避免了手动操作结果集的麻烦。 需要注意的是,对于单行查询,Go将没有结果的情况视为错误。sql包中定义了一个特殊的错误常量ErrNoRows,当结果为空时,QueryRow().Scan()会返回它。 4.3 修改数据 什么时候用Exec,什么时候用Query,这是一个问题。通常DDL和增删改使用Exec,返回结果集的查询使用Query。但这不是绝对的,这完全取决于用户是否希望想要获取返回结果。例如在PostgreSQL中:INSERT ... RETURNING *;虽然是一条插入语句,但它也有返回结果集,故应当使用Query而不是Exec。 Query和Exec返回的结果不同,两者的签名分别是: func (s *Stmt) Query(args ...interface{}) (*Rows, error) func (s *Stmt) Exec(args ...interface{}) (Result, error) Exec不需要返回数据集,返回的结果是Result,Result接口允许获取执行结果的元数据 type Result interface { // 用于返回自增ID,并不是所有的关系型数据库都有这个功能。 LastInsertId() (int64, error) // 返回受影响的行数。 RowsAffected() (int64, error) } Exec的用法如下所示: db.Exec(`CREATE TABLE test_users(id INTEGER PRIMARY KEY ,name TEXT);`) db.Exec(`TRUNCATE test_users;`) stmt, err := db.Prepare(`INSERT INTO test_users(id,name) VALUES ($1,$2) RETURNING id`) if err != nil { fmt.Println(err.Error()) } res, err := stmt.Exec(1, "Alice") if err != nil { fmt.Println(err) } else { fmt.Println(res.RowsAffected()) fmt.Println(res.LastInsertId()) } 相比之下Query则会返回结果集对象*Rows,使用方式见上节。其特例QueryRow使用方式如下: db.Exec(`CREATE TABLE test_users(id INTEGER PRIMARY KEY ,name TEXT);`) db.Exec(`TRUNCATE test_users;`) stmt, err := db.Prepare(`INSERT INTO test_users(id,name) VALUES ($1,$2) RETURNING id`) if err != nil { fmt.Println(err.Error()) } var returnID int err = stmt.QueryRow(4, "Alice").Scan(&returnID) if err != nil { fmt.Println(err) } else { fmt.Println(returnID) } 同样的语句使用Exec和Query执行有巨大的差别。如上文所述,Query会返回结果集Rows,而存在未读取数据的Rows其实会占用底层连接直到rows.Close()为止。因此,使用Query但不读取返回结果,会导致底层连接永远无法释放。database/sql期望用户能够用完就把连接还回来,所以这样的用法很快就会导致资源耗尽(连接过多)。所以,应该用Exec的语句绝不可用Query来执行。 4.4 准备查询 在上一节的两个例子中,没有直接使用数据库的Query和Exec方法,而是首先执行了db.Prepare获取准备好的语句(prepared statement)。准备好的语句Stmt和sql.DB一样,都可以执行Query、Exec等方法。 准备语句的优势 在查询前进行准备是Go语言中的惯用法,多次使用的查询语句应当进行准备(Prepare)。准备查询的结果是一个准备好的语句(prepared statement),语句中可以包含执行时所需参数的占位符(即绑定值)。准备查询比拼字符串的方式好很多,它可以转义参数,避免SQL注入。同时,准备查询对于一些数据库也省去了解析和生成执行计划的开销,有利于性能。 占位符 PostgreSQL使用$N作为占位符,N是一个从1开始递增的整数,代表参数的位置,方便参数的重复使用。MySQL使用?作为占位符,SQLite两种占位符都可以,而Oracle则使用:param1的形式。 MySQL PostgreSQL Oracle ===== ========== ====== WHERE col = ? WHERE col = $1 WHERE col = :col VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3) 以PostgreSQL为例,在上面的例子中:"SELECT generate_series(1,$1)" 就用到了$N的占位符形式,并在后面提供了与占位符数目匹配的参数个数。 底层内幕 准备语句有着各种优点:安全,高效,方便。但Go中实现它的方式可能和用户所设想的有轻微不同,尤其是关于和database/sql内部其他对象交互的部分。 在数据库层面,准备语句Stmt是与单个数据库连接绑定的。通常的流程是:客户端向服务器发送带有占位符的查询语句用于准备,服务器返回一个语句ID,客户端在实际执行时,只需要传输语句ID和相应的参数即可。因此准备语句无法在连接之间共享,当使用新的数据库连接时,必须重新准备。 database/sql并没有直接暴露出数据库连接。用户是在DB或Tx上执行Prepare,而不是Conn。因此database/sql提供了一些便利处理,例如自动重试。这些机制隐藏在Driver中实现,而不会暴露在用户代码中。其工作原理是:当用户准备一条语句时,它在连接池中的一个连接上进行准备。Stmt对象会引用它实际使用的连接。当执行Stmt时,它会尝试会用引用的连接。如果那个连接忙碌或已经被关闭,它会获取一个新的连接,并在连接上重新准备,然后再执行。 因为当原有连接忙时,Stmt会在其他连接上重新准备。因此当高并发地访问数据库时,大量的连接处于忙碌状态,这会导致Stmt不断获取新的连接并执行准备,最终导致资源泄露,甚至超出服务端允许的语句数目上限。所以通常应尽量采用扇入的方式减小数据库访问并发数。 查询的微妙之处 数据库连接其实是实现了Begin,Close,Prepare方法的接口。 type Conn interface { Prepare(query string) (Stmt, error) Close() error Begin() (Tx, error) } 所以连接接口上实际并没有Exec,Query方法,这些方法其实定义在Prepare返回的Stmt上。对于Go而言,这意味着db.Query()实际上执行了三个操作:首先对查询语句做了准备,然后执行查询语句,最后关闭准备好的语句。这对数据库而言,其实是3个来回。设计粗糙的程序与简陋实现驱动可能会让应用与数据库交互的次数增至3倍。好在绝大多数数据库驱动对于这种情况有优化,如果驱动实现sql.Queryer接口: type Queryer interface { Query(query string, args []Value) (Rows, error) } 那么database/sql就不会再进行Prepare-Execute-Close的查询模式,而是直接使用驱动实现的Query方法向数据库发送查询。对于查询都是即拼即用,也不担心安全问题的情况下,直接Query可以有效减少性能开销。 5. 使用事务 事物是关系型数据库的核心特性。Go中事务(Tx)是一个持有数据库连接的对象,它允许用户在同一个连接上执行上面提到的各类操作。 事务基本操作 通过db.Begin()来开启一个事务,Begin方法会返回一个事务对象Tx。在结果变量Tx上调用Commit()或者Rollback()方法会提交或回滚变更,并关闭事务。在底层,Tx会从连接池中获得一个连接并在事务过程中保持对它的独占。事务对象Tx上的方法与数据库对象sql.DB的方法一一对应,例如Query,Exec等。事务对象也可以准备(prepare)查询,由事务创建的准备语句会显式绑定到创建它的事务。 事务注意事项 使用事务对象时,不应再执行事务相关的SQL语句,例如BEGIN,COMMIT等。这可能产生一些副作用: Tx对象一直保持打开状态,从而占用了连接。 数据库状态不再与Go中相关变量的状态保持同步。 事务提前终止会导致一些本应属于事务内的查询语句不再属于事务的一部分,这些被排除的语句有可能会由别的数据库连接而非原有的事务专属连接执行。 当处于事务内部时,应当使用Tx对象的方法而非DB的方法,DB对象并不是事务的一部分,直接调用数据库对象的方法时,所执行的查询并不属于事务的一部分,有可能由其他连接执行。 Tx的其他应用场景 如果需要修改连接的状态,也需要用到Tx对象,即使用户并不需要事务。例如: 创建仅连接可见的临时表 设置变量,例如SET @var := somevalue 修改连接选项,例如字符集,超时设置。 在Tx上执行的方法都保证同一个底层连接执行,这使得对连接状态的修改对后续操作起效。这是Go中实现这种功能的标准方式。 在事务中准备语句 调用Tx.Prepare会创建一个与事务绑定的准备语句。在事务中使用准备语句,有一个特殊问题需要关注:一定要在事务结束前关闭准备语句。 在事务中使用defer stmt.Close()是相当危险的。因为当事务结束后,它会释放自己持有的数据库连接,但事务创建的未关闭Stmt仍然保留着对事务连接的引用。在事务结束后执行stmt.Close(),如果原来释放的连接已经被其他查询获取并使用,就会产生竞争,极有可能破坏连接的状态。 6. 处理空值 可空列(Nullable Column)非常的恼人,容易导致代码变得丑陋。如果可以,在设计时就应当尽量避免。因为: Go语言的每一个变量都有着默认零值,当数据的零值没有意义时,可以用零值来表示空值。但很多情况下,数据的零值和空值实际上有着不同的语义。单独的原子类型无法表示这种情况。 标准库只提供了有限的四种Nullable type::NullInt64, NullFloat64, NullString, NullBool。并没有诸如NullUint64,NullYourFavoriteType,用户需要自己实现。 空值有很多麻烦的地方。例如用户认为某一列不会出现空值而采用基本类型接收时却遇到了空值,程序就会崩溃。这种错误非常稀少,难以捕捉、侦测、处理,甚至意识到。 空值的解决办法 使用额外的标记字段 database\sql提供了四种基本可空数据类型:使用基本类型和一个布尔标记的复合结构体表示可空值。例如: type NullInt64 struct { Int64 int64 Valid bool // Valid is true if Int64 is not NULL } 可空类型的使用方法与基本类型一致: for rows.Next() { var s sql.NullString err := rows.Scan(&s) // check err if s.Valid { // use s.String } else { // handle NULL case } } 使用指针 在Java中通过装箱(boxing)处理可空类型,即把基本类型包装成一个类,并通过指针引用。于是,空值语义可以通过指针为空来表示。Go当然也可以采用这种办法,不过标准库中并没有提供这种实现方式。pgx提供了这种形式的可空类型支持。 使用零值表示空值 如果数据本身从语义上就不会出现零值,或者根本不区分零值和空值,那么最简便的方法就是使用零值来表示空值。驱动go-pg提供了这种形式的支持。 自定义处理逻辑 任何实现了Scanner接口的类型,都可以作为Scan传入的地址参数类型。这就允许用户自己定制复杂的解析逻辑,实现更丰富的类型支持。 type Scanner interface { // Scan 从数据库驱动中扫描出一个值,当不能无损地转换时,应当返回错误 // src可能是int64, float64, bool, []byte, string, time.Time,也可能是nil,表示空值。 Scan(src interface{}) error } 在数据库层面解决 通过对列添加NOT NULL约束,可以确保任何结果都不会为空。或者,通过在SQL中使用COALESCE来为NULL设定默认值。 7. 处理动态列 Scan()函数要求传递给它的目标变量的数目,与结果集中的列数正好匹配,否则就会出错。 但总有一些情况,用户事先并不知道返回的结果到底有多少列,例如调用一个返回表的存储过程时。 在这种情况下,使用rows.Columns()来获取列名列表。在不知道列类型情况下,应当使用sql.RawBytes作为接受变量的类型。获取结果后自行解析。 cols, err := rows.Columns() if err != nil { // handle this.... } // 目标列是一个动态生成的数组 dest := []interface{}{ new(string), new(uint32), new(sql.RawBytes), } // 将数组作为可变参数传入Scan中。 err = rows.Scan(dest...) // ... 8. 连接池 database/sql包里实现了一个通用的连接池,它只提供了非常简单的接口,除了限制连接数、设置生命周期基本没有什么定制选项。但了解它的一些特性也是很有帮助的。 连接池意味着:同一个数据库上的连续两条查询可能会打开两个连接,在各自的连接上执行。这可能导致一些让人困惑的错误,例如程序员希望锁表插入时连续执行了两条命令:LOCK TABLE和INSERT,结果却会阻塞。因为执行插入时,连接池创建了一个新的连接,而这条连接并没有持有表锁。 在需要时,而且连接池中没有可用的连接时,连接才被创建。 默认情况下连接数量没有限制,想创建多少就有多少。但服务器允许的连接数往往是有限的。 用db.SetMaxIdleConns(N)来限制连接池中空闲连接的数量,但是这并不会限制连接池的大小。连接回收(recycle)的很快,通过设置一个较大的N,可以在连接池中保留一些空闲连接,供快速复用(reuse)。但保持连接空闲时间过久可能会引发其他问题,比如超时。设置N=0则可以避免连接空闲太久。 用db.SetMaxOpenConns(N)来限制连接池中打开的连接数量。 用db.SetConnMaxLifetime(d time.Duration)来限制连接的生命周期。连接超时后,会在需要时惰性回收复用。 ​ 9. 微妙行为 database/sql并不复杂,但某些情况下它的微妙表现仍然会出人意料。 9.1 资源耗尽 不谨慎地使用database/sql会给自己挖许多坑,最常见的问题就是资源枯竭(resource exhaustion): 打开和关闭数据库(sql.DB)可能会导致资源枯竭; 结果集没有读取完毕,或者调用rows.Close()失败,结果集会一直占用池里的连接; 使用Query()执行一些不返回结果集的语句,返回的未读取结果集会一直占用池里的连接; 不了解准备语句(Prepared Statement)的工作原理会产生许多额外的数据库访问。 9.2 Uint64 Go底层使用int64来表示整型,使用uint64时应当极其小心。使用超出int64表示范围的整数作为参数,会产生一个溢出错误: // Error: constant 18446744073709551615 overflows int _, err := db.Exec("INSERT INTO users(id) VALUES", math.MaxUint64) 这种类型的错误非常不容易发现,它可能一开始表现的很正常,但是溢出之后问题就来了。 9.3 不合预期的连接状态 连接的状态,例如是否处于事务中,所连接的数据库,设置的变量等,应该通过Go的相关类型来处理,而不是通过SQL语句。用户不应当对自己的查询在哪条连接上执行作任何假设,如果需要在同一条连接上执行,需要使用Tx。 举个例子,通过USE DATABASE改变连接的数据库对于不少人是习以为常的操作,执行这条语句,只影响当前连接的状态,其他连接仍然访问的是原来的数据库。如果没有使用事务Tx,后续的查询并不能保证仍然由当前的连接执行,所以这些查询很可能并不像用户预期的那样工作。 更糟糕的是,如果用户改变了连接的状态,用完之后它成为空连接又回到了连接池,这会污染其他代码的状态。尤其是直接在SQL中执行诸如BEGIN或COMMIT这样的语句。 9.4 驱动的特殊语法 尽管database/sql是一个通用的抽象,但不同的数据库,不同的驱动仍然会有不同的语法和行为。参数占位符就是一个例子。 9.5 批量操作 出乎意料的是,标准库没有提供对批量操作的支持。即INSERT INTO xxx VALUES (1),(2),...;这种一条语句插入多条数据的形式。目前实现这个功能还需要自己手动拼SQL。 9.6 执行多条语句 database/sql并没有对在一次查询中执行多条SQL语句的显式支持,具体的行为以驱动的实现为准。所以对于 _, err := db.Exec("DELETE FROM tbl1; DELETE FROM tbl2") // Error/unpredictable result 这样的查询,怎样执行完全由驱动说了算,用户并无法确定驱动到底执行了什么,又返回了什么。 9.7 事务中的多条语句 因为事务保证在它上面执行的查询都由同一个连接来执行,因此事务中的语句必需按顺序一条一条执行。对于返回结果集的查询,结果集必须Close()之后才能进行下一次查询。用户如果尝试在前一条语句的结果还没读完前就执行新的查询,连接就会失去同步。这意味着事务中返回结果集的语句都会占用一次单独的网络往返。 10. 其他 本文主体基于[[Go database/sql tutorial]]([Go database/sql tutorial]),由我翻译并进行一些增删改,修正过时错误的内容。转载保留出处。
文章
SQL · 关系型数据库 · 数据库连接 · Go · 数据库
2017-08-25
SQL Server 2005系列教学(5) 排序及常用聚集函数
数据的排序: 数据库里面的行以随机顺序存储,但我们有时为了使数据更大的发挥它的价值,让用户用过来更好方面,我们有时间需要对数据进行排序.比如,一个表我们可以按年龄从大到小或从小到排列,按性别重新排列,我们考过试后老师要给大家按照分数从高到低排名次等等 主键的作用仅仅是为了保证记录的唯一性,而不是对数据排序。 语法:select 字段名列表 from 表名 [where 条件] order by 排序字段1 [asc ] [desc] [排序字段2……] 作用:对指定表名满足条件的记录按照排序字段进行排序 Asc 升序 从小到大   Desc 降序  从大到小 如果不带则默认升序 select 字段名列表 from 表名  [where 条件] order by 排序字段名1 asc|desc  [,排序字段名2 asc|desc] [.....] 作用:对指定表中满足条件记录安照排序字段名1进行排序,那如果字段名1相同的再安照字段名2进行排序. asc 升序  默认为升序 desc 降序 对yuangong按照年龄进行排序 select * from yuangong order by 年龄 desc 按照性别进行排序: select * from yuangong order by 性别 按照姓名进行排序 select * from yuangong order by 姓名 先按照性别降序排列,性别相同的再按照应发工资升序 select * from yuangong order by 性别 desc,应发工资 asc  指定目标数据库 上面的示例中,我们并没有在SELECT语句中指定数据库,这意味着在执行SQL语句时,是在当前连接的数据库中查找数据。有时候我们需要查询当前连接的数据库以外的数据库中的数据,那么我们需要在SQL语句中指定目标数据库。 SQL Server和Access允许我们在表名前加上数据库名后跟两个点,来指定表所属的数据库: SELECT * FROM InstantUniversity..Student;     -- SQL Server 在Oracle中,如果想访问与当前连接的数据库不同的Schema数据库中的表,可以在表名前面使用Schema名作为前缀。例如: SELECT * FROM scott.EMP; SELECT * FROM db2admin.Employee; 表和列命名注释 有时由于不佳的数据库设计,访问的表名或列名包含有空格。在这种情况下,一些关系型数据库管理系统要求将表名或者列名包含在方括号或者双引号中间。在SQL Server中,需要使用方括号。例如: SELECT * FROM [Valued Customers]; -- SQL Server 或 Access 如果表名或者列名是一个SQL关键字或者是数据库厂商的保留字,那么需要在SQL语句中使用双引号或者方括号。使用保留关键字作为表名或者列名是一个不好的设计,应该尽量避免,但是随着将来SQL扩展或者数据库厂商的保留关键字列表的扩展,我们还是可能碰到类似情况。 注意:数据库中的保留字一般有特殊的意义,不能被重新定义。因此,不能使用保留字作为数据库对象的名字。例如,Oracle不允许ROWID作为列名或者表名。 例如,假设我们访问一个名为Language的表,后来,数据库厂商将Language增加进保留字里表,我们仍然可以使用如下的方法在查询中使用该表: SELECT * FROM [Language];    -- SQL Server 或 Access SELECT * FROM "LANGUAGE";    -- Oracle 注意上面第二行中使用大写字母作为表名的例子。如果在建Language表时没有使用双引号,Oracle会自动将其变为LANGUAGE。那么前述查询会成功,而使用SELECT * FROM "Language"会失败。相反,如果建表时候用双引号将Language括起来,那么使用SELECT * FROM "LANGUAGE"会失败。   使用别名 前面我们已经学习了如何从一个表中选择指定的列,以及如何控制列返回的顺序。除此以外,我们还可以为列或者表指定一个别名。换句话说,我们在返回的数据中按照我们自己设定的列名来引用列,而不是数据库存在的列名。 使用别名,可以使SQL语句和返回的数据更容易读懂、更容易理解。在多个表有同样的列名的时候,使用别名更容易区分。 在列名或表名用AS关键字来提供别名,语法如下: SELECT 列A AS A, 列B AS B, 列C AS C FROM 表名 AS T; 当使用表的别名的时,可以在同一SQL语句中的列名使用别名,例如: SELECT T.列A AS A, T.列B AS B, T.列C AS C FROM 表 AS T; 在涉及到多表的复杂查询且表名比较长的时候,使用别名可以使SQL查询更具可读性,并且占用更少空间。 SQL-99规范允许忽略AS关键字,这种格式被除Access以外的大多数RDBMS支持。例如: SELECT T.列A A, T.列B B, T.列C C FROM 表 T; 注意:SQL Server 也允许使用=运算符代替AS,但是别名和列名的位置相反。例如:新列名=已存在列。 例如,在InstantUniversity的Student表中执行如下查询: SELECT Name AS StudentName, StudentID AS ID FROM Student   过滤数据 前面我们所接触到的查询都是从一个表中返回所有的行。但是,我们经常需要根据指定条件,从一个表中抽取部分数据或者仅仅一行数据。SQL中包含了大量的工具,来过滤数据,确保此类查询的实现。 使用DISTINCT 排除重复数据 数据库表的同列中,经常有相同值多次出现。有时,我们需要确保返回唯一的数据行。例如,在一次购买中,我们不想在一个订单中对客户表中的客户两次收费。 在SELECT语句中使用DISTINCT关键字可以过滤重复的行数据。首先我们从单列中返回的数据行的简单情况着手。如果我们想要返回某列的唯一的行值,可以使用如下查询: SELECT DISTINCT 列A FROM 表; 排除重复数据经常是很有用的。例如,一个数据库中的单列可能只允许限制的值集合。为了查找该列的所有不同值,我们肯定不想遍历表中的每一行,仅仅是为了查找值是否存在。为更清楚地展示,假设我们想要查询IntantUniversity数据库的Exam表,得到所有考试的时间列表。我们可以使用如下查询:  SELECT SustainedOn from Exam; 查询结果集合如下: sustainedon                                            ------------------------------------------------------ 2003-03-12 00:00:00.000 2003-03-13 00:00:00.000 2003-03-11 00:00:00.000 2003-03-18 00:00:00.000 2003-03-19 00:00:00.000 2003-03-25 00:00:00.000 2003-03-26 00:00:00.000 2003-03-10 00:00:00.000 2003-03-21 00:00:00.000 2003-03-26 00:00:00.000 2003-03-24 00:00:00.000   (所影响的行数为 11 行) 我们可以看到总共得到11行,其中有1条行的值“2003-03-26”是重复的。但是如果使用如下语法: SELECT DISTINCT SustainedOn FROM Exam;       聚集函数: 有时我们需要对表中的数值型数据进行一些汇总操作,比如到了月底要看一下基本工资总和是多少,通知会计去银行取钱发工资,到了年龄要看一下年龄的最大的是谁,是否到了退休年龄。像这样的操作就要用到一些函数,叫聚集函数,下面我们介绍几个常用的聚集函数。 1、  求和函数:sum(字段名)      支持数据类型:数字   求员工表中所有员工的基本工资总和 Select sum(基本工资) from 员工表 求员工表中所有员工的应发工资总和及年龄总和 select sum(基本工资+补助-工服),sum(年龄) from 员工表 2、  求平均值函数:avg(字段名)       支持数据类型:数字。 求员工表中所有员工的平均年龄 Select avg(年龄) from 员工表 3、求最小值函数;min(字段名)        支持数据类型:数字、字符、datetime    作用:返回指定列中的最小数字,最早日期时间和最小的字符串    查询年龄最小的员工记录    Select min(年龄) from 员工表 而 Select 姓名,min(年龄) from 员工表 则是错误的。 4、求最大值函数;max(字段名)        支持数据类型:数字、字符、datetime    作用:返回指定列中的最大数字,最近日期时间和最大的字符串    查询基本工资最多的员工记录    Select max (基本工资) from 员工表   5、count(*)      支持任意数据类型    作用:统计结果集中全部记录行的数量   在结果集内使用分组 聚集函数非常好,可以完成一部分工作,但问题是你是否经常需要对整个表做汇总呢,我们一般会按照一定的条件来对数据进行分组汇总,如某个特定时间段,某个部门,某个地区,等等, 求每个部门的基本工资平均值 Select avg(基本工资) from 员工表 where 部门='人事部'  所以这个语句已经不行了。可以实现但是功能有限了。大家想想,在多个类似的数据集合进行统计时,不得不输入大量很类似的语句.要使用大量的代码,所以SQL SERVER就不再使用where语句.此时应该使用GROUP BY 短句.则如下: SELECT  相关字段信息 FROM 表名  GROUP 字段名 如: 求每个部门的基本工资平均值 Select  部门,avg(基本工资) from 员工表  group by 部门 只是注意在字段名列表中只能是相关字段信息,不能含有多余的字段,否则报错!  本文转自 dufei 51CTO博客,原文链接:http://blog.51cto.com/dufei/80751,如需转载请自行联系原作者
文章
SQL · Oracle · 关系型数据库 · 数据库 · 存储
2017-11-14
SQL语句性能调整原则
一、问题的提出   在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。   在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。   二、SQL语句编写注意问题   下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。   1. IS NULL 与 IS NOT NULL   不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。   任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。   2. 联接列   对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工。   下面是一个采用联接查询的SQL语句,   select * from employss where  first_name||''||last_name ='Beill Cliton';  上面这条语句完全可以查询出是否有Bill Cliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。   当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。   Select * from employee where  first_name ='Beill' and last_name ='Cliton'; 遇到下面这种情况又如何处理呢?如果一个变量(name)中存放着Bill Cliton这个员工的姓名,对于这种情况我们又如何避免全程遍历,使用索引呢?可以使用一个函数,将变量name中的姓和名分开就可以了,但是有一点需要注意,这个函数是不能作用在索引列上。下面是SQL查询脚本:   select * from employee where  first_name = SUBSTR('&&name',1,INSTR('&&name',' ')-1) and  last_name = SUBSTR('&&name',INSTR('&&name’,' ')+1) 本文转自 牛海彬 51CTO博客,原文链接:http://blog.51cto.com/newhappy/136968,如需转载请自行联系原作者
文章
SQL · 数据库 · 索引 · 关系型数据库 · Oracle
2017-11-07
解Bug之路-记一次中间件导致的慢SQL排查过程
解Bug之路-记一次中间件导致的慢SQL排查过程 前言 最近发现线上出现一个奇葩的问题,这问题让笔者定位了好长时间,期间排查问题的过程还是挺有意思的,正好博客也好久不更新了,就以此为素材写出了本篇文章。 Bug现场 我们的分库分表中间件在经过一年的沉淀之后,已经到了比较稳定的阶段。而且经过线上压测的检验,每秒能够执行1.7W条sql。但线上情况还是有出乎我们意料的情况。有一个业务线反映,每天有几条sql有长达十几秒的超时。而且sql是主键更新或主键查询,更奇怪的是出现超时的是不同的sql,似乎毫无规律可寻,如下图所示:一个值得注意的点,就是此业务只有一部分流量走我们的中间件,另一部分还是直接走数据库的,而超时的sql只会在连中间件的时候出现,如下图所示: 很明显,是引入了中间件之后导致的问题。 排查是否sql确实慢 由于数据库中间件只关心sql,并没有记录对应应用的traceId,所以很难将对应的请求和sql对应起来。在这里,我们先粗略的统计了在应用端超时的sql的类型是否会有超时的情况。分析了日志,发现那段时间所有的sql在往后端数据执行的时候都只有0.5ms,非常的快。如下图所示: 看来是中间件和数据库之间的交互是正常的,那么继续排查线索。 寻找超时规律 由于比较难绑定对应请求和中间件执行sql之间的关系,于是笔者就想着列出所有的异常情况,看看其时间点是否有规律,以排查一些批处理导致中间件性能下降的现象。下面是某几条超时sql业务方给出的信息: 业务开始时间 执行sql的应用ip 业务执行耗时(s) 2018-12-24 09:45:24 xx.xx.xx.247 11.75 2018-12-24 12:06:10 xx.xx.xx.240 10.77 2018-12-24 12:07:19 xx.xx.xx.138 13.71 2018-12-24 22:43:07 xx.xx.xx.247 10.77 2018-12-24 22:43:04 xx.xx.xx.245 13.71 看上去貌似没什么规律,慢sql存在于不同的应用ip之上,排除某台应用出问题的可能。超时时间从早上9点到晚上22点都有发现超时,排除了某个点集中性能下降的可能。 注意到一个微小的规律 笔者观察了一堆数据一段时间,终于发现了一点小规律,如下面两条所示: 业务开始时间 执行sql的应用ip 业务执行耗时(s) 2018-12-24 22:43:07 xx.xx.xx.247 10.77 2018-12-24 22:43:04 xx.xx.xx.245 13.71 这两笔sql超时对应的时间点挺接近的,一个是22:43:07,一个是22:43:04,中间只差了3s,然后与后面的业务执行耗时相加,发现更接近了,让我们重新整理下: 业务开始时间 执行sql的应用ip 业务执行耗时(s) 业务完成时间(s) 2018-12-24 22:43:07 xx.xx.xx.247 10.77 22:43:17.77 2018-12-24 22:43:04 xx.xx.xx.245 13.71 22.43:17.71 发现这两笔业务虽然开始时间不同,但确是同时完成的,这可能是个巧合,也可能是bug出现导致的结果。于是继续看下是否有这些规律的慢sql,让业务又提供了最近的慢sql,发现这种现象虽然少,但是确实发生了不止一次。笔者突然感觉到,这绝对不是巧合。 由上述规律导致的思考 笔者联想到我们中间件有好多台,假设是中间件那边卡住的话,如果在那一瞬间,有两台sql同时落到同一台的话,中间件先卡住,然后在中间件恢复的那一瞬间,以0.5ms的速度执行完再返回就会导致这种现象。如下图所示: 当然了还有另一种可能,就是sql先以0.5ms的速度执行完,然后中间件那边卡住了,和上面的区别只是中间件卡的位置不同而已,另一种可能如下图所示: 是否落到同一台中间件 线上一共4台中间件,在经历了一堆复杂线上日志捞取分析相对应之后,发现那两条sql确实落在了同一台中间件上。为了保证猜想无误,又找了两条符合此规律的sql,同样的也落在同一台中间件上面,而且这两台中间件并不是同一台,排除某台机器有问题。如下图所示: 业务日志和中间件日志相对照 在上述发现的基础上,又经历了各种日志分析对应之后,终于找到了耗时sql日志和业务日志对应的关联。然后发现一个关键信息。中间件在接收到sql时候会打印一条日志,发现在应用发出sql到接收到sql还没来得及做后面的路由逻辑之前就差了10s左右,然后sql执行到返回确是非常快速的,如下图所示: 查看对应中间件那个时间点其它sql有无异常 笔者捞取了那个时间点中间件的日志,发现除了这两条sql之外,其它sql都很正常,整体耗时都在1ms左右,这又让笔者陷入了思考之中。 再从日志中找信息 在对当前中间件的日志做了各种思考各种分析之后,又发现一个诡异的点,发现在1s之内,处理慢sql对应的NIO线程的处理sql数量远远小于其它NIO线程。更进一步,发现在这1s的某个时间点之前,慢sql所在的NIO线程完全不打印任何日志,如下图所示: 同时也发现两条sql都落在对应的Reactor-Thread-2的线程里面,再往前回溯,发现近10s内的线程都没有打印任何信息,好像什么都没处理。如下图所示: 感觉离真相越来越近了。这边就很明显了,reactor线程被卡住了! 寻找reactor线程为何被卡住 笔者继续顺藤摸瓜,比较了一下几个卡住的reactor线程最后打印的日志,发现这几条日志对应的sql都很快返回了,没什么异常。然后又比较了一下几个卡住的reactor线程恢复后打印出来的第一条sql,发现貌似它们通过路由解析起来都很慢,达到了1ms(正常是0.01ms),然后找出了其对应的sql,发现这几条sql都是150K左右的大小,按正常思路,这消失的10s应该就是处理这150K的sql了,如下图所示: 为何处理150K的sql会耗时10s 排查是否是网络问题 首先,这条sql在接入中间件之前就有,也就耗时0.5ms左右。而且中间件在往数据库发送sql的过程中也是差不多的时间。如果说网络有问题的话,那么这段时间应该会变长,此种情况暂不考虑。 排查是否是nio网络处理代码的问题 笔者鉴于可能是中间件nio处理代码的问题,构造了同样的sql在线下进行复现,发现执行很快毫无压力。笔者一度怀疑是线上环境的问题,traceroute了一下发现网络基本和线下搭建的环境一样,都是APP机器直连中间件机器,MTU都是1500,而且中间没有任何路由。思路一下又陷入了停滞。 柳暗花明 思考良久无果之后。笔者觉得排查一下是否是构造的场景有问题,突然发现,线上是用的prepareStatement,而笔者在命令行里面用的是statement,两者是有区别的,prepare是按照select ?,?,?带参数的形式而statement直接是select 1,2,3这样的形式。 而在我们的中间件中,由于后端的数据库对使用prepareStatement的sql具有较大的性能提升,我们也支持了prepareStatement。而且为了能够复用原来的sql解析代码,我们会在接收到对应的sql和参数之后将其还原成不带?的sql算出路由到的数据库节点后,再将原始的带?的sql和参数prepare到对应的数据库,如下图所示: 重新构造prepareStatement场景 笔者重新构造了prepareStatement场景之后,发现在150K的sql下,确实耗时达到了10s,感觉终于见到曙光了。 最终原因字符串拼接 由于是线下,在各种地方打日志之后,终于发现耗时就是在这个将带?的sql渲染为不带问号的sql上面。下面是代码示意: String sql="select ?,?,?,?,?,?...?,?,?..."; for(int i=0 ; i < paramCount;i++){ sql = sql.replaceFirst("\\?",param[i]); } return sql; 这个replaceFirst在字符串特别大,需要替换的字符频率出现的特别多的时候方面有巨大的性能消耗。之前就发现replaceFirst这个操作里面有正则的操作导致特殊符号不能正确渲染sql(另外参数里面带?也不能正确渲染),于是其改成了用split ?的方式进行sql的渲染。但是这个版本并没有在此应用对应的集群上使用。可能也正是这些额外的正则操作导致了这个replaceFirst性能在这种情况下特别差。 对应优化 将其改成新版本后,新代码如下所示: String splits[] = sql.split("\\?"); String result=""; for(int i=0;i<splits.length;i++){ if(i<paramNumber){ result+=splits[i]+getParamString(paramNumber); }else{ result+=splits[i]; } } return result; 这个解析时间从10s下降到了2s,但感觉还是不够满意。 经同事提醒,试下StringBuilder。由于此应用使用的是jdk1.8,笔者一直觉得jdk1.8已经可以直接用原生的字符串拼接不需要用StringBuilder了。但还是试了一试,发现从2s下降到了8ms! 改成StringBuilder的代码后如下所示: String splits[] = sql.split("\\?"); StringBuilder builder = new StringBuilder(); for(int i=0;i<splits.length;i++){ if(i<paramNumber){ builder.append(splits[i]).append(getParamString(paramNumber)); }else{ builder.append(splits[i]); } } return builder.toString(); 笔者查了下资料,发现jdk 1.8虽然做了优化,但是每做一次拼接还是新建了一个StringBuilder,所以在大字符串频繁拼接的场景还是需要用一个StringBuilder,以避免额外的性能损耗。 总结 IO线程不能做任何耗时的操作,这样会导致整个吞吐量急剧下降,对应分库分表这种基础组件在编写代码的时候必须要仔细评估,连java原生的replaceFirst也会在特定情况下出现巨大的性能问题,不能遗漏任何一个点,否则就是下一个坑。 每一次复杂Bug的分析过程都是一次挑战,解决问题最重要也是最困难的是定位问题。而定位问题需要的是在看到现象时候能够浮现出的各种思路,然后通过日志等信息去一条条否决自己的思路,直至达到唯一的那个问题点。
文章
SQL · 监控 · 中间件 · 数据库 · Java · 测试技术
2018-12-29
SQL Server profile使用技巧
介绍 经常会有人问profile工具该怎么使用?有没有方法获取性能差的sql的问题。自从转mysql我自己也差不多2年没有使用profile,忽然profile变得有点生疏不得不重新熟悉一下。这篇文章主要对profile工具做一个详细的介绍;包括工具的用途和使用方法等。profile是SQLServer自带的一个性能分析监控工具,它也可以生成数据库引擎优化顾问分析需要的负载数据,比如开发对功能进行调试需要收集执行sql使用profile就是一个非常好的办法,profile主要用于在线实时监控和收集数据用于后期的分析使用,它可以将收集的数据保存成文件和插入到表。     跟踪属性 一、常规 将跟踪的记录保存到指定的文件。 1.最大文件大小 指定最大文件大小的跟踪在达到最大文件大小时,会停止将跟踪信息保存到该文件。使用此选项可将事件分组成更小、更容易管理的文件。此外,限制文件大小使得无人参与的跟踪运行起来更加安全,因为跟踪会在达到最大文件大小后停止。可以为通过 Transact-SQL 存储过程或使用 SQL Server Profiler创建的跟踪设置最大文件大小。 最大文件大小选项的上限为 1 GB。默认最大文件大小为 5 MB 注意:最大文件的大小建议不要设的太大,特别是需要用于数据库引擎优化顾问使用的文件,太大的跟踪文件需要很长的分析的时间而且由于数据库引擎优化顾问也是把收集的负载文件执行一遍有时候可能会导致负载过大分析失败,同时对服务器的压力持续的时间过长对业务影响也会比较大,默认大小即可,同时启动文件滚动更新,多次分析。 2.启用文件滚动更新 如果使用文件滚动更新选项,则在达到最大文件大小时,SQL Server 会关闭当前文件并创建一个新文件。新文件与原文件同名,但是文件名后将追加一个整数以表示其序列。例如,如果原始跟踪文件命名为 filename_1.trc,则下一跟踪文件为 filename_2.trc,依此类推。如果指定给新滚动更新文件的名称已经被现有文件使用,则将覆盖现有文件,除非现有文件为只读文件。默认情况下,将跟踪数据保存到文件时,会启用文件滚动更新选项。 3.服务器处理跟踪数据 确保服务器记录每个跟踪事件,如果记录事件会显著降低性能,可以清除服务器处理跟踪数据,这样服务器不会再记录事件。 4.最大行数 指定有最大行数的跟踪在达到最大行数时,会停止将跟踪信息保存到表。每个事件构成一行,因此该参数可设置收集的事件数的范围。设置最大行数使得无人参与的跟踪运行起来更加方便。例如,如果需要启动一个将跟踪数据保存到表的跟踪,同时希望在该表变得过大时停止跟踪,则可以使其自动停止。 如果已指定并且达到了最大行数,将在运行 SQL Server Profiler的同时继续运行跟踪,但不再记录跟踪信息。SQL Server Profiler将继续显示跟踪结果,直到跟踪停止 5.启用跟踪停止时间  启用跟踪停止时间之后,到了指定的时间跟踪自动停止。每一次跟踪建议都必须得设置一个跟踪停止时间防止忘记关闭跟踪导致服务器空间被占满,默认跟踪1小时。   注意: 从 SQL Server 2005 开始,服务器以微秒(百万分之一秒或 10-6 秒)为单位报告事件的持续时间,以毫秒(千分之一秒或 10-3 秒)为单位报告事件使用的 CPU 时间。 在 SQL Server 2000 中,服务器以毫秒为单位报告持续时间和 CPU 时间。 在 SQL Server 2005 及更高版本中,SQL Server Profiler图形用户界面默认以毫秒为单位显示“持续时间”列,但是当跟踪保存到文件或数据库表中之后,将以微秒为单位在“持续时间”列中写入值。 二、事件选择 对于不同跟踪选择不同的跟踪事件;通过勾选“显示所有跟踪事件”可以看到所有的跟踪事件,总共有21个事件分类。用得最多的两个分类就是存储过程和TSQL这两个分类主要用来记录执行的存储过程和SQL语句,把鼠标移动到具体的事件上面会显示该事件和事件列的具体说明,接下来就分析几个常用的事件和常用的事件列。 1.显示所有跟踪事件 勾选之后会将所有的事件都显示出来 2.显示所有列 勾选之后会将所有的列显示出来 3.列筛选 对列增加一些条件,其实可以将它理解在TSQL语句的WHERE后面添加条件,对于整形列直接输入数值即可,对于字符串列就相当于like一样使用不带引号的%%模糊匹配方法。通过勾选“排除不包含值的行”之后跟踪结果就会筛选掉不满足条件的记录。 4.列组织 列组织可以理解成TSQL语句里面做GROUP BY操作,可以将相同的条件放在一起去重。   事件 1.SQL:Stmt******* [SQL:StmtStarting]:启动TSQL语句时记录 [SQL:StmtCompleted]:完成TSQL语句时记录 这两事件的区别也同单词的意思一样,StmtStarting是记录事件的开始不关注这个事件在接下来会做什么,StmtCompleted是记录事件结束之后在开始和结束这个过程中做的一些操作比如一些常用的列"Duration","Cpu","Reads","Writes","EndTime"这些列就会出现在StmtCompleted事件中。所以如果你需要收集的记录不关心整个事件过程中的操作只需要收集数量那么可以使用Starting事件比如记录某个语句或者存储过程执行的次数等。 2.SQL:Batch****** [SQL:BatchStarting]:启动TSQL批处理时记录 [SQL:BatchCompleted]:完成TSQL批处理时记录   这次我把两个select语句放在一起来执行,可以从batch事件中可以看到它记录的整个批处理的SQL同时还包括相关注释,同时整个批处理两个TSQL作为一条事件记录,而stmt事件记录具体的TSQL语句把两个TSQL语句作为两条记录来记录。同时还可以发现两个TSQL的Duration相加是小于整个批处理的duration的,这也是正常的整个批处理在sql编译分析执行这块肯定比单个TSQL需要耗费更多的时间,但是相差也是非常的小。   batchcompleted事件多用于引擎优化顾问,而stmtcompleted事用于分析单个TSQL语句。同样Stored分类里面的starting事件和completed事件和TSQL里面的是一样的意思。 事件列 列举常用的事件列 TextData:文本详细信息,比如详细的执行SQL语句等等。 ApplicationName:连接SQLSever的客户端应用程序名称。 NTUserName:windows用户名 LoginName:SQLServer登入用户名。 CPU:事件占用的CPU时间,在图形化界面但是是毫秒(千分之一秒或 10-3 秒),在文本文件或者数据库表中单位是微妙(百万分之一秒或 10-6 秒)。 Reads:执行逻辑读的次数。 Writes:物理磁盘写入的次数。 Duration:事件的持续时间,也就是统计信息里面显示的占用时间,在图形化界面但是是毫秒(千分之一秒或 10-3 秒),在文本文件或者数据库表中单位是微妙(百万分之一秒或 10-6 秒) ClientProcessID:调用SQLServer的应用程序进程ID。 SPID:SQLServer为连接分配的数据库进程ID,也就是sys.processes里面记录的进程ID。 StartTime:事件的开始时间。 EndTime:事件的结束时间。 DBUserName:客户端的sqlserver用户名。 DatabaseID:如果指定了USE database就是指定的数据库id,否则就是默认的数据库id(也就是master的数据库id)。所以该列的作用不是很大。 Error:事件的错误号,通常是sysmessage中存储的错误号。 ObjectName:正在引用的对象名称。 三、自带跟踪模板 工具自带了几个比较实用的跟踪模板,一般的跟踪都可以直接使用自带的跟踪模板解决,同时自己也可以创建自定义的跟踪事件和跟踪属性保存成模板供以后使用。 SP_Counts:计算已运行的存储过程数,并且按存储过程的名称进行分组统计,此模板可以分析某时间段存储过程的行为。 Standard:记录所有存储过程和T-SQL语句批处理运行的时间,当你想要监视常规数据库服务器活动时即可使用该模板,一般的跟踪需要使用该模板就可以解决,这也是默认的模板。 TSQL:记录客户端提交给sqlserver的所有T-SQL语句的的内容和开始时间,通常使用该模板用于程序调试。 TSQL_Duration:记录客户端提交给sqlserver的所有T-SQL语句批处理信息以及执行这些语句所需的时间(毫秒),并按时间进行分组,使用该模板可以分析执行慢的查询,此模板的跟踪记录可以用于数据库引擎优化顾问分析使用。 TSQL_Grouped:按提交客户端和登入用户进行分组记录所有提交给SQLServer的T-SQL批处理语句及其开始时间,此模板用于分析某个客户或者用户执行的查询。 TSQL_Locks:记录所有开始和完成的存储过程和T-SQL语句,同时记录死锁信息,此模板用于跟踪死锁。 TSQL_Replay:记录有关已发出的T-SQL语句的详细信息,此模板记录重播跟踪所需的信息,此模板可执行跌到优化,例如基准测试。 TSQL_SPs:记录有关执行的所有存储过程的详细信息,此模板可以分析存储过程的组成步骤。如果你怀疑正在重新编译存储过程,请添加SP:Recomple事件 Tuning:记录有关存储和T-SQL语句批处理的信息以及执行这些语句所需的时间(毫秒),使用此模板生产跟踪输出可用于数据库引擎优化顾问工作负载来优化索引、优化性能。此模板和TSQL_Druation相似后者是做了时间分组。   数据库引擎优化顾问 1.如果需要用数据库引擎优化顾问分析跟踪事件记录必须捕获了以下跟踪事件: RPC:Completed SQL:BatchCompleted SP:StmtCompleted 也可以使用这些跟踪事件的 Starting 版本。 例如,SQL:BatchStarting。 但是,这些跟踪事件的 Completed 版本包括 Duration 列,它能使数据库引擎优化顾问更有效地优化工作负荷。 数据库引擎优化顾问不优化其他类型的跟踪事件。 2.包含 LoginName列 数据库引擎优化顾问在优化过程中提交显示计划请求。 当包含 LoginName 数据列的跟踪表或跟踪文件被用作工作负荷时,数据库引擎优化顾问将模拟 LoginName 中指定的用户。 如果没有为此用户授予 SHOWPLAN 权限(该权限使用户能够为跟踪中包含的语句执行和生成显示计划),数据库引擎优化顾问将不会优化这些语句。  避免为跟踪的 LoginName 列中指定的每个用户授予 SHOWPLAN 权限 通过从未优化的事件中删除 LoginName 列来创建新的工作负荷,然后只将未优化的事件保存到新的跟踪文件或跟踪表中。 将不带 LoginName 列的新工作负荷重新提交到数据库引擎优化顾问。 数据库引擎优化顾问将优化新的工作负荷,因为跟踪中未指定登录信息。 如果某个语句没有相应的 LoginName,数据库引擎优化顾问将通过模拟启动优化会话的用户(sysadmin 固定服务器角色或 db_owner 固定数据库角色的成员)来优化该语句。 3.数据库引擎优化顾问不能执行下列操作: 建议对系统表建立索引。 添加或删除唯一索引或强制 PRIMARY KEY 或 UNIQUE 约束的索引。 优化单用户数据库。 4.数据库引擎优化顾问具有下列限制: 数据库引擎优化顾问通过数据采样收集统计信息。因此,在相同的工作负荷上重复运行该工具可能生成不同的结果。 数据库引擎优化顾问不能用于优化 Microsoft SQL Server 7.0 或更早版本的数据库中的索引。 如果为优化建议指定的最大磁盘空间超过了可用空间,数据库引擎优化顾问将使用指定的值。但是,当您执行建议脚本来实施它时,如果未先添加更多磁盘空间,则脚本会失败。可以使用 dta 实用工具的 -B 选项指定最大磁盘空间,也可以通过在“高级优化选项”对话框中输入值来指定最大磁盘空间。 为了安全起见,数据库引擎优化顾问不能优化驻留在远程服务器上的跟踪表中的工作负荷。若要解除此限制,可以选择以下选项之一: 使用跟踪文件而不使用跟踪表。 将跟踪表复制到远程服务器。 当强制实施约束时,例如为优化建议指定最大磁盘空间时强制的约束(通过使用 -B 选项或“高级优化选项”对话框),数据库引擎优化顾问可能会被迫删除某些现有的索引。在此情况下,生成的数据库引擎优化顾问建议可能生成负的预期提高值。 指定限制优化时间的约束时(通过使用 dta 实用工具的 -A 选项或通过选择“优化选项”选项卡上的“限制优化时间”),数据库引擎优化顾问可能超过该时间限制,以便针对到当时为止已处理的工作负荷,生成精确预期的提高值和分析报告。 5.数据库引擎优化顾问可能在下列情况下不提供建议: 正在优化的表所包含的数据页数少于 10。 建议的索引对当前物理数据库设计的查询性能预计带来的提高值不够。 运行数据库引擎优化顾问的用户不是 db_owner 数据库角色或 sysadmin 固定服务器角色的成员。工作负荷中的查询在运行数据库引擎优化顾问的用户的安全上下文中进行分析。该用户必须是 db_owner 数据库角色的成员。 6.数据库引擎优化顾问可能在下列情况下不提供分区建议: 未启用 xp_msver 扩展存储过程。此扩展存储过程用于提取要优化的数据库所在服务器上的处理器数目以及可用内存。请注意,安装 SQL Server 后,默认情况下,此扩展存储过程处于打开状态。有关详细信息,请参阅了解外围应用配置器和 xp_msver (Transact-SQL)。 7.性能注意事项 在分析过程中,数据库引擎优化顾问可能占用相当多的处理器及内存资源。若要避免降低生产服务器速度,请采用下列策略之一: 在服务器空闲时优化数据库。数据库引擎优化顾问可能影响维护任务性能。 使用测试服务器/生产服务器功能。有关详细信息,请参阅减轻生产服务器优化负荷。 指定数据库引擎优化顾问仅分析物理数据库设计结构。数据库引擎优化顾问提供许多选项,但是请仅指定所需选项。 注意:由于数据库引擎优化顾问进行性能优化时也是将负载记录中的语句执行一篇查询分析执行计划的操作,所以对服务器同样存在压力。特别是对于大的负载分析可能需要分析一个小时甚至更长,这样可能会持续对服务器造成压力,所以避免在业务高峰期进行使用引擎优化顾问进行负载分析。 实例  接下来就列举三个案例,使用数据库引擎优化顾问来分析跟踪记录优化索引的案例、监控死锁的案例、创建自定义跟踪模板案例。 案例1:优化索引 1.创建测试数据 --创建测试表 CREATE TABLE [dbo].[book]( [id] [int] NOT NULL PRIMARY KEY, [name] [varchar](50) NULL); --插入10W条测试数据 DECLARE @id int SET @id=1 WHILE @id<100000 BEGIN INSERT INTO book values(@id,CONVERT(varchar(20),@id)) SET @id=@id+1 END; 2.创建跟踪 这里使用默认的跟踪模板“tuning” 1.创建好跟踪后点击运行即可,事件选择这里保持默认 2.执行SQL SELECT * FROM book WHERE name='10001'; 由于name字段没有建索引所以该查询执行计划分析过后会返回创建name字段的索引,通过引擎优化顾问分析同样如此 3.停止跟踪 在使用数据库引擎优化顾问分析负载跟踪之前必须先停止跟踪。 4.打开数据库引擎优化顾问 可以直接在profile的工具栏选择打开,“文件”选择刚才的跟踪文件,“负载数据库”选择需要进行优化的数据库,“选择要优化的数据库和表”也就需要优化的数据库的相关表。优化选项没有特别的需求选择默认即可,然后点击“开始分析”。 引擎优化顾问会自动生成创建索引的脚步,同时还给出了创建该索引之后预计性能可以提供的百分比,如果同时存在很多表的索引建议可以勾选需要保存的建议保存成sql文件在“开始分析”栏旁边有一个保存建议的按钮可以将建议保存成sql文件。 建议: 1.数据库引擎优化顾问给出的建议不是每一个都是对的,自己对比该SQL的执行频率来判断是否需要创建该索引,比如我当前这个SQL如果我这个SQL只执行了一次后面就不会再执行了那么这个索引就没必要创建了。 2.修改引擎优化顾问给出的索引名,数据库引擎优化顾问给出的创建索引的索引名不够直观,建议自己手动更改,比如改成“ix_book_name”,“索引标示_表名_字段描述”的规则。 3.用来分析的文件不要太大否则可能会分析不完成,不要在业务高峰期进行分析。 案例2:监控死锁 1.创建跟踪   模板选择自带的“TSQL_Locks”模板,运行跟踪。 2.执行SQL 打开两个会话窗口分表执行如下SQL,先在会话1执行然后在10S内在会话2中执行,两个会话拥有各自的排他锁同时又去申请对方拥有的排他锁造成死锁。 会话1执行:当前会话1是62 BEGIN TRANSACTION UPDATE book SET name='a' WHERE ID=10 --延时10s执行 waitfor delay '0:0:10' UPDATE book SET name='a' WHERE ID=100 会话2执行:当前会话2是 BEGIN TRANSACTION UPDATE book SET name='b' WHERE ID=100 --延时20执行 waitfor delay '0:0:20' UPDATE book SET name='b' WHERE ID=10 msms客户端返回的错误消息显示当前62会话作为死锁的牺牲品。 3.跟踪分析死锁  死锁跟踪事件使用图形和直观的返回了两个会话的死锁,其中62会话用了一个×表示当前的会话是死锁的牺牲品。 案例三:创建自定义跟踪模板  标准模板就是一个比较好的参考模板,比如我们对执行语句进行监控就可以参考标准模板在其基础上修改保存成自己的模板。 1.创建TSQL语句跟踪 2.创建跟踪模板 停止当前的TSQL跟踪,选择“文件”-“另存为跟踪模板”就可以保存成自己的跟踪模板。 3.列筛选   当前是筛选跟踪的TSQL语句中包含book,这里的列筛选这执行 where like 的语法类似。 整形列的话就不需要带模糊条件: 注意:如果要取消列筛选记得把刚才的筛选条件删除同时把“排除不包含值的行” 的勾选也去除,记得两者都要去掉否则跟踪还是包含筛选的跟踪。 4.列组织 列组织其实就是按某列进行分组显示跟踪,类似select查询里面的group by操作。比如我当前按持续时间进行分组跟踪。 通过对持续时间进行分组,相同的持续时间会放在一个分组里。 总结  由于篇幅有限列举了一些简单常用的操作,其它的分类监控的方法类似有兴趣可以多去研究,profile是非常实用且界面化很好的监控工具这也是SQLServer独特的条件,应该熟练运用。 本文转自pursuer.chen(陈敏华)博客园博客,原文链接:http://www.cnblogs.com/chenmh/p/6305448.html,如需转载请自行联系原作者
文章
SQL · 存储 · 监控 · 数据库 · 索引 · 安全 · 测试技术 · 关系型数据库 · MySQL · Windows
2015-06-15
经验之谈:内存问题造成数据库性能异常怎么破?
云栖号:https://yqh.aliyun.com第一手的上云资讯,不同行业精选的上云企业案例库,基于众多成功案例萃取而成的最佳实践,助力您上云决策! 导读:在使用数据库的过程中,内存不足常常会引起数据库异常。但是内存不足,又会为数据库带来哪些具体的影响呢?本次,我们将通过某客户现场数据库在某个时段内性能严重下降的案例来展示由于主机内存不足而造成数据库日志写入卡顿的问题分析过程。通过本案例,我们也可以对相关问题的分析方法及解决建议有一些深入的了解。 问题描述 2020年1月15号凌晨2点左右客户产线异常,应用后台消息报错业务处理超时。此外,在16号凌晨2点左右和下午2点左右,也发生业务处理超时,影响较大。 故障时段数据库的等待事件信息如下: 问题分析 查看数据库故障时间段的ash信息,可以看到确实在1:56~1:57分的时候,等待比较严重: select trunc(sample_time, 'mi'), count(1) from gv$active_session_history where sample_time >= to_date('2020-01-16 01:50:00', 'yyyy-mm-dd hh24:mi:ss') and sample_time < to_date('2020-01-16 01:59:00', 'yyyy-mm-dd hh24:mi:ss') and event is not null group by trunc(sample_time, 'mi') order by 1; 进一步分析等待事件,可以看到log file sync等待排名第一,而其他等待事件很少。因此主要是log filesync等待事件发了超时: select inst_id, event, count(1) from gv$active_session_history where sample_time >= to_date('2020-01-16 01:50:00', 'yyyy-mm-dd hh24:mi:ss') and sample_time < to_date('2020-01-16 01:58:00', 'yyyy-mm-dd hh24:mi:ss') and event is not null group by inst_id, event order by 1, 3 desc; 进一步查看lgwr 的 trace,但没有发现异常信息。 继续查看log file sync等待信息,可以看到都是被同一条SQL的会话阻塞。该SQL对应的文本为insert into xxx……,是用于业务写日志的语句,体现在应用日志上就是卡在进程刚开始的时候超时的执行。 由于告警日志和LGWR TRACE里都没有异常信息,于是我们可以查看那条SQL的执行情况,发现故障时点每次执行时间变长了。 继续查询故障时段log file sync、LGWR wait for redo copy等待事件直方图信息。从这条insert sql执行历史信息,调用次数并没有突增的情况,但是log filesync/LGWR wait for redo copy等待抖动比较严重: 根据故障处理经验来判断,LGWR抖动比较严重,怀疑物理IO出现了问题。 分析排查物理IO问题,IO没看到异常情况,所以在这里排除了IO引起的日志写入抖动的问题。 查询故障时段SQL占用CPU排名的情况 而该sql_id的sql_text则是: 对故障时间点的ASH报告进行分析,故障时间点这个select 1 from dual占用的cpu最高,这个sql一般是weblogic等中间件测试连接池连接用的,一般不会引起CPU的使用问题,且总体CPU使用率并没有撑满。故在这里可以排除CPU使用影响的情况,由于这套数据库平时内存的使用率就是98%左右,只剩2G空闲内存,而故障时点,只剩几百兆内存。 因此,分析到这里基本可以定位是内存消耗过高引起的问题,这里考虑到触发故障的时间点有高度规律性,于是考虑可能是由于一些定时任务引起的,于是检查了crontab,job定时任务、备份等,但都没发现有故障时间的运行的信息。 这个时候考虑数据库主机层面上定时任务和进程分析一些信息,由于以前出现故障的时候,有让客户开启oswatch采集,故这次也同样从osw中top的采样时间进行检查,且最终发现在异常时osw的采样时间也变长了,说明故障出现的时候整个操作系统都有受影响。 查看osw中ps的信息 将osw采样的时间加大到42秒的和正常15秒内的两个时间进行比对,可以发现故障出现的事件点内,多出来的一个进程是CVU的JAVA进程。故怀疑是cvu的java进程对主机的内存造成了大量的消耗。 查看cvu的运行日志,可以看到cvu是6小时执行一次,而在1:56和13:56的时候主机确实都运行了这个进程。当然,cvu服务停用并不影响数据库实例正常使用,只是在运行cluvfy时有调用到,而在01:56:25~01:57:07这个中间系统中是卡着的,且内存的free一直减少。 从vmstat上看,也验证了异常时主要是内存问题,交换空间页面切换和系统调用次数也在加大。 综合以上的分析,可以确认是cvu定时调用导致内存消耗过大,而内存本身就不足,在调用的那一瞬间引起了数据库主机内存抖动,引起了数据库主机的卡顿,临时处理方法是停止cvu服务,在之后的跟踪中没有发现同样的故障发生,故障处理完成。 cvu定时任务是集群软件调用cvu工具定时检查集群运行状态,记录到日志文件中的。它的运行导致现有服务器内存资源过于紧张,导致几乎所有进程都变慢。 问题解决 本次案例出现的主要原因是由于cvu定时任务进程的调用导致现有服务器内存资源过于紧张,引起了数据库主机内存抖动,造成数据库卡顿。临时处理方法是停止cvu服务,在之后的跟踪中没有发现同样的故障发生,故障处理完成。 云栖号在线课堂,每天都有产品技术专家分享立即加入圈子:https://c.tb.cn/F3.Z8gvnK与专家面对面,及时了解课程最新动态! 原文发布时间:2020-03-10本文作者:罗贵林本文来自:“数据和云公众号”,了解相关信息可以关注“数据和云”
文章
SQL · 运维 · Java · 中间件 · 应用服务中间件 · 数据库
2020-03-11
U-SQL 介绍 —— 大数据处理语言
微软宣布了新的 Azure 数据湖(Azure Data Lake)服务,该服务被用于云分析,包括了一个超大规模信息库;一个在 YARN 上建立的新的的分析服务,该服务允许数据开发者和数据科学家分析全部的数据;还有 HDInsight,一个全面管理 Hadoop、Spark、Storm 和 HBase 的服务。Azure 数据湖分析包括 U-SQL,这个语言综合了 SQL 的优点与你自己所写代码的表现能力。U-SQL 的可扩展分布式查询功能让你可以有效地分析存储器或关联存储器(比如 AzureSQL 数据库(Azure SQL Database))内的数据。这篇博文中,我将会概述 U-SQL 的开发目标、我们的一些灵感和这个语言背后的设计理念,同时我会向你展示一些用于说明这个语言几个主要方面的例子。 为什么我们需要 U-SQL? 如果你分析了的大数据分析的特点,就会很容易的产生一些有关易用性的需求,一个功能强大的语言应该: 可以处理任何类型的数据。例如从安全日志中分析僵尸网络(BotNet)攻击模式,通过机器学习来提取图像和视频中的特征,这种语言需要允许你操作任何类型的数据。 使用自定义的代码很容易地表现复杂的(经常是表现某公司自营业务的)算法。举例来说,基础的查询语言通常不易表达自定义的过程,从用户定义的函数到他们自定义的输入输出格式都是如此。 对任何尺度的数据进行有效的缩放,你无须再关心扩展拓扑(scale-out topology)、管道代码或特定分布式基础设施的限制。 现有的大数据语言该如何处理这些需求? 基于 SQL 的语言(例如 Hive 等)提供声明性的方式,原生支持扩容,并行执行以及优化。这个特性使得其简单易用,被开发人员广泛使用;其功能强大,适用于很多标准的分析及仓储类型。不过他们的扩展模型和对非结构化数据及文件的支持经常只是些附属功能,不容易使用。比如,即使您只想快速浏览一下文件或者远程数据,您也需要在查询之前先创建编目对象,将其系统化。这点严重降低了语言的敏捷性。虽然基于 SQL 的语言通常有一些扩展点来定制格式,定义函数以及聚合,但是他们的构建,集成和维护相当复杂,各种编程语言的支持也差别很大。 用基于编程语言的方式来处理大数据,这样方式可以简单方便地添加定制化代码。但是,程序员通常需要另外编写代码来处理扩容和性能,并且难以管理执行拓扑和工作流。比如不同执行阶段的并发或者架构扩容。这样的代码不仅难写,而且不易优化性能。有些框架支持声明式组件,例如集成语言查询,或者嵌入式 SQL 支持。但是 SQL 可能会被当做字符串处理,没有辅助工具。并且可扩展性集成较差,由于程序性代码并不考虑副作用,所以较难优化,而且不能重用。 综合考虑基于 SQL 的语言以及程序语言,我们设计出了 U-SQL,他用 C# 编写,具备声明性 SQL 语言原生的可扩展性,又对其进一步扩展。集各种范式于一身,集结构化,非结构化,远程数据处理于一身,集声明式以及定制化命令编程于一身,集语言扩展能力于一身。 U-SQL 构建在微软的 SCOP 经验以及其他语言例如 T-SQL,ANSI SQL 以及 Hive 的基础之上。例如,我们对 SQL 和编程语言的集成,执行以及对 U-SQL 框架的优化都基于 SCOPE,这使得每天可以运行成千上万个作业。我们也会对调整系统元数据(数据库,表等等),SQL 语法,T-SQL,ANSI SQL 等 SQL Server 用户所熟悉的语言语义等支持,是的他们可以协同通过。 我们使用C#数据类型即表达式支持,这样您可以在 SELECT 里无缝潜入 C# 谓词及表达式,从而植入业务逻辑。最后,通过对 Hive 以及其他大数据语言数据模式,处理需求等研究,将其集成到我们的框架里。 简言之,基于现有语言和经验的 U-SQL 语言,有利于您简单地处理复杂问题。 展示 U-SQL! 我们假设我已经下载了所有我的 Twitter 历史记录,包括:我推送的,转发的,提到的。并且作为一个 CSV 文件上传到我的 Azure Data Lake Store。 在这里例子里,我知道我想要处理数据的结构,第一步我只想分组查询出每个作者的推送数据的数量: @t = EXTRACT date string , time string , author string , tweet string FROM "/input/MyTwitterHistory.csv" USING Extractors.Csv(); @res = SELECT author , COUNT(*) AS tweetcount FROM @t GROUP BY author; OUTPUT @res TO "/output/MyTwitterAnalysis.csv" ORDER BY tweetcount DESC USING Outputters.Csv(); 上面的 U-SQL 脚本显示了三个使用 U-SQL 处理数据的主要步骤: 提取。从你的源数据中提取数据。 简单的说就是使用 EXTRACT 申明查询,概述数据。申明的数据类型基于 C# 的数据类型,这里我使用了内置的 Extractors 类库去读取和schematize(系统化) CSV 文件。 转换。使用 SQL 并且(或者)用户自定义操作 (你可以转换为其他时间)。在上面的例子中, 使用了一个常见的 SQL 表达式,做了一个 GROUP BY 聚合操作。 输出。结果既可以是文件,也可以输出到一个 U-SQL 表格保存在内存中用作后续的处理。 注意 U-SQL 的 SQL 关键字和 C# 的语法表达式的区别是使用大写来提供语法区别,可能两者是一样的,但是如果大写就是 U-SQL 的关键字,他们的文字一样但是可能有不同的意义。 还要注意每个表达式分配了一个变量 (@t , @res)。允许 U-SQL 通过一步步的表达式(增量表达式流),递增的转换和编译数据,增量表达式是使用函数式 lambda 表达式组成(与 Pig 语言类似)。执行框架,并且编译所有的表达式为一个的表达式。这个表达式可以是全局最优化,所以扩展的方式不可能是逐行执行表达式。下面的图标通过向你显示图表的方式,显示在这篇博文中的下一个查询: 回到我们的例子,我现在想要添加额外的信息:关于推文中提及的人,并且扩展我的聚合去返回在我的 tweet 网中出现的频率,还有他们的推文中提及我的频率。因为我可以使用 C# 去操作数据,所以我可以使用 C#LINQ 表达式扩展一个 ARRAY。这时我把得到的数组使用 EXPLODE 传入数据集,并且使用 CROSS APPLY 操作应用 EXPLODE 到每条数据。我使用 UNION 操作联合作者,但是需要使用@重新给变量赋值。 这里使用另外一个C#表达式(这里我使用了substring从第一位开始取数据-翻译认为应该是排除@符的意思) . @t = EXTRACT date string , time string , author string , tweet string FROM "/input/MyTwitterHistory.csv" USING Extractors.Csv(); @m = SELECT new SQL.ARRAY<string>( tweet.Split(' ').Where(x => x.StartsWith("@"))) AS refs FROM @t; @t = SELECT author, "authored" AS category FROM @t UNION ALL SELECT r.Substring(1) AS r, "mentioned" AS category FROM @m CROSS APPLY EXPLODE(refs) AS Refs(r); @res = SELECT author , category , COUNT(*) AS tweetcount FROM @t GROUP BY author, category; OUTPUT @res TO "/output/MyTwitterAnalysis.csv" ORDER BY tweetcount DESC USING Outputters.Csv(); 下一步,我可以使用 Visual Studio 的 Azure 数据湖工具里的代码转换功能将 C# 代码重构成 C# 函数。提交脚本后,系统会将代码自动部署到服务器上。 我们也可以在U-SQL元数据目录里部署和注册代码集。这样,我们或者其他人可以不时重用这组脚本。以下例子表示了,在代码集注册为TweetAnalysis之后,如何找到函数。 REFERENCE ASSEMBLY TweetAnalysis; @t = EXTRACT date string , time string , author string , tweet string FROM "/input/MyTwitterHistory.csv" USING Extractors.Csv(); @m = SELECT Tweets.Udfs.get_mentions(tweet) AS refs FROM @t; @t = SELECT author, "authored" AS category FROM @t UNION ALL SELECT Tweets.Udfs.cleanup_mentions(r) AS r, "mentioned" AS category FROM @m CROSS APPLY EXPLODE(refs) AS Refs(r); @res = SELECT author , category , COUNT(*) AS tweetcount FROM @t GROUP BY author, category; OUTPUT @res TO "/output/MyTwitterAnalysis.csv" ORDER BY tweetcount DESC USING Outputters.Csv(); 由于之前提到了除了删除@标识符之外,还想要对 mentions 做其他清理工作,所以该代码集还包含了一个 cleanup_mentions 函数,用来在删除@之后做其他处理。 为什么选择U-SQL 通过上述介绍,希望您已经窥测到了我们对 U-SQL 的想法,以及 U-SQL 是如何将查询以及大数据处理变得简单的。接下来的几周里,我们会进一步介绍该语言的设计逻辑,并会给出一些示例代码即应用场景,请参见Azure 博客大数据专题。我们会深入探讨一下特性: 通过模式匹配操作文件集合 使用(分区)表 对 SQL Azure 数据库进行联邦查询 将 U-SQL 代码封装为视图,表型数据函数,过程 SQL 窗体函数 使用C#编写用户定义操作(定制抽取,处理过程) 复合类型(MAP,ARRAY) 使用 U-SQL 树立数据管道 U-SQL 对物联网分析中lambda架构的支持 U-SQL 将大数据处理变得简单: 统一了声明行查询语言与复杂代码 统一了结构化查询与非结构化数据 统一了本地和远程查询 一旦上线,即可提高生产力和敏捷性 不只是 U-SQL - Auzre 数据湖为您的所有数据创建生产力 我们旨在将 Azure 数据湖建立成最有用的集编写,调试和优化各种规模数据功能的强大工具,U-SQL只是其中一个环节。通过对编写与监控 Hive 作业的全面支持, 我们开发了基于 C# 的编码模型,他可以创建用于流处理的 Storm 作业,而且从创建,到执行,支持作业生命周期的每一阶段。Azure 数据湖使得您可以专心于业务逻辑而不是调试分布式环境。我们的目标就是将大数据技术变得更加简单,被更多数人使用:大数据专家,工程师,数据科学家,分析师以及应用开发人员。 文章转载自 开源中国社区[https://www.oschina.net]
文章
SQL · 大数据 · C# · 数据库 · HIVE · 语音技术 · 流计算 · 分布式计算 · 程序员 · 算法
2017-06-06
跳转至:
阿里中间件
80373 人关注 | 38 讨论 | 693 内容
+ 订阅
  • 德勤正式加入阿里云原生合作伙伴计划,强强联手开创数字化咨询新风向
  • 阿里云 Serverless App Engine(SAE)助力升学在线从容应对流量高峰
  • 游戏打包过程枯燥且工作繁琐,如何提升打包效率?看鲸旗游戏的新思路
查看更多 >
数据库
87794 人关注 | 33970 讨论 | 27368 内容
+ 订阅
  • 256变4096:分库分表扩容如何实现平滑数据迁移?
  • 分享一个高弹性的架构
  • Java单元测试之 Apache HBase
查看更多 >
开发与运维
3685 人关注 | 91413 讨论 | 88096 内容
+ 订阅
  • 开年赢好运!最高6000元程序员加油包等你免费拿
  • 德勤正式加入阿里云原生合作伙伴计划,强强联手开创数字化咨询新风向
  • 基于WebRTC的互动直播实践
查看更多 >
安全
695 人关注 | 21383 讨论 | 26424 内容
+ 订阅
  • 技术干货 | mPaaS 小程序高玩带你起飞:客户端预置小程序无视网络质量
  • 德勤正式加入阿里云原生合作伙伴计划,强强联手开创数字化咨询新风向
  • 舞台现场直播技术实践
查看更多 >
人工智能
1963 人关注 | 7206 讨论 | 33373 内容
+ 订阅
  • 人工智能如何改变制造业和工业物联网?
  • 德勤正式加入阿里云原生合作伙伴计划,强强联手开创数字化咨询新风向
  • 基于WebRTC的互动直播实践
查看更多 >