022-2345 2937
185 2247 0110
business@forenose.com
QQ:2779623375
用户QQ群3:606835039
用户QQ群4:795287153
订阅号
服务号
客服微信:qianxiu0106
ForeSpider
数据采集引擎
科技资讯:
科技学院:
科技百科:
科技书籍:
网站大全:
软件大全:
UpdateRule | 修改规则 RemoveRule | Route 删除规则 | 获取路由后的结果 |
---|
Match | 查找指定的库、表匹配到的规则 Remove | AllRules 删除规则 | 返回所有的规则 |
---|
PatternTable | 匹配规则的表的模式 | 可以设置为指定的表名,也可以使用通配符 “*” 和 “?” | |||
---|---|---|---|---|---|
SourceColumn | 需要转化的列 | 列名 | |||
TargetColumn | 转化后的值保存到哪个列 | 列名 Expression | Arguments 转化表达式 | 转化所需要的参数 目前只支持 PartitionID | Expression 为 PartitionID ,参数为 InstanceID 、schema 名称前缀、table 名称前缀以及前缀与 ID 的分割符号 |
UpdateRule | 修改规则 RemoveRule | HandleRowValue 删除规则 | 获取转化结果 |
---|
TablePattern | 匹配规则的表的模式 | 可以设置为指定的表名,也可以使用通配符 “*” 和 “?” | |||
---|---|---|---|---|---|
Events | 规则适用于哪些类型的 binlog | binlog event 的类型 SQLPattern | Action 匹配的 SQL 的模式 | 是否对符合上面要求的 binlog 进行过滤 SQL 语句的模式,支持适用正则表达式 | Ignore 或者 Do |
UpdateRule | 修改规则 RemoveRule | Filter 删除规则 | 判断指定的 binlog 是否应该过滤 |
---|
$ wget http://cdn.sequoiadb.com/images/sequoiadb/x86_64/sequoiadb-3.2.1-linux_x86_64.tar.gz $ tar -zxvf sequoiadb-3.2.1-linux_x86_64.tar.gz $ cd sequoiadb-3.2.1/ $ sudo ./setup.sh |
---|
$ sudo su sdbadmin $ /opt/sequoiadb/tools/deploy/quickDeploy.sh |
---|
$ /opt/sequoiadb/bin/sdb > db = new Sdb() ; > db.updateConf ( { transactionon: true, transisolation: 1 } ) ; > quit ; $ /opt/sequoiadb/bin/sdbstop $ /opt/sequoiadb/bin/sdbstart |
---|
$ sudo apt-get update $ sudo apt-get install apache2 php libapache2-mod-php php-mysql unzip php-xml |
---|
$ wget https://wordpress.org/wordpress-5.2.1.tar.gz |
---|
$ tar -zxvf wordpress-5.2.1.tar.gz $ cd wordpress $ sudo rm /var/www/html/* $ sudo cp -R * /var/www/html/ $ sudo cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php $ sudo chown www-data:www-data /var/www/html/* |
---|
$ sudo vi /var/www/html/wp-config.php define( 'DB_NAME', 'database_name_here' ); 变为 define( 'DB_NAME', 'wordpress’ ); define( 'DB_USER', 'username_here' ); 变为 define( 'DB_USER', ‘sequoiadb’ ); define( 'DB_PASSWORD', 'password_here' ); 变为 define( 'DB_PASSWORD', 'sequoiadb' ); define( 'DB_HOST', 'localhost' ); 变为 define( 'DB_HOST', ‘<服务器IP地址>’ ); |
---|
$ sudo su sdbadmin $ /opt/sequoiasql/mysql/bin/mysql -S /opt/sequoiasql/mysql/database/3306/mysqld.sock -u root mysql> create user 'sequoiadb'@'localhost' identified by 'sequoiadb'; mysql> create database wordpress; mysql> grant all on wordpress.* to ‘sequoiadb'@’localhost'; mysql> grant all privileges on *.* to 'sequoiadb'@'%' identified by 'sequoiadb' with grant option; mysql> exit |
---|
$ /opt/sequoiadb/bin/sdb > db=new Sdb() ; > db.snapshot(SDB_SNAP_CATALOG) ; …… { "_id": { "$oid": "5cecf121116eae6117df17dc" }, "Name": "wordpress.wp_posts", "UniqueID": 4294967308, "Version": 1, "ReplSize": -1, "Attribute": 1, "AttributeDesc": "Compressed", "CompressionType": 1, "CompressionTypeDesc": "lzw", "ShardingKey": { "ID": 1 }, "EnsureShardingIndex": false, "ShardingType": "hash", "Partition": 4096, "InternalV": 3, "CataInfo": [ { "ID": 0, "GroupID": 1000, "GroupName": "group1", "LowBound": { "": 0 }, "UpBound": { "": 1365 } }, { "ID": 1, "GroupID": 1001, "GroupName": "group2", "LowBound": { "": 1365 }, "UpBound": { "": 2730 } }, { "ID": 2, "GroupID": 1002, "GroupName": "group3", "LowBound": { "": 2730 }, "UpBound": { "": 4096 } } ], "AutoSplit": true, "AutoIncrement": [ { "SequenceName": "SYS_4294967308_ID_SEQ", "Field": "ID", "Generated": "default", "SequenceID": 11 } ] } …… |
---|
2 | Jann | 1996-01-04 | |||
---|---|---|---|---|---|
3 | Ray | 1996-01-08 | |||
4 | Michael | 1996-01-10 | |||
5 | Jack | 1996-01-13 6 | 7 Steven | Lily 1996-01-23 | 1996-01-25 |
CHANGE | 可以重命名列或者修改列的定义 | 标准SQL的扩展 MODIFY | 通过文档介绍的功能,我们就基本能够判断处该使用使用哪种语法,CHANGE功能最强大,什么情况下都可以使用(达到预期的效果)。但是还有一个区别: 可以更改列的定义,但不能更改列的名称 | 兼容Oracle的扩展 | ALTER 语法只是修改 .frm 文件,不会去更新表中的数据; MODIFY和CHANGE在更新表结构的时候重新插入表中的数据,因此比较耗费时间。 所以,当只需要修改某一列的默认值的时候,优先选择用ALTER,需要修改列的名称用CHANGE,只修改列的定义用MIODIFY 如果修改的列上有索引,修改完后最好重建一下索引 数据库 2019-06-04 21:19:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 1.1 基础概念 MVCC,Multi-Version Concurrency Control,多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。 如果有人从数据库中读数据的同时,有另外的人写入数据,有可能读数据的人会看到『半写』或者不一致的数据。有很多种方法来解决这个问题,叫做并发控制方法。最简单的方法,通过加锁,让所有的读者等待写者工作完成,但是这样效率会很差。MVCC 使用了一种不同的手段,每个连接到数据库的读者, 在某个瞬间看到的是数据库的一个快照 ,写者写操作造成的变化在写操作完成之前(或者数据库事务提交之前)对于其他的读者来说是不可见的。 当一个 MVCC 数据库需要更一个一条数据记录的时候,它不会直接用新数据覆盖旧数据,而是将旧数据标记为过时(obsolete)并在别处增加新版本的数据。这样就会有存储多个版本的数据,但是只有一个是最新的。这种方式允许读者读取在他读之前已经存在的数据,即使这些在读的过程中半路被别人修改、删除了,也对先前正在读的用户没有影响。这种多版本的方式避免了填充删除操作在内存和磁盘存储结构造成的空洞的开销,但是需要系统周期性整理(sweep through)以真实删除老的、过时的数据。对于面向文档的数据库(Document-oriented database,也即半结构化数据库)来说,这种方式允许系统将整个文档写到磁盘的一块连续区域上,当需要更新的时候,直接重写一个版本,而不是对文档的某些比特位、分片切除,或者维护一个链式的、非连续的数据库结构。 MVCC 提供了时点(point in time)一致性视图。MVCC 并发控制下的读事务一般使用 时间戳 或者 事务 ID 去标记当前读的数据库的状态(版本),读取这个版本的数据。读、写事务相互隔离,不需要加锁。读写并存的时候,写操作会根据目前数据库的状态,创建一个新版本,并发的读则依旧访问旧版本的数据。 一句话讲,MVCC就是用 同一份数据临时保留多版本的方式 的方式,实现并发控制。 这里留意到 MVCC 关键的两个点: 在读写并发的过程中如何实现多版本; 在读写并发之后,如何实现旧版本的删除(毕竟很多时候只需要一份最新版的数据就够了); 1.2 实现 MVCC 使用时间戳(TS)、递增的事务 ID(T)实现事务一致性。 MVCC 通过维护多版本数据,保证一个读事务永远不会被阻塞。对象 P 维护有多个版本,每个版本会有一个读时间戳(Read TimeStamp, RTS)和 写时间戳(Write TimeStamp, WTS),事务 Ti 读对象 P 的最新版本,该版本早于事务 Ti 的读时间戳 RTS(Ti)。 事务 Ti 要对 P 执行写操作,如果有其他事务 Tk 同时对 P 操作,则 RTS(Ti)必须要早于 RTS(Tk),即有 RTS(Ti) < RTS(Tk),这样对 Ti 对 P 的写操作才能完成。一般地,如果其他事务拥有 P 的一个更早的读时间戳的情况下,写操作是不能完成的。打个比方就是在存储前面有一道线,只有等你前面的人的完成了他们的事务,你的修改事务才可以提交完成。 重复说一下:每个对象 P 有一个时间戳 TS,如果事务 Ti 想要对 P 执行写操作,(写要先读)事务的读时间戳是 RTS(Ti),如果有其他事务拥有一个比较早的时间戳,有 TS(P) < RTS(Ti),这时事务 Ti 会退出并重新开始。否则,事务 Ti 创建一个 P 的新版本,并设置新版本 P 的时间戳,似的 TS = TS(Ti)。 MVCC 系统明显的缺点是会存储多个版本数据的冗余开销。但同时,读操作永不会被阻塞,这对那些以读操作为主的数据库来说非常重要。MVCC 实现了真的快照隔离(snapshot isolation),然后其他的并发控制方法要么是不完整的快照隔离方式,要么需要较高的性能损耗。 Wikipedia 中的内容有点繁琐,简单地,上面的描述,阐明了在同一数据版本下写操作的限制,已经通过多版本实现快照隔离的优越性。 1.3 示例 Time Object1 Object2 0 1 | "Foo" by T0 "Hello" by T1 | "Bar" by T0 | Time=1 的时候数据库的状态如上: T0 写 Object1 为 "Foo",写 Object2 为 "Bar";之后 T1 写 Object1 为 "Hello",保留 Object2 为原始值。 Object1 的新值将取代 Time=0 时刻的旧值,并提供给 T1提交之后的发生的所有事务。Object1的版本号为0的旧数据会被 GC 掉。 如果有一个长事务 T2,在 T1之后对 Object1和 Object2 进行读操作,同时并行地,有事务 T3 做更新:删除 Object2、增加 Object3="Foo-Bar" ,在 Time=2 数据的状态如下所示: Time Object1 Object2 Object3 0 1 | 2 | "Foo" by T0 "Hello" by T1 | | "Bar" by T0 | (delete)by T3 | | "Foo-Bar" by T3 | 在 Time=2 Object2有一个新版本:标记删除,同时增加了新对象 Object3 。T2 和 T3 并发执行,T2 看到的是数据在 Time=2 且 T3提交前的版本,这样 T2读到了 Object2="Bar"" 且 Object1="Hello" 。 以上就是 MVCC 在不加锁的情况下实现的快照隔离的读的原理。 1.4 历史 最早于1978年,论文『Naming and Synchronization in a Decentralized Computer System』清晰地介绍了 MVCC,这是公认关于 MVCC 最早的工作。 在1981年,论文『Concurrency Control in Distributed Database System』介绍MVCC的一些细节。 目前支持 MVCC 的数据库,包括 DB2、Oracle、Sybase、SQL Server、MySQL、PG 等所有主流数据库,以及 HBase、Couchbase、Berkeley DB 等 NoSQL 数据库 数据库 2019-06-04 20:39:00 「深度学习福利」大神带你进阶工程师,立即查看>>> Oracle中INSTR和SUBSTR的用法 Oracle中INSTR的用法: INSTR方法的格式为 INSTR(源字符串, 要查找的字符串, 从第几个字符开始, 要找到第几个匹配的序号) 返回找到的位置,如果找不到则返回0. 例如:INSTR('CORPORATE FLOOR','OR', 3, 2)中,源字符串为'CORPORATE FLOOR', 在字符串中查找'OR',从第三个字符位置开始查找"OR",取第三个字后第2个匹配项的位置。 默认查找顺序为从左到右。当起始位置为负数的时候,从右边开始查找。 所以SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "aaa" FROM DUAL的显示结果是 Instring —————— 14 oracle的substr函数的用法: 取得字符串中指定起始位置和长度的字符串 substr( string, start_position, [ length ] ) 如: substr('This is a test', 6, 2) would return 'is' substr('This is a test', 6) would return 'is a test' substr('TechOnTheNet', -3, 3) would return 'Net' substr('TechOnTheNet', -6, 3) would return 'The' select substr('Thisisatest', -4, 2) value from dual 综合应用: SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "Instring" FROM DUAL --INSTR(源字符串, 目标字符串, 起始位置, 匹配序号) SELECT INSTR('CORPORATE FLOOR','OR', 3, 2) "Instring" FROM DUAL SELECT INSTR('32.8,63.5',',', 1, 1) "Instring" FROM DUAL SELECT SUBSTR('32.8,63.5',INSTR('32.8,63.5',',', 1, 1)+1) "INSTRING" FROM DUAL SELECT SUBSTR('32.8,63.5',1,INSTR('32.8,63.5',',', 1, 1)-1) "INSTRING" FROM DUAL -- CREATED ON 2008-9-26 BY ADMINISTRATOR DECLARE -- LOCAL VARIABLES HERE T VARCHAR2(2000); S VARCHAR2(2000); NUM INTEGER; I INTEGER; POS INTEGER; BEGIN -- TEST STATEMENTS HERE T := '12.3,23.0;45.6,54.2;32.8,63.5;'; SELECT LENGTH(T) - LENGTH(REPLACE(T, ';', '')) INTO NUM FROM DUAL; DBMS_OUTPUT.PUT_LINE('NUM:' || NUM); POS := 0; FOR I IN 1 .. NUM LOOP DBMS_OUTPUT.PUT_LINE('I:' || I); DBMS_OUTPUT.PUT_LINE('POS:' || POS); DBMS_OUTPUT.PUT_LINE('==:' || INSTR(T, ';', 1, I)); DBMS_OUTPUT.PUT_LINE('INSTR:' || SUBSTR(T, POS + 1, INSTR(T, ';', 1, I) - 1)); POS := INSTR(T, ';', 1, I); END LOOP; END; -- Created on 2008-9-26 by ADMINISTRATOR declare -- Local variables here i integer; T VARCHAR2(2000); S VARCHAR2(2000); begin -- Test statements here --历史状态 T := '12.3,23.0;45.6,54.2;32.8,63.5;'; IF (T IS NOT NULL) AND (LENGTH(T) > 0) THEN --T := T || ','; WHILE LENGTH(T) > 0 LOOP --ISTATUSID := 0; S := TRIM(SUBSTR(T, 1, INSTR(T, ';') - 1)); IF LENGTH(S) > 0 THEN DBMS_OUTPUT.PUT_LINE('LAT:'||SUBSTR('32.8,63.5',1,INSTR('32.8,63.5',',', 1, 1)-1)); DBMS_OUTPUT.PUT_LINE('LON:'||SUBSTR('32.8,63.5',INSTR('32.8,63.5',',', 1, 1)+1)); -- COMMIT; END IF; T := SUBSTR(T, INSTR(T, ';') + 1); END LOOP; END IF; end; 数据库 2019-06-04 20:34:00 「深度学习福利」大神带你进阶工程师,立即查看>>> SELECT * FROM table_name AS OF TIMESTAMP TO_TIMESTAMP('2016-06-17 16:00:00', 'YYYY-MM-DD HH24:MI:SS') WHERE XQID='20160219-133933-03263723' and XSID='440106200012311810' SELECT * FROM table_name AS OF TIMESTAMP sysdate -62/1440//62分钟前的数据 WHERE XQID='20160219-133933-03263723' and XSID='440106200012311810' order by rqxx desc 数据库 2019-06-04 20:34:00 「深度学习福利」大神带你进阶工程师,立即查看>>> SELECT sum(fzxx) FROM table_name VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE WHERE XQID='20160219-133933-03263723' and XSID='440106200012311810' order by RQXX desc ; 数据库 2019-06-04 20:34:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 一、配置web.xml 二、加入注解 @Namespace(value="/test") @Action(value = "file-manager", interceptorRefs = { @InterceptorRef(value = "fileUpload",params={"maximumSize","1024000","allowedTypes","image/pjpeg"}), @InterceptorRef(value = "basicStack")}, results = {@Result(name = ActionSupport.SUCCESS, location = "/view/file-manager-sucess.jsp"), @Result(name = ActionSupport.ERROR, location = "/view/file-manager-error.jsp") }, exceptionMappings = {@ExceptionMapping(exception = "java.lang.Exception", result = ActionSupport.ERROR)} ) 验证注解: @Validations( requiredStrings={ @RequiredStringValidator(fieldName="username",message="用户名不能为空!"), @RequiredStringValidator(fieldName="telNum",message="电话号码不能为空!") }, regexFields={@RegexFieldValidator(fieldName="telNum",expression="^(//+86|0|1)//d{10,11}$", message="电话号码格式不正确!")} ) 跳过验证注解: @SkipValidation 三、Convention的Annotation 1)与Action相关的两个Annotation是@Action 和@Actions 2)@Action中可指定一个value属性。类似于指定 3)@Action中还可以指定一个params属性,该属性是一个字符串数组,用于该Acion指定的参数名和参数值。params属性应遵守如下格式:{“name1”,”value1”,”name2”,”value2”} 4)@Actions 也用于修饰Action类里的方法,用于将该方法映射到多个URL.@Actions用于组织多个@Action.因此它可将一个方法映射成多个逻辑Action。 四、与Result配置相关的Annotation 1)@ResultPath @Result 和Results 2)@Results用于组织多个@Result因此它只需指定一个value属性值,该value属性值为多个@Result 3)@Result相当于struts.xml文件中的 ☆ type 相当于 ☆ location 相当于 ☆ params:该属性相当于 4)@Result有以下两种用法 1.Action级的Result映射:以@Actions组合多个@Action后修饰的Action类。这种Result映射对该Action里的所有方法都有效。 2.方法级Result映射:将多个@Result组成数组后作为@Action的results属性值。这种Result映射仅对被修饰的方法有效。 5)@ResultPath则用于修饰包和Action类,用于改变被修饰Action所对应的物理视图资源的根路径。举例说:默认情况下,Convention都会到WEB-INF/content路径下找物理视图资源,一旦我们使用@ResultPath("/view")修饰该Action,系统将回到view目录下寻找物理视图资源。 五、与包和命名空间相关的Annotation: @Namespace:修饰Action类或其所在的包。该Annotation中指定一个value属性值,用于指定被修饰的Action所在的命名空间 @Namespaces:修饰Action类或其所在的包,用于组合多个@Namespace @ParentPackage: 用于指定被修饰的Action所在包的父包。 六、与异常处理相关的Annotation @ExceptionMappings 用于组织多个@ExceptionMapping,因此它只需指定一个value属性值,该value属性值为多个@ExceptionMapping。 @ExceptionMapping 用于定义异常类和物理视图之间的对应关系,也相当于struts.xml文件里 exception: 用于指定异常类 result:用于指定逻辑视图 @ExceptionMpping有如下两种用法 Action级的异常定义:以@ExceptionMappings组合多个@ExceptionMapping后修饰的Action类。这种异常定义对Action中的所有方法有效 方法级的异常定义:将多个@ExceptionMapping组成数组后作为@Action的exceptionMappings属性值,这种异常定义仅对被修饰的方法有效。 七、与拦截器配置相关的Annotation 与拦截器配置的Annotation有@InterceptorRef、@InterceptorRefs和@DefaultInterceptorRef @InterceptorRefs用于组织多个@InterceptorRef,因此它只需要指定一个value属性值,该value属性值为多个@InterceptorRef @InterceptorRef用于为指定Action引用lanjieq或者是拦截器栈。也就相当于strut.xml中位于 八、查看struts2配置 为了看到struts2应用里的Action等各种资源的影射情况,struts2提供了Config Browser插件。 使用方法:将struts2-config-browser-plugin-2.1.6.jar文件复制到struts2应用的WEB-INF/lib目录中。 打开首页地址:http://localhost:8080/应用名字/config-browser/actionNames.action 这里可以看到Config Browser插件的首页。 struts2注解实例: 使用注解完成一个Action的流程必须要如下的7个jar包 1.commons-fileupload-1.2.1.jar 2.commons-io-1.3.2.jar 3.freemarker-2.3.15.jar 4.ognl-2.7.3.jar 5.struts2-convention-plugin-2.1.8.1.jar 6.struts2-core-2.1.8.1.jar 7.xwork-core-2.1.6.jar 如下用户登录的Action通过注解的方式验证通过: package com.huawei.action; import org.apache.struts2.convention.annotation.Action; import org.apache.struts2.convention.annotation.ExceptionMapping; import org.apache.struts2.convention.annotation.ExceptionMappings; import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.ParentPackage; import org.apache.struts2.convention.annotation.Result; import org.apache.struts2.convention.annotation.Results; import com.opensymphony.xwork2.ActionSupport; /** * @name * @date 2011-3-24 * @action LoginAction.java * @time 下午11:23:58 * @package_name com.huaweiaction * @project_name steutsAction */ /* * 这个小Demo的主要作用就是温故一下Struts2 Action的注解 * 一般在一个项目实施开发中是不会配置struts.xml进行Action的转发或重定向的,其都是通过注解的方式来配置Action的 */ ///////////使用注解来配置Action/////////////////////////// @ParentPackage("struts-default") // 父包 @Namespace("") @Results( { @Result(name = com.opensymphony.xwork2.Action.SUCCESS, location = "/msg.jsp"), @Result(name = com.opensymphony.xwork2.Action.ERROR, location = "/erlogin.jsp") }) // @ExceptionMappings 一级声明异常的数组 // @ExceptionMapping 映射一个声明异常 @ExceptionMappings( { @ExceptionMapping(exception = "java.lange.RuntimeException", result = "error") }) public class LoginAction extends ActionSupport { private static final long serialVersionUID = -2554018432709689579L; private String loginname; private String pwd; // @Action(value="login") 指定某个请求处理方法的请求URL。注意,它不能添加在Action类上,要添加到方法上。 @Action(value = "loginName") public String login() throws Exception { if ("HEFE".equalsIgnoreCase(loginname.trim())&&"123".equalsIgnoreCase(pwd.trim())) { return SUCCESS; } else { System.out.println("==========="); return ERROR; } } @Action(value = "add", results = { @Result(name = "success", location = "/index.jsp") }) public String add() throws Exception { return SUCCESS; } public String getLoginname() { return loginname; } public void setLoginname(String loginname) { this.loginname = loginname; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } } 数据库 2019-06-04 20:32:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 那就从简单的标签说起吧!1.x中常用的标签只有4中html、bean、logic、tiles 而struts2.0里的标签却没有分类,只用在jsp头文件加上 <%@ taglib prefix="s" uri="/struts-tags" %> 就能使用struts2.0的标签库 下面就介绍每个标签的具体应用实例说明:按字母排列 A: 1. 2. 3. 4. 5. 6. 7. B: 1. C: 1. 2. 3. 4. D: 1. 2. 3. 4. 5. E: 1. 2. 3. F: 1. 2. 3. G: 1. H: 1. 2. I: 1. 2. 3. 4. L: 1. M: 1. O: 1. 2. P: 1. struts2的s:param标签主要有两个属性name与value, 若想在value属性中输入直接量,则可以这样写: 2. 3. 4. R: 1. 2. 3. S: 1. 2. 3. 4. 5. T: 1. 2. 3. 4. 5. 6. 7. 8. U: 1. 2. ___________________________________________________ struts2框架的标签库分为三类: 用户标签:生成HTML元素 非用户标签:数据访问、逻辑控制等。 AJAX标签:支持AJAX技术。 用户界面标签又分两类: 表单标签:生成HTML页面的from元素 非表单标签:生成页面上的Tab、Tree等 非界面标签又分为数据访问标签和流程控制标签。 控制标签: if/elseif/else标签:控制流程分支。 iteratot标签:对集合属性迭代(属性类型:List、Map、数组)。 append标签:将多个集合对象拼接在一起,组成一个新的集合。将多个集合使用一个 generatot标签:将指定的字符串按照规定的分隔符分解成多个子字符串。 merge标签:将多个集合拼接在一起。 subset标签:获取某个集合的子集合。 sort标签:多指定的集合元素进行排序。 数据标签: action:直接调用一个Action,根据executeResult参数,可以将Action的处理结果包含到页面中。 bean:创建一个JavaBean实例。。 date:格式化输出一个日期属性。 debug:生成一个调试链接,当单击该链接时,可以看到当前栈值中的内容。 i18n:指定国际化资源文件的baseName。 include:包含其他的页面资源。 param:设置参数。 property:输出某个值。可以输出值栈、StackContext 、ActionContext中的值。 push:将某个值放入值栈。 set:设置一个新的变量。 text:输出国际化信息。 url:生成一个URL地址。 表单标签: checkbox标签:复选框。 checkboxlist标签:根据一个集合属性创建一系列的复选框。 combobox标签:生成一个单选文本框和一个下拉列表框的组合。 doubleselect标签:生成一个相互关联的列表框,该标签由两个下拉选择框组成。 datetimepicker标签:生成一个日期、时间下拉列表框。 head标签:生成HTML页面的HEAD部分。 file标签:在页面上生成一个上传文件元素。 hidder标签:生成一个不看见的用户输入元素。 select标签:生成下拉列表框。 optiontransferselect标签:创建两个选项以及转移下拉列表项,该标签生成两个下拉列表框,同时生成相应的按钮,这些按钮可以控制选项在两个下拉列表之间移动、排序。 radio标签:单选框。 optgroup标签:生成一个下拉列表框的选择组,下拉列表框中可以包含多个选择组。 token标签:防用户多次提交表单。 textarea标签:文本域。 updownselct标签:支持选项内容的上下移动。 password表单:密码表单域。 textfield标签:单行文本输入框。 非表单标签: actionerror标签:输出Action中getActionErrors()方法返回的异常信息。 actionmessage标签:输出Action中getActionErrors()方法返回的信息。 component标签:生成一个自定义的组件。 div标签:AJAX标签,生成一个div片段。 fielderror标签:输出异常提示信息。 tabbedPanel:AJAX标签,生成HTML中的Tab页。 tree标签:生成一个树形结构。 treenode标签:生成树形结构的节点。 ____________________________________________________ Struts之LOGIC标签库详解 1. logic:empty 该标签是用来判断是否为空的。如果为空,该标签体中嵌入的内容就会被处理。该标签用于以下情况: 1)当Java对象为null时; 2)当String对象为""时; 3)当java.util.Collection对象中的isEmpty()返回true时; 4)当java.util.Map对象中的isEmpty()返回true时。 eg. 该句等同于: if (userList.isEmpty()) { ... } 2. logic:notEmpty 该标签的应用正好和logic:empty标签相反。 3. logic:equal 该标签为等于比较符。 eg1. 比较用户的状态属性是否1,若为1,输出"启用"; eg2. 如果上例中的value值是动态获得的,例如需要通过bean:write输出,因struts不支持标签嵌套,可采用EL来解决该问题。 4. logic:notEqual 该标签意义与logic:equal相反,使用方法类似,略。 5. logic:forward 该标签用于实现页面导向,查找配置文件的全局forward。struts-config.xml文件中全局转向 eg. 6. logic:greaterEqual 为大于等于比较符。 eg. 当某学生的成绩大于等于60时,输出“及格”: 及格 7. logic:greaterThan 此为大于比较符,使用方法同logic:greaterEqual; 8. logic:lessEqual 此为小于等于比较符,使用方法同logic:greaterEqual; 9. logic:lessThan 此为小于比较符,使用方法同logic:greaterEqual; 10. logic:match 此标签比较对象是否相等; eg1. 检查在request范围内的name属性是否包含"hello"串: eg2. 检查在request范围内的name属性是否已“hello”作为起始字符串: eg3. 你运行的是Windows系统 11. logic:notMatch 此标签用于比较对象是否不相同,与logic:match意义相反,使用方法类似 12. logic:messagePresent 该标签用于判断ActionMessages/ActionErrors对象是否存在; eg. 如果存在error信息,将其全部输出: 13. logic:messagesNotPresent 该标签用于判断ActionMessages/ActionErrors对象是否不存在,使用方法与logic:messagePresent类似 14. logic:present 此标签用于判断request对象传递参数是否存在。 eg1. user对象和它的name属性在request中都存在时,输出相应字符串: user对象和该对象的name属性都存在 eg2. 若有一个名字为“hello”的JavaBean,输出对应字符串: 有一个名字为“hello”的JavaBean。 eg3. we got a user-agent header. 15. logic:notPresent 此标签用于判断request对象传递参数是否不存在,意义与了logic:present相反,使用方法类似。 16. logic:iterator 用于显示列表为collection的值(List ,ArrayList,HashMap等)。 eg1. 逐一输出用户列表(empList)中用户的姓名: eg2. 从用户列表中输出从1开始的两个用户的姓名 eg3. logic:iterator标签的嵌套举例 17. logic:redirect 该标签用于实现页面转向,可传递参数。 eg. logic:iterate logic:iterate标签用来迭代集合,您可以使用如下方式来为这个标签指定其要叠代的集合: 使用一个运行时表达式,这个表达式的值是一个集合。 用name属性引用一个JSP Bean,这个JSP Bean本身就是一个集合。 用name属性引用一个JSP Bean,这个JSP Bean的一个属性是一个集合,这时可以联合使用property来指定这个集合。 上面所提到的集合可以是: 对象类型或原子类型的数组(Array)。 java.util.Collection的实现,包括ArrayList,Vector。 java.util.Enumeration的实现。 java.util.Iterator的实现。 java.util.Map的实现,包括HashMap,Hashtable和TreeMap。 如果您叠代的集合中含有null的值,这时需要采取一定的措施,因为这时logic:iterate不会在page作用域中创建对象。一般是使用 下面是logic:iterate叠代ArrayList的示例的对象引用关系和部分代码: 图示 3. logic:iterate中对象的引用关系 图中的persons列表是在ListAction中填充的,在这里只是简单的加入了三个Person对象,在实际的应用中这些数据应该取自数据库。具体的代码如下: public ActionForward execute(ActionMapping mapping,ActionForm. form, HttpServletRequest request, HttpServletResponse response) { ListForm. listForm. = (ListForm) form; List Person person1 = new Person(); person1.setId("00001"); person1.setName("赵辰"); Person person2 = new Person(); person2.setId("00002"); person2.setName("李为芳"); Person person3 = new Person(); person3.setId("00003"); person3.setName("王微"); persons.add(person1); persons.add(person2); persons.add(person3); listForm.setPersons(persons); return mapping.findForward("success"); } 标签输出的结果为: 00001-->赵辰 00002-->李为芳 00003-->王微 回页首 logic:present 如果指定的值出现该标签就会创建其标签体内的内容。该标签用于以下情况: 检查具有指定名称的cookie是否出现。 检查具有指定名称的header是否出现。 检查具有指定名称的JSP Bean是否出现 或 检查具有指定名称的JSP Bean中的property属性是否出现。 检查request中指定名称的参数是否出现。 检查当前的认证用户是否与指定的安全角色相关联。 检查当前认证的主体是否具有指定的名字。 下面的代码示例了logic:present标签检查具有指定名称User-Agent的header是否出现: 您的浏览器是 logic:notPresent标签的应用正好和logic:present标签相反。 回页首 logic:messagesPresent logic:messagesPresent标签用于以下情况: 在request作用域中存在一个ActionMessages对象,标签的property属性和ActionMessages中的property对应。 在request作用域中存在一个ActionErrors对象,标签的property属性和ActionErrors中的property对应。 存在一个String对象,将其转换(构造)成ActionMessage然后在添加到ActionMessages中。 存在一个String Array对象,将数组中的每一个String转换成一个ActionMessage,在将其添加到ActionMessages中。 标签的message属性值为true时将以Globals.MESSAGE_KEY为key在request作用域中查找Message,其它情况下,将name的值作为key查找,如果name 没有出现,默认值为Globals.ERROR_KEY。 下面的代码示例了logic:messagesPresent标签的典型用法: 标签logic:messagesNotPresent的应用正好和logic:messagesPresent的应用相反。 回页首 logic:empty logic:empty标签是用来判断是否为空的。如果为空,该标签体中嵌入的内容就会被处理。该标签用于以下情况: 当Java对象为null时 当String对象为""时 当java.util.Collection对象中的isEmpty()返回true时 当java.util.Map对象中的isEmpty()返回true时 下面的代码示例了logic:empty标签判断集合persons是否为空: 集合persons为空! logic:notEmpty标签的应用正好和logic:empty标签相反。 回页首 logic:match logic:match标签用来处理子串匹配问题。 如果指定的值匹配该标签就会创建其标签体内的内容。该标签用于以下情况: 检查具有指定名称的cookie是否与该标签的value匹配。 检查具有指定名称的header是否与该标签的value匹配。 检查具有指定名称的JSP Bean是否与该标签的value匹配 或 检查具有指定名称的JSP Bean中的property属性值是否与该标签的value匹配。 检查request中指定名称的参数值是否与该标签的value匹配。 下面的代码示例了logic:match标签的典型用法: MS IE 6.0 logic:notMatch标签的应用正好和logic:match标签相反。 回页首 logic:equal 这里要介绍的不只是logic:equal(=)标签,而是要介绍一类标签,这类标签完成比较运算,包括: logic:equal(=) logic:notEqual(!=) logic:greaterEqual(>=) logic:lessEqual(<=) logic:graterThan(>) logic:lessThan(<) 该类标签的用法类似,我们只介绍logic:equal标签,其它的留给您自己去完成。 logic:equal是用来判断是否相等的。如果相等,该标签体中嵌入的内容就会被处理。该标签用于以下情况: 比较由该标签的cookie属性指定的cookie的值是否与该标签的value属性值相等。 比较由该标签的header属性指定的header的值是否与该标签的value属性值相等。 比较由该标签的name属性指定的JSP Bean是否与该标签的value属性值相等(property属性不出现)或比较由该标签的name属性指定的JSP Bean中的property属性值是否与该标签的value属性值相等。 比较由该标签的parameter属性指定的参数值(request中)是否与该标签的value属性值相等。 回页首 logic:forward 我觉得将forward和redirect这两个动作放到一起对比着介绍更加有利于理解,基于此原因也就将logic:forward和logic:redirect这两个标签也拿到这里一起介绍了。 让我们看看这两个动作的区别: forward是在servlet内部执行,浏览器完全不会感知到这个动作,原始的url也不会改变,浏览器重新装载的话也只是对原始的请求进行简单的重复。 redirect则分成两个步骤:第一步是web应用程序告诉浏览器第二个url,然后浏览器向第二个url发送请求。 redirect比forward慢,因为浏览器要做二次请求。还有就是要注意,在第一次的请求作用域(request作用域)内的bean对于第二次请求是不可见的。 理解了上面描述的区别也就知道了什么时候该选用logic:forward标签什么时候该选用logic:redirect标签了。logic:forward标签完成PageContext.forward()或HttpServletResponse.sendRedirect(),如何选择由控制器决定。logic:redirect标签完成HttpServletResponse.sendRedirect()。 在使用logic:redirect标签时我们可以向使用html:link一样构造baseurl和query参数。如果您感兴趣可以参考html:link标签。 (2012-09-27 18:04:34) 转载▼ 标签: <saction> struts2 杂谈 分类: struts2 使用action标签,可以允许在jsp页面中直接调用Action,在调用Action时候,可以指定需要被调用的Action的name和namespace.如果指定了executeResult参数的属性值为true,该标签会把Action的处理结果(视图资源)包含到本页面中. 使用action标签指定属性有: id: 可选属性,作为该Action的引用ID name:必选属性,指定调用Action namespace:可选属性,指定该标签调用Action所属namespace executeResult:可选属性,指定是否将Action的处理结果包含到本页面中.默认值为false,不包含. ignoreContextParam:可选参数,指定该页面的请求参数是否需要传入调用的Action中,默认值是false,即传入参数. 传递参数 所以request.getParameter("")会获取不到值 通过request.getAttribute("")方式可以获取 另外:使用 和 例如: struts.xml如下配置 页面加载如下段 xxx.action中只要set,get headerMap,scopeId就可以拿到值了 数据库 2019-06-04 20:32:00 「深度学习福利」大神带你进阶工程师,立即查看>>> (1): 使用: (2): 使用: (3): 使用: 前三个基本都是一样的。如果是要显示值的话可以这样:value = “实体Bean。Get***()”。? (4): 使用: <1>:第一种方式:list = “#{‘male’:’男’,’female’:’女’}”? <2>:第二中方式:list = “#request/session.list”。 ---- 与action结合起来了。? 如果要默认选中的话后面加上value = “”.也可以通过javascript的方式来默认选中。? (5): 这两个标签一般结合起来来使用。? 使用: (6): 使用:? <1>: <2>: (7): (8): (9): 使用: (10): 使用: <1>:list = “{18,20,30}”? <2>:list = “#request/session.list”? (11): 使用: (12): 使用: <1>:list = “{‘hibernate’, ‘spring’, ‘strust2’}”? <2>:list = “#request/session.list”.? 如果想默认选中的话,那么加上value=””? (13): 使用:? Will Not Be Executed ? Will Be Executed ? Will Not Be Executed ? (14): (15): 使用:? ? Days表示:在action中存的值,status :表示起的别名。 (16): 使用:? Value : 表示默认值。? (17): 使用: 页面输出的结果:jsf。? (18): 使用:? 页面中显示的结果:24/09/2008. 格式还有很多:MM/dd/yyyy、MM/dd/yyyy hh:mm:ss等。? (19): 使用: (20): <1>:name(String):参数名。? <2>:value(Object):参数值。? 使用:? ? insert? (21): 使用:? 教师职称: 页面显示:教师职称:数学高级教师。? (22): 使用:在页面加载时, 全局唯一标识符)值的隐藏输入框如:? ? ? 放在页面中随便的一个地方。 数据库 2019-06-04 20:31:00 「深度学习福利」大神带你进阶工程师,立即查看>>> echarts官网 提供了源码和说明文档,使用echarts需要先到官网下载需要的js源文件。 官网上的demo中夹杂着很多我们用不到的东西,想使用饼状图就得从demo中把不用的去掉,劈植斩叶留下最原始的功能实现。这样毕竟比较费时,我就在裁剪后的代码中加以总结于是乎新的使用教程如下所示: echarts饼状图实现步骤: 1,在简单的html中引入js文件 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > body > 2,为图形准备容器 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > body > 就是在html中添加一个div给定id,图表就会显示在div中。 3,模块导入js [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > < script type = "text/javascript" > // 路径配置 require.config({ paths:{ 'echarts' : 'js/echarts', 'echarts/chart/pie' : 'js/echarts' } }); script > body > 4,添加显示数据 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > < script type = "text/javascript" > // 路径配置 require.config({ paths:{ 'echarts' : 'js/echarts', 'echarts/chart/pie' : 'js/echarts' } }); // 使用 require( [ 'echarts', 'echarts/chart/pie' // 使用柱状图就加载bar模块,按需加载 ], function (ec) { // 基于准备好的dom,初始化echarts图表 var myChart = ec .init(document.getElementById(' < span style = "font-family:SimSun;" > picturePlace span > < span style = "font-family:FangSong_GB2312;" > ' span > )); option = { title : { text: '某站点用户访问来源', subtext: '纯属虚构', x:'center' }, tooltip : { trigger: 'item', formatter: "{a} < br /> {b} : {c} ({d}%)" }, legend: { orient : 'vertical', x : 'left', data:['直接访问','邮件营销','联盟广告','视频广告','搜索引擎'] }, toolbox: { show : true, feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : true, series : [ { name:'访问来源', type:'pie', radius : '55%', center: ['50%', '60%'], data:[ {value:335, name:'直接访问'}, {value:310, name:'邮件营销'}, {value:234, name:'联盟广告'}, {value:135, name:'视频广告'}, {value:1548, name:'搜索引擎'} ] } ] }; // 为echarts对象加载数据 myChart.setOption(option); } ); script > body > 5,运行程序显示结果 以上是饼状图的实现步骤,柱状图散点图跟这个类似就是引用js时饼状图是pie,柱状图是bar,对应的option里面的数据不同,程序的架子是一样的。。 出处: 数据库 2019-06-04 20:30:00 「深度学习福利」大神带你进阶工程师,立即查看>>> echarts官网 提供了源码和说明文档,使用echarts需要先到官网下载需要的js源文件。 官网上的demo中夹杂着很多我们用不到的东西,想使用饼状图就得从demo中把不用的去掉,劈植斩叶留下最原始的功能实现。这样毕竟比较费时,我就在裁剪后的代码中加以总结于是乎新的使用教程如下所示: echarts饼状图实现步骤: 1,在简单的html中引入js文件 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > body > 2,为图形准备容器 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > body > 就是在html中添加一个div给定id,图表就会显示在div中。 3,模块导入js [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > < script type = "text/javascript" > // 路径配置 require.config({ paths:{ 'echarts' : 'js/echarts', 'echarts/chart/pie' : 'js/echarts' } }); script > body > 4,添加显示数据 [html] view plain copy print ? < head > < meta charset = "utf-8" > < title > Charts demo title > < script src = "js/esl.js" > script > head > < body > < div id = "picturePlace" > div > < script type = "text/javascript" > // 路径配置 require.config({ paths:{ 'echarts' : 'js/echarts', 'echarts/chart/pie' : 'js/echarts' } }); // 使用 require( [ 'echarts', 'echarts/chart/pie' // 使用柱状图就加载bar模块,按需加载 ], function (ec) { // 基于准备好的dom,初始化echarts图表 var myChart = ec .init(document.getElementById(' < span style = "font-family:SimSun;" > picturePlace span > < span style = "font-family:FangSong_GB2312;" > ' span > )); option = { title : { text: '某站点用户访问来源', subtext: '纯属虚构', x:'center' }, tooltip : { trigger: 'item', formatter: "{a} < br /> {b} : {c} ({d}%)" }, legend: { orient : 'vertical', x : 'left', data:['直接访问','邮件营销','联盟广告','视频广告','搜索引擎'] }, toolbox: { show : true, feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : true, series : [ { name:'访问来源', type:'pie', radius : '55%', center: ['50%', '60%'], data:[ {value:335, name:'直接访问'}, {value:310, name:'邮件营销'}, {value:234, name:'联盟广告'}, {value:135, name:'视频广告'}, {value:1548, name:'搜索引擎'} ] } ] }; // 为echarts对象加载数据 myChart.setOption(option); } ); script > body > 5,运行程序显示结果 以上是饼状图的实现步骤,柱状图散点图跟这个类似就是引用js时饼状图是pie,柱状图是bar,对应的option里面的数据不同,程序的架子是一样的。。 数据库 2019-06-04 20:29:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 首先介绍Oracle 9i新增加的一个系统自带的排序函数 1、按首字母排序 在oracle9i中新增了按照拼音、部首、笔画排序功能。设置NLS_SORT值 SCHINESE_RADICAL_M 按照部首(第一顺序)、笔划(第二顺序)排序 SCHINESE_STROKE_M 按照笔划(第一顺序)、部首(第二顺序)排序 SCHINESE_PINYIN_M 按照拼音排序 oracle9i中新增了按照拼音、部首、笔画排序功能 用法示例: 拼音 SELECT * FROM TEAM ORDER BY NLSSORT(排序字段,'NLS_SORT = SCHINESE_PINYIN_M') 笔划 SELECT * FROM TEAM ORDER BY NLSSORT(排序字段,'NLS_SORT = SCHINESE_STROKE_M') 部首 SELECT * FROM TEAM ORDER BY NLSSORT(排序字段,'NLS_SORT = SCHINESE_RADICAL_M') 2、按首字母拼音查询 首先我们要创建一个可以查询汉字首字母的函数,函数代码如下: CREATE OR REPLACE FUNCTION F_TRANS_PINYIN_CAPITAL(P_NAME IN VARCHAR2) RETURN VARCHAR2 AS V_COMPARE VARCHAR2(100); V_RETURN VARCHAR2(4000); FUNCTION F_NLSSORT(P_WORD IN VARCHAR2) RETURN VARCHAR2 AS BEGIN RETURN NLSSORT(P_WORD, 'NLS_SORT=SCHINESE_PINYIN_M'); END; BEGIN FOR I IN 1..LENGTH(P_NAME) LOOP V_COMPARE := F_NLSSORT(SUBSTR(P_NAME, I, 1)); IF V_COMPARE >= F_NLSSORT(' 吖 ') AND V_COMPARE <= F_NLSSORT('驁 ') THEN V_RETURN := V_RETURN || 'a'; ELSIF V_COMPARE >= F_NLSSORT('八 ') AND V_COMPARE <= F_NLSSORT('簿 ') THEN V_RETURN := V_RETURN || 'b'; ELSIF V_COMPARE >= F_NLSSORT('嚓 ') AND V_COMPARE <= F_NLSSORT('錯 ') THEN V_RETURN := V_RETURN || 'c'; ELSIF V_COMPARE >= F_NLSSORT('咑 ') AND V_COMPARE <= F_NLSSORT('鵽 ') THEN V_RETURN := V_RETURN || 'd'; ELSIF V_COMPARE >= F_NLSSORT('妸 ') AND V_COMPARE <= F_NLSSORT('樲 ') THEN V_RETURN := V_RETURN || 'e'; ELSIF V_COMPARE >= F_NLSSORT('发 ') AND V_COMPARE <= F_NLSSORT('猤 ') THEN V_RETURN := V_RETURN || 'f'; ELSIF V_COMPARE >= F_NLSSORT('旮 ') AND V_COMPARE <= F_NLSSORT('腂 ') THEN V_RETURN := V_RETURN || 'g'; ELSIF V_COMPARE >= F_NLSSORT('妎 ') AND V_COMPARE <= F_NLSSORT('夻 ') THEN V_RETURN := V_RETURN || 'h'; ELSIF V_COMPARE >= F_NLSSORT('丌 ') AND V_COMPARE <= F_NLSSORT('攈 ') THEN V_RETURN := V_RETURN || 'j'; ELSIF V_COMPARE >= F_NLSSORT('咔 ') AND V_COMPARE <= F_NLSSORT('穒 ') THEN V_RETURN := V_RETURN || 'k'; ELSIF V_COMPARE >= F_NLSSORT('垃 ') AND V_COMPARE <= F_NLSSORT('擽 ') THEN V_RETURN := V_RETURN || 'l'; ELSIF V_COMPARE >= F_NLSSORT('嘸 ') AND V_COMPARE <= F_NLSSORT('椧 ') THEN V_RETURN := V_RETURN || 'm'; ELSIF V_COMPARE >= F_NLSSORT('拏 ') AND V_COMPARE <= F_NLSSORT('瘧 ') THEN V_RETURN := V_RETURN || 'n'; ELSIF V_COMPARE >= F_NLSSORT('筽 ') AND V_COMPARE <= F_NLSSORT('漚 ') THEN V_RETURN := V_RETURN || 'o'; ELSIF V_COMPARE >= F_NLSSORT('妑 ') AND V_COMPARE <= F_NLSSORT('曝 ') THEN V_RETURN := V_RETURN || 'p'; ELSIF V_COMPARE >= F_NLSSORT('七 ') AND V_COMPARE <= F_NLSSORT('裠 ') THEN V_RETURN := V_RETURN || 'q'; ELSIF V_COMPARE >= F_NLSSORT('亽 ') AND V_COMPARE <= F_NLSSORT('鶸 ') THEN V_RETURN := V_RETURN || 'r'; ELSIF V_COMPARE >= F_NLSSORT('仨 ') AND V_COMPARE <= F_NLSSORT('蜶 ') THEN V_RETURN := V_RETURN || 's'; ELSIF V_COMPARE >= F_NLSSORT('侤 ') AND V_COMPARE <= F_NLSSORT('籜 ') THEN V_RETURN := V_RETURN || 't'; ELSIF V_COMPARE >= F_NLSSORT('屲 ') AND V_COMPARE <= F_NLSSORT('鶩 ') THEN V_RETURN := V_RETURN || 'w'; ELSIF V_COMPARE >= F_NLSSORT('夕 ') AND V_COMPARE <= F_NLSSORT('鑂 ') THEN V_RETURN := V_RETURN || 'x'; ELSIF V_COMPARE >= F_NLSSORT('丫 ') AND V_COMPARE <= F_NLSSORT('韻 ') THEN V_RETURN := V_RETURN || 'y'; ELSIF V_COMPARE >= F_NLSSORT('帀 ') AND V_COMPARE <= F_NLSSORT('咗 ') THEN V_RETURN := V_RETURN || 'z'; END IF; END LOOP; RETURN V_RETURN; END; 随后,我们可以先取出姓名,然后截取第一个汉字,最后取汉字的首字母,即可用来作为查询条件 select e.fullname,e.expert_id from expert e where f_trans_pinyin_capital(substr(e.fullname,1,1)) = 'z' 最后展示一个将两个结合起来运用的例子: select e.expert_id,e.eperson_id,e.fullname, sum(b2i.browse_num) browsecount, count(o.object_id),wmsys.wm_concat(distinct d.name) from expert e left join expert2disciplinetype e2d on e2d.expert_id = e.expert_id left join disciplinetype d on d.discipline_type_id = e2d.discipline_type_id and d.upid = '0' left join community2expert c2e on c2e.expert_id = e.expert_id left join item i on i.item_type_id = 'AcademicRes' and i.withdrawn = 'N' join metadatavalue m on m.item_id = i.item_id and m.metadata_field_id = '64' join objectpublishinfo o on o.object_id = i.item_id and o.object_type = 'item' and o.viewobjecttype = 'eperson' and o.viewobjectid = e.eperson_id and o.state = '1' and o.publishstate_id = '3' left join browse2item b2i on b2i.item_id = o.object_id where e.state = '1' and f_trans_pinyin_capital(substr(e.fullname,1,1)) = 'z' group by e.expert_id,e.eperson_id,e.fullname order by nlssort(e.fullname,'NLS_SORT=SCHINESE_PINYIN_M') 转自:http://zheng12tian.iteye.com/blog/817571 数据库 2019-06-04 20:26:00 「深度学习福利」大神带你进阶工程师,立即查看>>> MySQL数据库的优化技术: 对数据库的优化是一个综合性的技术,主要包括: 1.表的设计是否合理(符合三范式,3NF) 2.添加适当索引(常见索引有四种:普通索引,主键索引,唯一索引,全文索引,(空间索引,复合索引) 3.分表技术(水平分割,垂直分割) 4.读写分离 5.存储过程(SQL语句每次执行都需要编译,存储过程只编译一次,模块化编程) 6.对MySQL的配置优化(配置最大并发数 my.ini,调整缓存大小) 7.MySQL服务器的硬件升级 8.定时清除不需要的数据,定时进行碎片整理 9.SQL语句优化 l 需求:为什么要调优 l 分析:怎么找到软肋 l 实战:如何调优 l 总结 为什么要调优 § 老板要求 § 客户投诉 § 机器发飙 § 自己主动 机器发飙 网站或客户端打开非常慢,而 webserver 负载低,或打开静态页面很快,那就很可能是数据库的问题 load average >= 5 Iowait >= 10 vmstat procs(r b) 值较高 top 中 CPU 的 idle 很小, sys 或 wait 较高 服务器的 swap 严重 mysql 的内存命中率很低,例如 myisam_key_read_hit_ratio 或 innodb_buffer_hit_ratio 较低 瓶颈定位 vmstat,iostat,top 等系统级别的工具 explain slow query show status/show processlist /show engine innodb status 其他,如 mysqlreport , profiling 瓶颈定位 – vmstat / iostat 瓶颈定位 - Explain Explain 都能提供什么信息呢? 表的读取顺序 每个表都是如何读取的 可能用到哪些索引,实际使用了哪些索引 表是如何引用的 查询优化器从每个表中预计读取的记录数 其他额外信息,例如是否使用了内存表,是否引发排序等 瓶颈定位 – 续 将 LONG_QUERY_TIME 设为最小值;建议打补丁,这样单位可以设成微秒,并可查看详细执行计划 执行 SHOW [GLOBAL] STATUS/PROCESSLIST 查看当前运行状态,从结果中发现可能的问题 执行 SHOW ENGINE INNODB STATUS 查看 INNODB 的状态 另外 , 要定期检查多余的索引以及没有使用索引的慢查询 利用 mysqlreport 产生可读性更强的报告 利用 Profiling 剖析一次查询瓶颈所在 其他工具,包括监控工具, linux 自带工具等 MySQL 调优的几种途径 硬件、网络、软件 MySQL 参数设置 应用程序、架构优化 查询优化、索引 硬件、网络、软件 通常硬件是优化的最佳入口,主要是 CPU 、内存、磁盘、网络 客户端和服务器在一个高速的局域网内 通常,新版本的效率不如旧版本,但是可以利用新版本的新功能来从另一方面得到性能上的提升 编译优化,采用静态编译等 使用更稳定高效的内核 使用合适的文件系统,推荐使用 xfs ( 高级文件系统实现者指南 ) MySQL 参数设置
应用程序、架构优化 垂直 / 水平切分服务器 / 数据库、表 开启 MySQL 复制,实现读、写分离 在复制的基础上,增加负载均衡 采用集群 + 复制 (MySQL 6.0+) 频繁更新的表,可以分离成父表和子表 ( 内存表 ) 用统计表保存定时统计结果,而不是在大表上直接统计 编写存储过程 / 函数来代替大量的外部应用程序交互 查询优化、索引 确保索引合理利用,尽量使用联合索引 适当加大查询缓存 (query cache) 尽量减少交互次数 尽量使用固定格式的 SQL 语句,查询语句中少用运算或函数 缩短每个事务 使用适当的字段类型;适当的长度,有需要的时候再扩充 分解复杂查询为多个小查询 字符型字段采用前缀索引 其他 …… 调优方法 一、选择合适的引擎 MyISAM 这个是默认类型 , 基于传统的 ISAM 类型,它是存储记录和文件的标准方法。与其他存储引擎比较, MyISAM 具有检查和修复表格的大多数工具。 MyISAM 表格可以被压缩 , 而且它们支持全文搜索。它们不是事务安全的,而且也不支持外键。 InnoDB ACID 、外键、日志修复。 InnoDB 表格速度很快。如果需要一个事务安全的存储引擎或者是需要大量并发的 INSERT 或 UPDATE ,应该使用 InnoDB 表。 NDB 支持事务,用于 cluster ,实现高可用,但性能仍欠佳。 调优方法 – 续 ( 其他 ) 不直接执行 COUNT(*) – innodb 多个操作放在一起提交,但要注意事务不能太大 日志文件并非越大越好,需要考虑恢复和检查点 左连接时把数据量小的表放在前面 innodb_flush_log_at_trx_commit 可以尝试设置为 2 ,甚至是 0 导入数据时关闭 AUTOCOMMIT 以及 UNIQUE_CHECKS 、 FOREIGN_KEY_CHECKS 复杂的查询总是先用 EXPLAIN 来分析一下 定期执行 OPTIMIZE TABLE 整理碎片 用 char 来代替 varchar , MyISAM 是这样, InnoDB 则相反 数据库 2019-06-04 20:24:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 触发器 TRIGGER 1、触发器的定义 2、使用的场景 3、掌握触发器的创建语法 4、理解触发器的触发机制 什么叫做触发器? 当一个表中的数据发生改变的时候,会引起其他表中相关数据改变, 编制一个小程序附着在表上,把这种改变自动化执行,成为触发器。 触发器的类型? 在进行insert、update、delete操作时,触发相关的insert、update、delete 触发器触发。分为:insert、update、delete触发器。 USE wlw; CREATE TABLE goods ( gid INT NOT NULL PRIMARY KEY COMMENT '商品编号', gname VARCHAR(20) COMMENT '商品名称', kcnum DECIMAL(4,2) COMMENT '库存数量' ); INSERT INTO goods VALUES(112,'肉猪',25); INSERT INTO goods VALUES(113,'臭狗',56); INSERT INTO goods VALUES(114,'瞎马',78); SELECT * FROM goods; CREATE TABLE dindans ( ddh INT NOT NULL PRIMARY KEY COMMENT '订单号', gid INT COMMENT '商品编号', gname VARCHAR(20) COMMENT '商品名称', ddnum DECIMAL(4,2) COMMENT '订单数量' ); SELECT * FROM dindans; ===================================== #insert触发器 DELIMITER $$ CREATE TRIGGER tri_insert AFTER INSERT #在插入之后 ON dindans #在某个表上创建触发器 FOR EACH ROW #insert的每一行 BEGIN UPDATE goods SET kcnum = kcnum - new.ddnum #new代表着新插入的这一行,new是一个行对象,相当于dindans表的一行,new.id就可以取得新插入行的id WHERE gid=new.gid; END$$ DELIMITER ; DROP TRIGGER tri_insert; SELECT * FROM goods; SELECT * FROM dindans; #插入数据后,就会触发触发器,在goods表中减少相应的数量 INSERT INTO dindans VALUES(1,114,'瞎马',5); SELECT * FROM goods; SELECT * FROM dindans; INSERT INTO dindans VALUES(2,113,'臭狗',25); ============================ #delete触发器 DELIMITER $$ CREATE TRIGGER tri_del AFTER DELETE ON dindans FOR EACH ROW BEGIN UPDATE goods SET kcnum=kcnum + old.ddnum #old代表着刚刚删除的这一行,old是一个行对象,old.id可以取得新删除这一行的id WHERE gid=old.gid; END$$ DELIMITER ; SELECT * FROM dindans; SELECT * FROM goods; #用delete语句触发delete触发器,在删除之后,增加goods表的数量 DELETE FROM dindans WHERE gid=113; ======================== SELECT * FROM goods; SELECT * FROM dindans; INSERT INTO dindans VALUES(2,112,'肉猪',20); DROP TRIGGER tri_insert; #当订单的数量大于库存量的时候,我们在触发器中要有解决问题的 #办法,处理此类情况 DELIMITER $$ CREATE TRIGGER tri_insert AFTER INSERT ON dindans FOR EACH ROW BEGIN DECLARE sl INT; SELECT kcnum INTO sl FROM goods WHERE gid=new.gid; IF sl<=new.ddnum THEN UPDATE goods SET kcnum=0 WHERE gid=new.gid; ELSE UPDATE goods SET kcnum=kcnum-new.ddnum WHERE gid=new.gid; END IF; END$$ DELIMITER ; SELECT * FROM goods; SELECT * FROM dindans; DROP TRIGGER tri_insert; INSERT INTO dindans VALUES(3,112,'肉猪',5); ================================ #当更改订单表中的数据的时候,会触发订单表的数据,从而导致 #goods表的数据发生改变 SELECT * FROM goods; SELECT * FROM dindans; DELIMITER $$ CREATE TRIGGER tri_update AFTER UPDATE ON dindans FOR EACH ROW BEGIN IF old.ddnum >= new.ddnum THEN UPDATE goods SET kcnum=kcnum+old.ddnum - new.ddnum WHERE gid=new.gid; ELSE UPDATE goods SET kcnum =0; END IF; END$$ DELIMITER ; SELECT * FROM goods; SELECT * FROM dindans; UPDATE dindans SET ddnum=8 WHERE ddh=2; ==================================== 做一个日志应用,使用触发器 SELECT * FROM goods; DROP TABLE klog; CREATE TABLE klog ( id INT PRIMARY KEY AUTO_INCREMENT, kusr VARCHAR(50), kcz VARCHAR(20), kgoodname VARCHAR(60), ktime DATETIME ); #当对goods插入数据时,即可在klog中生成一条记录 DELIMITER $$ CREATE TRIGGER g_insert AFTER INSERT ON goods FOR EACH ROW BEGIN INSERT INTO klog(kusr,kcz,kgoodname,ktime) VALUES(USER(),'正在insert...',new.gname,NOW()); END$$ DELIMITER ; #当对goods删除数据时,即可在klog中生成一条记录 DELIMITER $$ CREATE TRIGGER g_delete AFTER DELETE ON goods FOR EACH ROW BEGIN INSERT INTO klog(kusr,kcz,kgoodname,ktime) VALUES(USER(),'正在delete...',old.gname,NOW()); END$$ DELIMITER ; #当对goods更新数据时,即可在klog中生成一条记录 DELIMITER $$ CREATE TRIGGER g_udpate AFTER UPDATE ON goods FOR EACH ROW BEGIN INSERT INTO klog(kusr,kcz,kgoodname,ktime) VALUES(USER(),'正在update...',old.gname,NOW()); END$$ DELIMITER ; SELECT * FROM goods; INSERT INTO goods VALUES(115,'骆驼',13); SELECT * FROM goods; SELECT * FROM klog; INSERT INTO goods VALUES(116,'孔雀',63); INSERT INTO goods VALUES(117,'羊驼',234); DELETE FROM goods WHERE gid=114; UPDATE goods SET kcnum=400 WHERE gid=117; SELECT * FROM goods; ================================== 用户自定义函数 1、系统函数 SELECT NOW(); SELECT USER(); 2、数据库函数 SELECT COUNT(*) FROM klog; 3、用户自定义函数 (1)定义 CREATE FUNCTION 函数名() RETURNS 返回值类型 BEGIN RETURN 类型; END; 定义函数 DELIMITER $$ CREATE FUNCTION hello() RETURNS VARCHAR(20) BEGIN RETURN 'hello 你好!'; END$$ DELIMITER ; 调用函数 SELECT kusr,kcz,hello() FROM klog; ============================ DELIMITER $$ CREATE FUNCTION hellohaha(xm VARCHAR(20)) RETURNS VARCHAR(20) BEGIN RETURN CONCAT('你好!',xm); END$$ DELIMITER ; SELECT kusr,kcz,hellohaha(kusr) FROM klog; SELECT xy,hellohaha(xm) FROM test.xuesheng; SELECT * FROM emp; SELECT empid,hellohaha(empname),income FROM emp; ============================== DELIMITER $$ CREATE FUNCTION find_xh(arga VARCHAR(20)) RETURNS VARCHAR(20) BEGIN DECLARE xxh VARCHAR(20); SELECT xh INTO xxh FROM test.xuesheng WHERE xm=arga; IF ISNULL(xxh) THEN RETURN '没找到'; ELSE RETURN xxh; END IF; END$$ DELIMITER ; DROP FUNCTION find_xh; SELECT find_xh('王余昌'); SELECT find_xh('宋有国'); 数据库 2019-06-04 17:30:00 「深度学习福利」大神带你进阶工程师,立即查看>>> Cursor的应用场景: 查询结果集 list ,使用 cursor 的情况,肯定会有下边这两种情况,才可能使用到 cursor : 需要依据查询到的结果集,作为条件,进行下一步的查询。 需要在结果集某个位置上,满足某种条件时,对数据进行不同的修改。 上边的两种情况,都会涉及到遍历数据,所以游标的使用也需要注意: 比如,你的查询结果集很大,首先占了很多的内存,其次,遍历这么庞大的结果集,效率也肯定高不了。 另外,如果还有其他方式解决问题的话,那就不要选择使用游标,但是游标在使用中,的确有,定义游标、打开游标、关闭游标,这些操作,这样也肯定快不了多少。 Cursor的类别: 1.隐式游标。 DML操作和单行SELECT语句会使用隐式游标,如:INSERST,UPDATE,DELETE , SELECT ... INTO .. 另一种方式 : create or replace procedure test_class_procedure as begin for class_dec in ( select * from t_class ) loop dbms_output.put_line('姓名:'||class_dec.name||' 年龄:'||class_dec.age); end loop; end; 执行: set serveroutput on; begin test_class_procedure; end; 隐式游标其实默认已经打开游标,并在执行完成后关闭游标释放资源。 2.显示游标。 显示游标分为静态游标与动态游标,显示游标的写法会在存储过程中定义 Cursor ,并且一般都有固定的四个步骤: 声明游标 、 打开游标 、 提取数据 、 关闭游标 。显式游标打开后,必须显式地关闭。游标一旦关闭,游标占用的资源就被释放,游标变成无效,必须重新打开才能使用。 示例: CREATE OR REPLACE NONEDITIONABLE PROCEDURE PRC_MDS1_TO_MDS2 (parameter IN VARCHAR2, exitcode OUT INT) AS -- 定义一个变量用来保存此存储过程的名字 v_logic_proc_name VARCHAR2(30) := 'PRC_MDS1_TO_MDS2'; -- 声明显式游标, 查询udt_mds1_input中item不为null的数据 CURSOR c_udt_mds1_input_item_notnull IS SELECT * FROM scpomgr.udt_mds1_input WHERE item IS NOT NULL AND TRIM(item) IS NOT NULL; -- 定义游标变量,该变量的类型为基于游标c_udt_mds1_input_item_notnull的记录 c_mds1_item_notnull_row c_udt_mds1_input_item_notnull%ROWTYPE; -- 声明一个游标,查询udt_mds1_input中item为null的数据 -- MARKET、itemgroup、package、config不为NULL CURSOR c_udt_mds1_input_item_null IS SELECT * FROM scpomgr.udt_mds1_input WHERE (item IS NULL OR TRIM(item) IS NULL) AND market IS NOT NULL AND TRIM(market) IS NOT NULL AND itemgroup IS NOT NULL AND TRIM(itemgroup) IS NOT NULL AND package IS NOT NULL AND TRIM(package) IS NOT NULL AND config IS NOT NULL AND TRIM(config) IS NOT NULL; -- 定义游标变量 c_mds1_item_null_row c_udt_mds1_input_item_null%ROWTYPE; -- 再声明一个游标,查询udt_mds1_input中item为null -- MARKET、ITEMGROUP、PACKAGE、CONFIG有为NULL的 CURSOR c_udt_mds1_item_null_other IS SELECT * FROM scpomgr.udt_mds1_input WHERE (item IS NULL OR TRIM(item) IS NULL) AND (market IS NULL OR TRIM(market) IS NULL OR itemgroup IS NULL OR TRIM(itemgroup) IS NULL OR package IS NULL OR TRIM(package) IS NULL OR config IS NULL OR TRIM(config) IS NULL); -- 定义游标变量 c_mds1_item_null_other_row c_udt_mds1_item_null_other%ROWTYPE; BEGIN -- 设置要返回的变量值 exitcode := -20999; -- 如果传入的参数是BYPASS, 则表示不运行 IF parameter = 'BYPASS' THEN exitcode := 0; -- 返回0表示执行成功 RETURN; END IF; -- 记录具体逻辑开始执行 logger.info (v_logic_proc_name || ':' || parameter || ' ,Start'); ------------------- 具体的业务逻辑 ------------------- -- 1. 将UDT_MDS1_INPUT所有字段输入到MDS1_BCKUP对应同名字段中 pkg_log.info(v_logic_proc_name || ':' || '步骤1,将UDT_MDS1_INPUT的数据备份到MDS1_BCKUP表中'); INSERT INTO scpomgr.MDS1_BCKUP SELECT * FROM scpomgr.UDT_MDS1_INPUT; -- 2. 是否要先清空中间表和UDT_MDS2_REVIEW表中的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤2,删除mid_mds1_mds2和udt_mds2_review表中的数据'); DELETE FROM scpomgr.mid_mds1_mds2; -- 删除中间表数据 DELETE FROM scpomgr.udt_mds2_review; -- 删除UDT_MDS2_REVIEW表数据 -- 3. 循环处理ITEM不为NULL的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤3,循环处理udt_mds1_input中item不为null的数据'); FOR c_mds1_item_notnull_row IN c_udt_mds1_input_item_notnull LOOP -- 使用嵌套内部块 declare -- 定义变量 v_country scpomgr.udt_country_ratio.country%TYPE; -- 定义一个国家变量 v_color scpomgr.udt_basic_info.color%TYPE; -- 定义一个颜色变量 begin -- 3.1 查询udt_country_ratio.country信息,并赋值给v_country BEGIN SELECT country INTO v_country FROM ( SELECT ucr.country FROM scpomgr.udt_country_ratio ucr WHERE ucr.market=c_mds1_item_notnull_row.market AND ucr.itemgroup=c_mds1_item_notnull_row.itemgroup ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_notnull_row.market || '和' || c_mds1_item_notnull_row.itemgroup || '没有在scpomgr.udt_country_ratio查询到对应的国家信息'); prc_udt_error_log(c_mds1_item_notnull_row.itemgroup, c_mds1_item_notnull_row.market, '', '没有在scpomgr.udt_country_ratio查询到对应的国家信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 3.2 查询udt_basic_info.color信息,并赋值给v_color BEGIN SELECT color INTO v_color FROM ( SELECT ubi.color FROM scpomgr.udt_basic_info ubi WHERE ubi.item = c_mds1_item_notnull_row.item ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_notnull_row.item || '没有在scpomgr.udt_basic_info查询到对应的颜色信息'); prc_udt_error_log(c_mds1_item_notnull_row.item, '', '', '没有在scpomgr.udt_basic_info查询到对应的颜色信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 3.3 将数据保存到中间表中 INSERT INTO scpomgr.mid_mds1_mds2(market,brand,itemgroup,item,descr,status,loc,package,config,dmdtype,version_no,country,color, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) VALUES (c_mds1_item_notnull_row.market, c_mds1_item_notnull_row.brand, c_mds1_item_notnull_row.itemgroup, c_mds1_item_notnull_row.item, c_mds1_item_notnull_row.descr, c_mds1_item_notnull_row.status, c_mds1_item_notnull_row.loc, c_mds1_item_notnull_row.package, c_mds1_item_notnull_row.config, c_mds1_item_notnull_row.dmdtype, c_mds1_item_notnull_row.version_no, v_country, v_color, c_mds1_item_notnull_row.w0, c_mds1_item_notnull_row.w1, c_mds1_item_notnull_row.w2, c_mds1_item_notnull_row.w3, c_mds1_item_notnull_row.w4, c_mds1_item_notnull_row.w5, c_mds1_item_notnull_row.w6, c_mds1_item_notnull_row.w7, c_mds1_item_notnull_row.w8, c_mds1_item_notnull_row.w9, c_mds1_item_notnull_row.w10, c_mds1_item_notnull_row.w11, c_mds1_item_notnull_row.w12, c_mds1_item_notnull_row.w13, c_mds1_item_notnull_row.w14, c_mds1_item_notnull_row.w15, c_mds1_item_notnull_row.w16, c_mds1_item_notnull_row.w17, c_mds1_item_notnull_row.w18, c_mds1_item_notnull_row.w19, c_mds1_item_notnull_row.w20, c_mds1_item_notnull_row.w21, c_mds1_item_notnull_row.w22, c_mds1_item_notnull_row.w23, c_mds1_item_notnull_row.w24, c_mds1_item_notnull_row.w25, c_mds1_item_notnull_row.w26, c_mds1_item_notnull_row.w27); end; END LOOP; -- 4. 循环处理ITEM为NULL的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤4,循环处理udt_mds1_input中item为null,但MARKET/itemgroup/package/config不为NULL的数据'); FOR c_mds1_item_null_row IN c_udt_mds1_input_item_null LOOP -- 使用嵌套内部块 declare -- 定义变量 v_item scpomgr.udt_basic_info.item%TYPE; -- 定义item v_descr scpomgr.udt_basic_info.descr%TYPE; -- 定义descr -- 定义国家和国家占比 v_country scpomgr.udt_country_ratio.country%TYPE; -- 国家 v_country_ratio scpomgr.udt_country_ratio.country_ratio%TYPE; -- 国家占比 -- 使用数组定义15种颜色及占比 type v_color_varray is varray(15) of scpomgr.udt_country_ratio.color1%TYPE; -- 颜色 v_color v_color_varray := v_color_varray('', '', '', '', '', '', '', '', '', '', '', '', '', '', ''); type v_color_ratio_varray is varray(15) of scpomgr.udt_country_ratio.color1_ratio%TYPE; -- 颜色占比 v_color_ratio v_color_ratio_varray := v_color_ratio_varray(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); -- 使用数组定义28周的数据量变量 type v_w_varray is varray(28) of scpomgr.udt_mds1_input.w0%TYPE; v_w v_w_varray := v_w_varray(c_mds1_item_null_row.w0,c_mds1_item_null_row.w1,c_mds1_item_null_row.w2,c_mds1_item_null_row.w3,c_mds1_item_null_row.w4,c_mds1_item_null_row.w5, c_mds1_item_null_row.w6,c_mds1_item_null_row.w7,c_mds1_item_null_row.w8,c_mds1_item_null_row.w9,c_mds1_item_null_row.w10,c_mds1_item_null_row.w11, c_mds1_item_null_row.w12,c_mds1_item_null_row.w13,c_mds1_item_null_row.w14,c_mds1_item_null_row.w15,c_mds1_item_null_row.w16,c_mds1_item_null_row.w17, c_mds1_item_null_row.w18,c_mds1_item_null_row.w19,c_mds1_item_null_row.w20,c_mds1_item_null_row.w21,c_mds1_item_null_row.w22,c_mds1_item_null_row.w23, c_mds1_item_null_row.w24,c_mds1_item_null_row.w25,c_mds1_item_null_row.w26,c_mds1_item_null_row.w27); begin -- 4.1 查询udt_country_ratio中抓取对应的country、 country_ratio、color1..15、color1..15_ratio,并赋值给相应的变量 BEGIN SELECT country,country_ratio,color1,color1_ratio,color2,color2_ratio,color3,color3_ratio,color4,color4_ratio,color5,color5_ratio,color6,color6_ratio, color7,color7_ratio,color8,color8_ratio,color9,color9_ratio,color10,color10_ratio,color11,color11_ratio,color12,color12_ratio,color13,color13_ratio, color14,color14_ratio,color15,color15_ratio INTO v_country,v_country_ratio,v_color(1),v_color_ratio(1),v_color(2),v_color_ratio(2),v_color(3),v_color_ratio(3),v_color(4),v_color_ratio(4), v_color(5),v_color_ratio(5),v_color(6),v_color_ratio(6),v_color(7),v_color_ratio(7),v_color(8),v_color_ratio(8),v_color(9),v_color_ratio(9), v_color(10),v_color_ratio(10),v_color(11),v_color_ratio(11),v_color(12),v_color_ratio(12),v_color(13),v_color_ratio(13),v_color(14),v_color_ratio(14), v_color(15),v_color_ratio(15) FROM ( SELECT ucr.country,ucr.country_ratio,ucr.color1,ucr.color1_ratio,ucr.color2,ucr.color2_ratio,ucr.color3,ucr.color3_ratio,ucr.color4,ucr.color4_ratio, ucr.color5,ucr.color5_ratio,ucr.color6,ucr.color6_ratio,ucr.color7,ucr.color7_ratio,ucr.color8,ucr.color8_ratio,ucr.color9,ucr.color9_ratio, ucr.color10,ucr.color10_ratio,ucr.color11,ucr.color11_ratio,ucr.color12,ucr.color12_ratio,ucr.color13,ucr.color13_ratio,ucr.color14,ucr.color14_ratio, ucr.color15,ucr.color15_ratio FROM scpomgr.udt_country_ratio ucr WHERE ucr.market = c_mds1_item_null_row.market AND ucr.itemgroup = c_mds1_item_null_row.itemgroup ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_null_row.market || '和' || c_mds1_item_null_row.itemgroup || '没有在scpomgr.udt_country_ratio查询到对应国家和颜色及占比信息'); prc_udt_error_log(c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.market, '', '没有在scpomgr.udt_country_ratio查询到对应国家和颜色及占比信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 4.2对于country_ratio、color_ratio不为NULL的记录 更新W0-W27的数据 FOR i in 1 .. 15 LOOP -- 先判断有没有颜色,如果没有则不处理 IF(v_color(i) IS NOT NULL AND TRIM(v_color(i)) IS NOT NULL) THEN -- 4.2.1 获取item,descr v_item := ''; v_descr := ''; -- 先设置为默认值 BEGIN SELECT item,descr INTO v_item,v_descr FROM ( SELECT ubi.item,ubi.descr FROM scpomgr.udt_basic_info ubi WHERE ubi.country = v_country AND ubi.itemgroup = c_mds1_item_null_row.itemgroup AND ubi.package = c_mds1_item_null_row.package AND ubi.color = v_color(i) AND ubi.config = c_mds1_item_null_row.config ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_null_row.itemgroup || ',' || c_mds1_item_null_row.package || ',' || v_color(i) || ',' || c_mds1_item_null_row.config || '没有在scpomgr.udt_basic_info查询到对应的item和descr信息'); prc_udt_error_log(c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.package|| '+' || c_mds1_item_null_row.config, '', '没有在scpomgr.udt_basic_info查询到对应的item和descr信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 4.2.2 如果不为空,则要计算v_w的数据 IF(v_country_ratio IS NOT NULL AND v_color_ratio(i) IS NOT NULL) THEN --循环处理v_w0...v_w27的数据 FOR j in 1 .. 28 LOOP v_w(j) := v_w(j) * v_country_ratio * v_color_ratio(i); END LOOP; -- 将处理完的数据插入到中间表中 INSERT INTO scpomgr.mid_mds1_mds2(market,itemgroup,package,config,country,country_ratio,color,color_ratio,item, descr, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) VALUES (c_mds1_item_null_row.market, c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.package, c_mds1_item_null_row.config, v_country, v_country_ratio, v_color(i),v_color_ratio(i), v_item, v_descr, v_w(1), v_w(2), v_w(3), v_w(4), v_w(5), v_w(6), v_w(7), v_w(8), v_w(9), v_w(10), v_w(11), v_w(12), v_w(13), v_w(14), v_w(15), v_w(16),v_w(17), v_w(18), v_w(19), v_w(20), v_w(21), v_w(22), v_w(23), v_w(24), v_w(25), v_w(26), v_w(27), v_w(28)); END IF; END IF; END LOOP; end; END LOOP; -- 5. 对于若UDT_MDS1_INPUT.ITEM为NULL,MARKET、ITEMGROUP、PACKAGE、CONFIG有为NULL的 pkg_log.info(v_logic_proc_name || ':' || '步骤5,循环处理udt_mds1_input中item为null,并且MARKET/itemgroup/package/config有为NULL字段的数据'); FOR c_mds1_item_null_other_row IN c_udt_mds1_item_null_other LOOP -- 使用嵌套内部块 declare -- 定义一个变量保存数据 v_null_filed varchar2(500); begin if(c_mds1_item_null_other_row.market is null or trim(c_mds1_item_null_other_row.market) is null) then v_null_filed := v_null_filed || 'market,'; elsif(c_mds1_item_null_other_row.itemgroup is null or trim(c_mds1_item_null_other_row.itemgroup) is null) then v_null_filed := v_null_filed || 'itemgroup,'; elsif(c_mds1_item_null_other_row.package is null or trim(c_mds1_item_null_other_row.package) is null) then v_null_filed := v_null_filed || 'package,'; elsif(c_mds1_item_null_other_row.config is null or trim(c_mds1_item_null_other_row.config) is null) then v_null_filed := v_null_filed || 'config'; end if; -- 找出哪个字段为空,并记录下来 pkg_log.error(v_logic_proc_name || ':' || 'scpomgr.UDT_MDS1_INPUT表中下面字段为空: ' || v_null_filed); prc_udt_error_log('', '', '', 'scpomgr.UDT_MDS1_INPUT表中下面字段为空: ' || v_null_filed, 1, 'PRC_MDS1_TO_MDS2'); END; END LOOP; -- 6. 将中间表的数据保存到UDT_MDS2_REVIEW表中 pkg_log.info(v_logic_proc_name || ':' || '步骤6,将中间表mid_mds1_mds2的数据保存到udt_mds2_review表中'); INSERT INTO scpomgr.udt_mds2_review(market,brand,itemgroup,item,descr,status,loc,package,color,config,dmdtype,version_no, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) SELECT market,brand,itemgroup,item,descr,status,loc,package,color,config,dmdtype,version_no, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27 FROM scpomgr.mid_mds1_mds2; -- 说明没有错误, 执行成功 exitcode := 0; -- 记录完成 logger.info (v_logic_proc_name || ':' || parameter || ' ,Success'); -- 成功的话, 提交 COMMIT; EXCEPTION WHEN OTHERS THEN exitcode := SQLCODE; -- 返回错误代码 pkg_log.error('Failure:' || v_logic_proc_name || ':' || SQLCODE || ':' || SQLERRM || ':' || substr(dbms_utility.format_error_backtrace, 1, 1024)); COMMIT; END PRC_MDS1_TO_MDS2; 数据库 2019-06-04 16:55:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 在MySql中有两种方法可以清除表数据 DELETE DELETE FROM tablename delete语句可以携带where条件进行删除,所以使用起来很灵活。但是delete语句在删除的过程中会产生事务日志,可以回滚数据,所以性能上面会有所降低! 值得注意的是,当表中有自增字段的时候,清除完数据之后,自增的值将会从1开始,如果不希望如此,可以在delete的时候加上一个永远为真的条件,比如where 1 = 1或者where true等。 TRUNCATE TRUNCATE TABLE tablename truncate是清除表内所有的数据,灵活性不是很好。但是由于不创建事务日志,truncate删除的数据将无法恢复,性能上面将提升很多! 两种方式都可以选择,根据不同需求选择不同的方式! 数据库 2019-06-04 16:24:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 数据仓库的定义之一是反映历史变化,数据或多或少都会包含时间特征,因此日期维度就成了数据仓库中不可或缺的维度之一,可以说在任何一个事实表中都会有一个或者多个日期维度的外键。日期维度可以尽可能多的包含日期的详细信息,比如周几、农历、年、月、日、节假日、季度,甚至是生肖年、干支纪年、星座等信息。 生成日期维度表的方式有很多,可以通过数据库例如MySQL直接生成日期维度,也可以通过编程语言JAVA、PYTHON等来生成。本文采用的是通过PYTHON来实现日期维度表的生成,该方式的优点是生成的日期维度相对详实。先来看一下最终的结果 每一列的含义如下
最后将生成的csv文件导入数据库即可 数据库 2019-06-04 14:47:00 「深度学习福利」大神带你进阶工程师,立即查看>>> Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'userinfo.t_long.user_name' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by 原因: MySQL 5.7.5和up实现了对功能依赖的检测。如果启用了only_full_group_by SQL模式(在默认情况下是这样),那么MySQL就会拒绝选择列表、条件或顺序列表引用的查询,这些查询将引用组中未命名的非聚合列,而不是在功能上依赖于它们。(在5.7.5之前,MySQL没有检测到功能依赖项,only_full_group_by在默认情况下是不启用的。关于前5.7.5行为的描述,请参阅MySQL 5.6参考手册。) 执行以下个命令,可以查看 sql_mode 的内容。 mysql> SHOW SESSION VARIABLES; 1 mysql> SHOW GLOBAL VARIABLES; 1 mysql> select @@sql_mode; 1 可见session和global 的sql_mode的值都为: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION only_full_group_by说明: only_full_group_by :使用这个就是使用和oracle一样的group 规则, select的列都要在group中,或者本身是聚合列(SUM,AVG,MAX,MIN) 才行,其实这个配置目前个人感觉和distinct差不多的,所以去掉就好 官网摘抄: 官网:ONLY_FULL_GROUP_BY Reject queries for which the select list, HAVING condition, or ORDER BY list refer to nonaggregated columns that are neither named in the GROUP BY clause nor are functionally dependent on (uniquely determined by) GROUP BY columns. As of MySQL 5.7.5, the default SQL mode includes ONLY_FULL_GROUP_BY. (Before 5.7.5, MySQL does not detect functional dependency and ONLY_FULL_GROUP_BY is not enabled by default. For a description of pre-5.7.5 behavior, see the MySQL 5.6 Reference Manual.) A MySQL extension to standard SQL permits references in the HAVING clause to aliased expressions in the select list. Before MySQL 5.7.5, enabling ONLY_FULL_GROUP_BY disables this extension, thus requiring the HAVING clause to be written using unaliased expressions. As of MySQL 5.7.5, this restriction is lifted so that the HAVING clause can refer to aliases regardless of whether ONLY_FULL_GROUP_BY is enabled. 解决: 执行以下两个命令: mysql> set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; 1 mysql> set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; 1 这两个命令,去掉 sql_mode 的 ONLY_FULL_GROUP_BY 见其他文章有说: 直接修改mysql配置文件(我的系统是Ubuntu16.04的,在/etc/mysql/mysql.conf.d/mysqld.cnf 中并没有sql_mode这个配置,所以直接加上就好,如果是其他系统有得修改就不用添加了) 这个方法暂时没有式。 mysql 配置信息读取顺序。 ①ps aux|grep mysql|grep ‘my.cnf’ ②mysql –help|grep ‘my.cnf’ /etc/my.cnf, /etc/mysql/my.cnf, /usr/local/etc/my.cnf, ~/.my.cnf 这些就是mysql默认会搜寻my.cnf的目录,顺序排前的优先。mysql按照上面的顺序加载配置文件,后面的配置项会覆盖前面的。 如果没有该文件可以自定义一个文件。然后回默认读取配置中的内容) 查看你需要修改的是哪个配置文件。我只有/etc/my.cnf 只修改这个文件即可 配置文件my.cnf通常会分成好几部分,如[client],[mysqld], [mysql]等等。MySQL程序通常是读取与它同名的分段部分,例如服务器mysqld通常读取[mysqld]分段下的相关配置项。如果配置项位置不正确,该配置是不会生效的 参考:https://stackoverflow.com/questions/37951742/1055-expression-of-select-list-is-not-in-group-by-clause-and-contains-nonaggr 这个语句没试过,先记录: set @@sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; 1 去掉ONLY_FULL_GROUP_BY即可正常执行sql. 数据库 2019-06-04 13:25:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 2019年5月8日-5月10日,由国内知名IT技术社区主办的数据库技术交流盛会——DTCC 2019在北京新云南皇冠假日大酒店召开。数据风云,十年变迁,DTCC见证并铭记了国内数据库技术的关键成长历程。作为DTCC的老朋友和全球领先的云计算厂商,阿里云数据库团队受邀参加本次技术盛会,不仅派出了重量级嘉宾阵容,还为广大数据库业内人士和行业用户奉上了 8场精彩议题 。 洞见前沿技术趋势,见证阿里云数据库成长历程。在这场数据库领域从业人士难得的年度盛会和交流平台上,来看看阿里云技术大牛们都带来了怎样的饕餮盛宴。 8场议题的直播回顾、干货PPT下载及分享实录 汇总如下: 1、阿里云李飞飞:云原生新战场,我们如何把握先机? 直播回顾: https://yq.aliyun.com/live/1043?utm_content=g_1000061144 分享实录文字版: https://yq.aliyun.com/articles/703885?utm_content=g_1000061145 会上, 阿里云POLARDB荣膺2019中国数据库年度最佳创新产品 相关阅读见: https://yq.aliyun.com/articles/702310?utm_content=g_1000061146 2、深度解码阿里数据库实现 数据库内核——基于HLC的分布式事务实现深度剖析 直播回顾: https://yq.aliyun.com/live/1045?utm_content=g_1000061147 分享实录文字版: https://yq.aliyun.com/articles/703552?utm_content=g_1000061148 PPT下载: https://yq.aliyun.com/download/3566?utm_content=g_1000061149 3、海量数据毫秒级分析的背后——《阿里超大规模实时数仓架构挑战与实践解析》 直播回顾: https://yq.aliyun.com/live/1047?utm_content=g_1000061150 分享实录文字版: https://yq.aliyun.com/articles/702765?utm_content=g_1000061151 PPT下载: https://yq.aliyun.com/download/3553?utm_content=g_1000061152 4、前沿技术应用知多少? 阿里云图数据库GDB带你探索互联数据的奥秘 直播回顾: https://yq.aliyun.com/live/1050?utm_content=g_1000061153 分享实录文字版: https://yq.aliyun.com/articles/703444?utm_content=g_1000061154 PPT下载: https://yq.aliyun.com/download/3555?utm_content=g_1000061155 5、把握数据库发展趋势 DBA应如何避免“踩坑”? 直播回顾: https://yq.aliyun.com/live/1046?utm_content=g_1000061156 分享实录文字版: https://yq.aliyun.com/articles/703521?utm_content=g_1000061157 PPT下载: https://yq.aliyun.com/download/3562?utm_content=g_1000061158 6、阿里云TSDB: 教你解锁时序时空数据库的种种黑科技 直播回顾: https://yq.aliyun.com/live/1044?utm_content=g_1000061159 分享实录文字版: https://yq.aliyun.com/articles/703542?utm_content=g_1000061160 PPT下载: https://yq.aliyun.com/download/3563?utm_content=g_1000061161 7、云时代数据库迁移 & 容灾技术新进展与应用 直播回顾: https://yq.aliyun.com/live/1048?utm_content=g_1000061162 分享实录文字版: https://yq.aliyun.com/articles/703544?utm_content=g_1000061163 PPT下载: https://yq.aliyun.com/download/3564?utm_content=g_1000061164 8、NoSQL数据库最新发展趋势 如何拥有居家必备的企业级能力? 直播回顾: https://yq.aliyun.com/live/1049?utm_content=g_1000061165 分享实录文字版: https://yq.aliyun.com/articles/703549?utm_content=g_1000061166 PPT下载: https://yq.aliyun.com/download/3565?utm_content=g_1000061167 作者:七幕 原文链接 本文为云栖社区原创内容,未经允许不得转载。 数据库 2019-06-04 13:00:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 1.基本结构: CREATE OR REPLACE PROCEDURE 存储过程名字 ( 参数1 IN NUMBER, 参数2 IN NUMBER ) IS 变量1 INTEGER :=0; 变量2 DATE; BEGIN END 存储过程名字 2.SELECT INTO STATEMENT 将select查询的结果存入到变量中,可以同时将多个列存储多个变量中,必须有一条 记录,否则抛出异常(如果没有记录抛出NO_DATA_FOUND) 例子: BEGIN SELECT col1,col2 into 变量1,变量2 FROM typestruct where xxx; EXCEPTION WHEN NO_DATA_FOUND THEN xxxx; END; ... 3.IF 判断 IF V_TEST=1 THEN BEGIN do something END; END IF; 4.while 循环 WHILE V_TEST=1 LOOP BEGIN XXXX END; END LOOP; 5.变量赋值 V_TEST := 123; 6.用for in 使用cursor ... IS CURSOR cur IS SELECT * FROM xxx; BEGIN FOR cur_result in cur LOOP BEGIN V_SUM :=cur_result.列名1+cur_result.列名2 END; END LOOP; END; 7.带参数的cursor CURSOR C_USER(C_ID NUMBER) IS SELECT NAME FROM USER WHERE TYPEID=C_ID; OPEN C_USER(变量值); LOOP FETCH C_USER INTO V_NAME; EXIT FETCH C_USER%NOTFOUND; do something END LOOP; CLOSE C_USER; 简单示例: CREATE OR REPLACE NONEDITIONABLE PROCEDURE PRC_MDS1_TO_MDS2 (parameter IN VARCHAR2, exitcode OUT INT) AS -- 定义一个变量用来保存此存储过程的名字 v_logic_proc_name VARCHAR2(30) := 'PRC_MDS1_TO_MDS2'; -- 声明显式游标, 查询udt_mds1_input中item不为null的数据 CURSOR c_udt_mds1_input_item_notnull IS SELECT * FROM scpomgr.udt_mds1_input WHERE item IS NOT NULL AND TRIM(item) IS NOT NULL; -- 定义游标变量,该变量的类型为基于游标c_udt_mds1_input_item_notnull的记录 c_mds1_item_notnull_row c_udt_mds1_input_item_notnull%ROWTYPE; -- 声明一个游标,查询udt_mds1_input中item为null的数据 -- MARKET、itemgroup、package、config不为NULL CURSOR c_udt_mds1_input_item_null IS SELECT * FROM scpomgr.udt_mds1_input WHERE (item IS NULL OR TRIM(item) IS NULL) AND market IS NOT NULL AND TRIM(market) IS NOT NULL AND itemgroup IS NOT NULL AND TRIM(itemgroup) IS NOT NULL AND package IS NOT NULL AND TRIM(package) IS NOT NULL AND config IS NOT NULL AND TRIM(config) IS NOT NULL; -- 定义游标变量 c_mds1_item_null_row c_udt_mds1_input_item_null%ROWTYPE; -- 再声明一个游标,查询udt_mds1_input中item为null -- MARKET、ITEMGROUP、PACKAGE、CONFIG有为NULL的 CURSOR c_udt_mds1_item_null_other IS SELECT * FROM scpomgr.udt_mds1_input WHERE (item IS NULL OR TRIM(item) IS NULL) AND (market IS NULL OR TRIM(market) IS NULL OR itemgroup IS NULL OR TRIM(itemgroup) IS NULL OR package IS NULL OR TRIM(package) IS NULL OR config IS NULL OR TRIM(config) IS NULL); -- 定义游标变量 c_mds1_item_null_other_row c_udt_mds1_item_null_other%ROWTYPE; BEGIN -- 设置要返回的变量值 exitcode := -20999; -- 如果传入的参数是BYPASS, 则表示不运行 IF parameter = 'BYPASS' THEN exitcode := 0; -- 返回0表示执行成功 RETURN; END IF; -- 记录具体逻辑开始执行 logger.info (v_logic_proc_name || ':' || parameter || ' ,Start'); ------------------- 具体的业务逻辑 ------------------- -- 1. 将UDT_MDS1_INPUT所有字段输入到MDS1_BCKUP对应同名字段中 pkg_log.info(v_logic_proc_name || ':' || '步骤1,将UDT_MDS1_INPUT的数据备份到MDS1_BCKUP表中'); INSERT INTO scpomgr.MDS1_BCKUP SELECT * FROM scpomgr.UDT_MDS1_INPUT; -- 2. 是否要先清空中间表和UDT_MDS2_REVIEW表中的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤2,删除mid_mds1_mds2和udt_mds2_review表中的数据'); DELETE FROM scpomgr.mid_mds1_mds2; -- 删除中间表数据 DELETE FROM scpomgr.udt_mds2_review; -- 删除UDT_MDS2_REVIEW表数据 -- 3. 循环处理ITEM不为NULL的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤3,循环处理udt_mds1_input中item不为null的数据'); FOR c_mds1_item_notnull_row IN c_udt_mds1_input_item_notnull LOOP -- 使用嵌套内部块 declare -- 定义变量 v_country scpomgr.udt_country_ratio.country%TYPE; -- 定义一个国家变量 v_color scpomgr.udt_basic_info.color%TYPE; -- 定义一个颜色变量 begin -- 3.1 查询udt_country_ratio.country信息,并赋值给v_country BEGIN SELECT country INTO v_country FROM ( SELECT ucr.country FROM scpomgr.udt_country_ratio ucr WHERE ucr.market=c_mds1_item_notnull_row.market AND ucr.itemgroup=c_mds1_item_notnull_row.itemgroup ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_notnull_row.market || '和' || c_mds1_item_notnull_row.itemgroup || '没有在scpomgr.udt_country_ratio查询到对应的国家信息'); prc_udt_error_log(c_mds1_item_notnull_row.itemgroup, c_mds1_item_notnull_row.market, '', '没有在scpomgr.udt_country_ratio查询到对应的国家信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 3.2 查询udt_basic_info.color信息,并赋值给v_color BEGIN SELECT color INTO v_color FROM ( SELECT ubi.color FROM scpomgr.udt_basic_info ubi WHERE ubi.item = c_mds1_item_notnull_row.item ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_notnull_row.item || '没有在scpomgr.udt_basic_info查询到对应的颜色信息'); prc_udt_error_log(c_mds1_item_notnull_row.item, '', '', '没有在scpomgr.udt_basic_info查询到对应的颜色信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 3.3 将数据保存到中间表中 INSERT INTO scpomgr.mid_mds1_mds2(market,brand,itemgroup,item,descr,status,loc,package,config,dmdtype,version_no,country,color, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) VALUES (c_mds1_item_notnull_row.market, c_mds1_item_notnull_row.brand, c_mds1_item_notnull_row.itemgroup, c_mds1_item_notnull_row.item, c_mds1_item_notnull_row.descr, c_mds1_item_notnull_row.status, c_mds1_item_notnull_row.loc, c_mds1_item_notnull_row.package, c_mds1_item_notnull_row.config, c_mds1_item_notnull_row.dmdtype, c_mds1_item_notnull_row.version_no, v_country, v_color, c_mds1_item_notnull_row.w0, c_mds1_item_notnull_row.w1, c_mds1_item_notnull_row.w2, c_mds1_item_notnull_row.w3, c_mds1_item_notnull_row.w4, c_mds1_item_notnull_row.w5, c_mds1_item_notnull_row.w6, c_mds1_item_notnull_row.w7, c_mds1_item_notnull_row.w8, c_mds1_item_notnull_row.w9, c_mds1_item_notnull_row.w10, c_mds1_item_notnull_row.w11, c_mds1_item_notnull_row.w12, c_mds1_item_notnull_row.w13, c_mds1_item_notnull_row.w14, c_mds1_item_notnull_row.w15, c_mds1_item_notnull_row.w16, c_mds1_item_notnull_row.w17, c_mds1_item_notnull_row.w18, c_mds1_item_notnull_row.w19, c_mds1_item_notnull_row.w20, c_mds1_item_notnull_row.w21, c_mds1_item_notnull_row.w22, c_mds1_item_notnull_row.w23, c_mds1_item_notnull_row.w24, c_mds1_item_notnull_row.w25, c_mds1_item_notnull_row.w26, c_mds1_item_notnull_row.w27); end; END LOOP; -- 4. 循环处理ITEM为NULL的数据 pkg_log.info(v_logic_proc_name || ':' || '步骤4,循环处理udt_mds1_input中item为null,但MARKET/itemgroup/package/config不为NULL的数据'); FOR c_mds1_item_null_row IN c_udt_mds1_input_item_null LOOP -- 使用嵌套内部块 declare -- 定义变量 v_item scpomgr.udt_basic_info.item%TYPE; -- 定义item v_descr scpomgr.udt_basic_info.descr%TYPE; -- 定义descr -- 定义国家和国家占比 v_country scpomgr.udt_country_ratio.country%TYPE; -- 国家 v_country_ratio scpomgr.udt_country_ratio.country_ratio%TYPE; -- 国家占比 -- 使用数组定义15种颜色及占比 type v_color_varray is varray(15) of scpomgr.udt_country_ratio.color1%TYPE; -- 颜色 v_color v_color_varray := v_color_varray('', '', '', '', '', '', '', '', '', '', '', '', '', '', ''); type v_color_ratio_varray is varray(15) of scpomgr.udt_country_ratio.color1_ratio%TYPE; -- 颜色占比 v_color_ratio v_color_ratio_varray := v_color_ratio_varray(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); -- 使用数组定义28周的数据量变量 type v_w_varray is varray(28) of scpomgr.udt_mds1_input.w0%TYPE; v_w v_w_varray := v_w_varray(c_mds1_item_null_row.w0,c_mds1_item_null_row.w1,c_mds1_item_null_row.w2,c_mds1_item_null_row.w3,c_mds1_item_null_row.w4,c_mds1_item_null_row.w5, c_mds1_item_null_row.w6,c_mds1_item_null_row.w7,c_mds1_item_null_row.w8,c_mds1_item_null_row.w9,c_mds1_item_null_row.w10,c_mds1_item_null_row.w11, c_mds1_item_null_row.w12,c_mds1_item_null_row.w13,c_mds1_item_null_row.w14,c_mds1_item_null_row.w15,c_mds1_item_null_row.w16,c_mds1_item_null_row.w17, c_mds1_item_null_row.w18,c_mds1_item_null_row.w19,c_mds1_item_null_row.w20,c_mds1_item_null_row.w21,c_mds1_item_null_row.w22,c_mds1_item_null_row.w23, c_mds1_item_null_row.w24,c_mds1_item_null_row.w25,c_mds1_item_null_row.w26,c_mds1_item_null_row.w27); begin -- 4.1 查询udt_country_ratio中抓取对应的country、 country_ratio、color1..15、color1..15_ratio,并赋值给相应的变量 BEGIN SELECT country,country_ratio,color1,color1_ratio,color2,color2_ratio,color3,color3_ratio,color4,color4_ratio,color5,color5_ratio,color6,color6_ratio, color7,color7_ratio,color8,color8_ratio,color9,color9_ratio,color10,color10_ratio,color11,color11_ratio,color12,color12_ratio,color13,color13_ratio, color14,color14_ratio,color15,color15_ratio INTO v_country,v_country_ratio,v_color(1),v_color_ratio(1),v_color(2),v_color_ratio(2),v_color(3),v_color_ratio(3),v_color(4),v_color_ratio(4), v_color(5),v_color_ratio(5),v_color(6),v_color_ratio(6),v_color(7),v_color_ratio(7),v_color(8),v_color_ratio(8),v_color(9),v_color_ratio(9), v_color(10),v_color_ratio(10),v_color(11),v_color_ratio(11),v_color(12),v_color_ratio(12),v_color(13),v_color_ratio(13),v_color(14),v_color_ratio(14), v_color(15),v_color_ratio(15) FROM ( SELECT ucr.country,ucr.country_ratio,ucr.color1,ucr.color1_ratio,ucr.color2,ucr.color2_ratio,ucr.color3,ucr.color3_ratio,ucr.color4,ucr.color4_ratio, ucr.color5,ucr.color5_ratio,ucr.color6,ucr.color6_ratio,ucr.color7,ucr.color7_ratio,ucr.color8,ucr.color8_ratio,ucr.color9,ucr.color9_ratio, ucr.color10,ucr.color10_ratio,ucr.color11,ucr.color11_ratio,ucr.color12,ucr.color12_ratio,ucr.color13,ucr.color13_ratio,ucr.color14,ucr.color14_ratio, ucr.color15,ucr.color15_ratio FROM scpomgr.udt_country_ratio ucr WHERE ucr.market = c_mds1_item_null_row.market AND ucr.itemgroup = c_mds1_item_null_row.itemgroup ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_null_row.market || '和' || c_mds1_item_null_row.itemgroup || '没有在scpomgr.udt_country_ratio查询到对应国家和颜色及占比信息'); prc_udt_error_log(c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.market, '', '没有在scpomgr.udt_country_ratio查询到对应国家和颜色及占比信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 4.2对于country_ratio、color_ratio不为NULL的记录 更新W0-W27的数据 FOR i in 1 .. 15 LOOP -- 先判断有没有颜色,如果没有则不处理 IF(v_color(i) IS NOT NULL AND TRIM(v_color(i)) IS NOT NULL) THEN -- 4.2.1 获取item,descr v_item := ''; v_descr := ''; -- 先设置为默认值 BEGIN SELECT item,descr INTO v_item,v_descr FROM ( SELECT ubi.item,ubi.descr FROM scpomgr.udt_basic_info ubi WHERE ubi.country = v_country AND ubi.itemgroup = c_mds1_item_null_row.itemgroup AND ubi.package = c_mds1_item_null_row.package AND ubi.color = v_color(i) AND ubi.config = c_mds1_item_null_row.config ) WHERE ROWNUM=1; EXCEPTION WHEN no_data_found THEN pkg_log.error(v_logic_proc_name || ':' || '根据' || c_mds1_item_null_row.itemgroup || ',' || c_mds1_item_null_row.package || ',' || v_color(i) || ',' || c_mds1_item_null_row.config || '没有在scpomgr.udt_basic_info查询到对应的item和descr信息'); prc_udt_error_log(c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.package|| '+' || c_mds1_item_null_row.config, '', '没有在scpomgr.udt_basic_info查询到对应的item和descr信息', 1, 'PRC_MDS1_TO_MDS2'); END; -- 4.2.2 如果不为空,则要计算v_w的数据 IF(v_country_ratio IS NOT NULL AND v_color_ratio(i) IS NOT NULL) THEN --循环处理v_w0...v_w27的数据 FOR j in 1 .. 28 LOOP v_w(j) := v_w(j) * v_country_ratio * v_color_ratio(i); END LOOP; -- 将处理完的数据插入到中间表中 INSERT INTO scpomgr.mid_mds1_mds2(market,itemgroup,package,config,country,country_ratio,color,color_ratio,item, descr, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) VALUES (c_mds1_item_null_row.market, c_mds1_item_null_row.itemgroup, c_mds1_item_null_row.package, c_mds1_item_null_row.config, v_country, v_country_ratio, v_color(i),v_color_ratio(i), v_item, v_descr, v_w(1), v_w(2), v_w(3), v_w(4), v_w(5), v_w(6), v_w(7), v_w(8), v_w(9), v_w(10), v_w(11), v_w(12), v_w(13), v_w(14), v_w(15), v_w(16),v_w(17), v_w(18), v_w(19), v_w(20), v_w(21), v_w(22), v_w(23), v_w(24), v_w(25), v_w(26), v_w(27), v_w(28)); END IF; END IF; END LOOP; end; END LOOP; -- 5. 对于若UDT_MDS1_INPUT.ITEM为NULL,MARKET、ITEMGROUP、PACKAGE、CONFIG有为NULL的 pkg_log.info(v_logic_proc_name || ':' || '步骤5,循环处理udt_mds1_input中item为null,并且MARKET/itemgroup/package/config有为NULL字段的数据'); FOR c_mds1_item_null_other_row IN c_udt_mds1_item_null_other LOOP -- 使用嵌套内部块 declare -- 定义一个变量保存数据 v_null_filed varchar2(500); begin if(c_mds1_item_null_other_row.market is null or trim(c_mds1_item_null_other_row.market) is null) then v_null_filed := v_null_filed || 'market,'; elsif(c_mds1_item_null_other_row.itemgroup is null or trim(c_mds1_item_null_other_row.itemgroup) is null) then v_null_filed := v_null_filed || 'itemgroup,'; elsif(c_mds1_item_null_other_row.package is null or trim(c_mds1_item_null_other_row.package) is null) then v_null_filed := v_null_filed || 'package,'; elsif(c_mds1_item_null_other_row.config is null or trim(c_mds1_item_null_other_row.config) is null) then v_null_filed := v_null_filed || 'config'; end if; -- 找出哪个字段为空,并记录下来 pkg_log.error(v_logic_proc_name || ':' || 'scpomgr.UDT_MDS1_INPUT表中下面字段为空: ' || v_null_filed); prc_udt_error_log('', '', '', 'scpomgr.UDT_MDS1_INPUT表中下面字段为空: ' || v_null_filed, 1, 'PRC_MDS1_TO_MDS2'); END; END LOOP; -- 6. 将中间表的数据保存到UDT_MDS2_REVIEW表中 pkg_log.info(v_logic_proc_name || ':' || '步骤6,将中间表mid_mds1_mds2的数据保存到udt_mds2_review表中'); INSERT INTO scpomgr.udt_mds2_review(market,brand,itemgroup,item,descr,status,loc,package,color,config,dmdtype,version_no, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27) SELECT market,brand,itemgroup,item,descr,status,loc,package,color,config,dmdtype,version_no, w0,w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12,w13,w14,w15,w16,w17,w18,w19,w20,w21,w22,w23,w24,w25,w26,w27 FROM scpomgr.mid_mds1_mds2; -- 说明没有错误, 执行成功 exitcode := 0; -- 记录完成 logger.info (v_logic_proc_name || ':' || parameter || ' ,Success'); -- 成功的话, 提交 COMMIT; EXCEPTION WHEN OTHERS THEN exitcode := SQLCODE; -- 返回错误代码 pkg_log.error('Failure:' || v_logic_proc_name || ':' || SQLCODE || ':' || SQLERRM || ':' || substr(dbms_utility.format_error_backtrace, 1, 1024)); COMMIT; END PRC_MDS1_TO_MDS2; 数据库 2019-06-04 11:41:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 摘要:阿里云TSDB是阿里自研的一种高性能,低成本,稳定可靠的在线时序时空数据库产品。该产品统一了阿里巴巴集团90%以上的APM数据和事件型数据的存储和计算,并在广泛应用于外部的物联网,工业制造,电力,化工以及IT运维等行业。本文中,阿里云智能数据库产品事业部技术专家伊翼就为大家介绍了阿里云TSDB的种种黑科技。 专家简介:伊翼(花名:老滚)。阿里云智能数据库产品事业部技术专家,主要从事TSDB核心引擎的研发工作。 直播回放 链接 : https://yq.aliyun.com/live/1044 议题PPT下载,戳这里! https://yq.aliyun.com/download/3563 本次分享的内容主要包括以下四个方面: 走进时序数据库 认识阿里云TSDB 阿里云TSDB技术内幕 未来与展望 一、走进时序数据库 熟悉而又陌生的时序数据 时序数据库本身是一个比较新的概念,直到5年前,DB-Engine才将时序数据库列为一个独立的分类。虽然时序数据库的概念比较新,但是时序数据却由来已久。从古至今,在我们的日常生活中,时序数据从未缺席。古代记录灾害与祥瑞出现时间的县志也能够发挥类似今天时序数据库的作用,帮助决策者指定相关的决策,地方官员可以根据县志中的记录判断是否需要进行祭祀,也可以决策是否需要向中央朝廷报告祥瑞以谋取升迁等,因此当时的县志也发挥了类似于OLAP的功能。但由于理念和技术的限制,当时所记录的时序数据信息是有限的,精度也是有限的。 技术发展到今天,时序数据所能记录的信息和精度都有了极大的提升。如下图所示的是杭州市空气监测时序数据片段。由此可以看出,时序数据有一些共同的特征,比如多样的指标值、比较稳定的采集频率以及任何一个数据点都有时间戳。在技术飞速发展的今天,时序数据的规模越来越大,增长速度也越来越快。因此,我们需要面对一些问题,比如面对如此大规模的时序数据,应该将其存放在哪里。 时序数据库的概念 在十几年前,时序数据只能选择存放在关系型数据库中,但是随着通信技术的发展,特别是互联网技术的发展,时序数据的增长速度呈现指数级别,使用关系型数据库来存储时序数据显然跟不上时代的节奏了,所以时序数据库应运而生。时序数据库就是一类专门为处理时间序列数据而设计并优化的数据库管理系统。 相较传统的关系型数据库,时序数据库的特点如下: 存储的任何一条数据记录都必然带一个时间戳 通常高频访问热数据 数据写入频率相对稳定,且远大于数据读取的频率 通常按照时间窗口查询数据 基本不提供单点数据的更新或删除功能 无需提供类似关系型数据库事务级别的数据强一致性 目前,使用时序数据库的行业应用越来越广泛。 电力行业:智能电表、电网、发电设备的集中监测 交通行业:实时路况,路口流量监测,卡口数据的采集与分析 石油石化:油井、运输管线、运输车队的实时监测 物流行业:车辆、集装箱的追踪监测 环境监测:天气、空气、水文、地质环境等监测 物联网:电梯、锅炉、机械、水表、气表等各种联网设备的数据采集、分析与检测 军工行业:军事装备的数据采集、存储与分析 制造业:生产过程管控,流程数据、供应链数据采集与分析 互联网:互联网应用的PV/UV数据,基础设施的性能监控 时序数据库的迅猛发展 由于时序数据库的适用性非常广泛,因此其在DB-Engine上的受关注度一直处于增长态势。面对这样的关注度增长态势,时序数据库技术的发展也作出了积极的响应。无论是在开源领域还是商用领域,都推出了大量的时序数据库产品,比如InfluxDB、OpenTSDB、TimescaleDB以及阿里云时序时空TSDB等。 二、认识阿里云TSDB 阿里云时序时空TSDB架构 如下图所示的是阿里云时序时空TSDB的整体架构,从左到右依次是采集端、TSDB服务端以及靠近最终用户和开发者的实例端。在采集端,阿里云时序时空TSDB采用了边缘计算的解决方案,其可以应用在资源受限或者网络状况不稳定的场景下。采集端可以和服务端进行打通,服务端可以向边缘下发各种各样的规则,使得边缘端能够直接进行数据清洗和计算,这就实现了“边云一体化”。图中的中间部分是TSDB的服务端,它也分为几个组件,TS计算引擎主要负责预聚合、降精度以及持续查询,TSQL引擎主要负责处理SQL查询,此外还有一个基于已经训练好的模型算法库,提供各行业定制化解决方案的智能引擎。在这三个引擎下面就是TSDB的时序引擎。 接下来为大家介绍阿里云时序时空TSDB在功能层面的一些特性。 特性1:强力的数据模型支持 阿里云TSDB支持多样的数据模型,同时支持了多值模型和单值模型。举例而言,温度监控设备需要每间隔一段时间向数据库上报温度数据,其上报的数据中必然带有一个时间戳以及温度值,这样最基础的数据形式称之为单值模型。而如果上报的数据中不仅仅包含了一个时间戳和室内温度,还包含了室外温度以及空气湿度等,这样的数据就可以称之为多值模型。其实,时序数据库对于多值模型的支持并不是行业要求,因此即便是在开源领域,各种数据库对于多值模型的支持也不同。支持多值模型的好处在于可以提升数据的写入效率,另外一方面就是对于业务应用的开发者而言可以使得设计更加直观。 除了对于多值模型的支持之外,阿里云TSDB还支持多种的数据类型,不仅支持传统数据类型,还能够支持字符串类型数据,并且能够支持精确到毫秒的时间戳。 特性2:降采样&数据聚合 对于时序数据库而言,降采样和数据聚合也是非常重要的特性。依旧以温度采集为例,温度采集设备可能上报数据的频率非常高,比如每秒钟上传一次数据,但是在做数据查询的时候并不需要按照原始的数据采集频率进行分析和展示,因此就需要对于上报的数据进行降采样操作,比如将按秒采样的数据降采样为按小时或者按天进行分析和展示。 与之相对的,数据聚合在分析和展示中也非常重要。通常情况下,有很多个数据采集设备,不同设备每隔一段时间上报数据的时候就认为这些数据属于不同的时间序列,而随着设备的增多,必然使得时间序列变得非常多,而在做分析和查询的时候并不需要对多个时间序列进行分析,只需要将其进行汇总,比如使用汇总后的平均值进行分析。这种情况下就是对于一个数据的指标值按照时间维度将多个时间序列聚合成一条,这就是数据聚合。无论是降采样还是数据聚合,阿里云TSDB都提供了非常丰富的聚合算子,有了这样的能力,就可以仅凭借阿里云原生能力来满足各种复杂的查询分析场景。 特性3:SQL查询能力 由于时序数据库本身属于比较新的概念,为了降低开发人员以及数据分析人员使用时序数据库的门槛和学习成本,阿里云TSDB也提供了基于SQL的查询接口。有了SQL的查询接口,用户就可以非常方便地使用SQL来操作时序模型。而阿里云TSDB的SQL接口也基于时序场景进行了算法上的优化,可以将SQL中的过滤、聚合等操作全部下推到TSDB的内核中,这样就可以最优化的方式来处理时序数据的分析和查询。 特性4:内置对接Prometheus 在最新版的阿里云TSDB中,已经实现了内置对接Prometheus的能力。Prometheus是一个非常适用于监控Kubernetes集群的工具,但是其对于监控数据的存储能力比较薄弱,虽然社区也考虑到这一点并且提供了Prometheus Adapter的第三方组件来将Prometheus的数据对接到各种各样的数据源上,但是当数据链路中增加一个组件就意味着查询性能的降低。为了在阿里云TSDB对接Prometheus的同时保持较高的查询效率,TSDB内置了对接Prometheus的能力。经过测试,内置对接Prometheus的方式相对于经由Prometheus Adapter中转方式的查询性能要高很多。 特性5:边缘计算能力 阿里云TSDB的边缘端计算能力处于行业内的领先地位。因为在物联网应用和工业大数据的应用场景中,无法保证数据的采集端是实时在线的,这样的场景就是边缘计算的用武之地。考虑到用户数据的可用性,TSDB边缘端再设计的时候也采用了高可用架构。当网络状况恢复稳定的时候,边缘段会将数据同步给阿里云TSDB服务端,这样可以方便用户在服务端进行统一的数据分析和查询。 与其他时序数据库的功能对比 下图中的表格列出了目前主流的时序数据库在功能特性上的支持情况对比。 接下来为大家介绍几个阿里云TSDB实际的应用案例。 案例1: 某互联网餐饮系统研发企业 该企业在自己的解决方案中将阿里云TSDB整合了进去,利用阿里云TSDB高性能写入将整个链路中的所有时序数据以及业务指标全部写入了TSDB中,借助TSDB优越的查询性能以及将监控系统整合在一起,从而支持了对于整个解决方案中所有链路节点的实时监控,与此同时提高了系统的整体稳定性。 案例2:某直播平台运维监控APM 该直播平台原来的APM系统中将所有采集到的时序数据全部通过消息队列存储到OpenTSDB集群中,但是很快就发现OpenTSDB的写入存在瓶颈,而且OpenTSDB在时序索引方面天生存在薄弱点,因此在面向较为复杂的查询的时候,几乎处于不可用的状态。在经过比较之后,该直播平台选择使用阿里云TSDB来替换所有的OpenTSDB,并且加大了写入规模,从实际效果来看,阿里云TSDB达到了所期望的效果。 案例3: 阿里巴巴集团内部全业务对接 最后的一个案例是阿里巴巴集团内部的案例。从上图可以看出,无论是底层的资源调控、整体监控还是上层应用,阿里云TSDB已经覆盖了阿里集团内部的130余个线上业务。而在2018年双11大促期间,阿里云TSDB承接的来自于阿里集团内部的各个业务的时序数据,写入TPS峰值达到了4000万TPS,查询峰值达到了2万QPS,累计时间线数量超过了100亿。 三、阿里云TSDB技术内幕 时序时空TSDB引擎的核心技术 阿里云时序时空TSDB引擎具有很多的核心技术,在本次分享中主要为大家介绍数据压缩、时序索引以及聚合引擎三个方面的核心技术。 数据压缩 时序数据的规模增长速度很快,而用户往往出于日后需要进行查询或者分析的考虑,希望所能够存储的时序数据越多越好。但是通常情况下,对于大规模时序数据的查询而言,往往非常困难。一方面需要满足用户对于查询的需求,另外一方面需要有效地降低用户存储的成本。针对于以上两方面的诉求,阿里云TSDB研发了一套数据压缩技术。下图中左侧是一张示意图,其每一行代表一个时间序列,其列代表数据点。在没有进行数据压缩的情况下,如果想要将其数据调整到毫秒级别,就会发现其列数会增加到360万,这样的数据量是非常可观的,所以必须要进行压缩。阿里云TSDB所采用的压缩思路借鉴了Facebook Gorilla的实现思路,会将时间戳和数据两块压缩成两个大数据块,对时间戳采用了delta-delta的压缩方法,而对于不同的数据类型则采用了相应的数据压缩算法。在压缩成两个大数据块基础之上,再对其进行通用的块压缩。经过两部分的压缩就使得数据压缩比达到15:1的效果。 如下图所示的是真实场景下的数据压缩效果。原始情况下数据大约6TB,一开始尝试最普通的块压缩,将数据压缩到了715G,但此时的数据压缩比不到10:1,而采用先进行时序压缩再追加一次块压缩后使得最终数据压缩为413G,压缩比达到了15:1。那么,追求如此之高的数据压缩比有什么好处呢?其实主要有两个好处,第一个好处就是能够帮助用户降低存储成本;另外一个好处就是因为数据压缩比很大,因此当在进行大范围的时序数据查询的时候,IO效率会非常高,在这个例子中可以将查询延时降低约50%。 时序索引 TSDB的整体查询流程非常简单,当用户指定了一个查询条件,阿里云TSDB首先会解析这个查询条件,同时做一定程度的优化。接下来会做两件事情,一件是将查询条件扔给时序索引模块,时序索引模块会根据查询条件计算命中的时间线数量以及相关信息,拿到时间线信息之后再将时间线集合扔给聚合索引,聚合索引再到底层存储上面获取相应的时间数据并进行降采样、聚合等操作。虽然这一过程看上去比较简单,但是却存在很多值得研究的点。 如下图所示的是时间线的生命周期,如果用户想要查询T2-T3时间范围内的数据,肯定不希望数据中包含T0-T2已经消亡或者说不再有新的数据进来的时间线,所以这部分也是时序索引可以进一步研究的地方。 对于时序索引而言,还需要支持模糊查询,所谓模糊查询就是给出的并不是一个完整的时间序列定义,而可能是Tag的全量匹配,或者基于Tag或者Tag Value的模糊查询,需要能够找到相应的时间线,此时就需要基于Tag Key或者Tag Value做一个倒排索引。在时间序列生命周期的启发下,在倒排索引技术基础之上,TSDB增加了时间序列生命周期的倒排索引。同时加上对于生命周期的进一步过滤,最终得到相对较少的时间线。将这些相对较少的时间线扔给下一层进行计算的时候就会带来一个好处就是减少了IO,提供了极致的查询性能。除了上述优化工作之外,TSDB还将整个倒排索引持久化到存储层,这使得索引节点可以处于无状态的结构,并且使得水平扩展变得非常容易,对于时间线数据实现TTL。 在时序索引模块中还实现了一个评估器, 评估器会以自己认为的最合适的方式计算时间线,因为当查询条件非常复杂的时候,计算时间线的方式有很多种,就好比在关系型数据库中做Join也有很多种方法。评估器会根据几个相应的来源做评估,分别是HHL技术器、BloomFilter以及时序索引缓存。HHL计数器所记录的就是倒排索引中命中的时间线数量,BloomFilter记录的是时间线是否还真实存在的情况,这两部分数据并不是非常精确的,它们是在过去写入查询的过程中所得到的粗略统计数据。但是当时间线本身出现量级差异的时候,这样的评估就会变得非常有意义,其能够以最优化的代价来获取时间线。因此,评估器的作用就类似于关系型数据库中的CBO (Cost-based Optimizer)。 聚合引擎 时序索引的下个模块就是聚合引擎,时序索引将查询条件所命中的时间线集合获取之后交给聚合索引。而聚合索引就是按照传统关系型数据库的执行器的火山模型模型进行设计的,我们为其设计了很多的聚合算子和插值算子,这些算子都是以Pipeline方式进行一轮轮迭代的。目前,一共实现了10多个核心聚合算子,20多个填充策略以及10多个插值算法,并且这些算子的数量还在不断地增加中。 借助聚合引擎,可以减少内存开销以及对于底层存储的查询压力,这是因为有了算子的支持之后,只需要每次抓取少批量数据进行计算即可。此外,聚合引擎和预聚合、降采样也进行了无缝对接,当数据写入的时候已经实施了采样过程,在实际查询的时候就可以很容易地实现采样,聚合引擎就不会从存储层捞取原始数据,而是直接捞取预降采样数据,从而进行进一步的数据计算,这就减少了底层存储的IO操作。 四、未来与展望 最后为大家介绍一下,阿里云数据库技术团队目前在时序时空领域所做的工作和尝试。 阿里云TSDB自研引擎的未来像 阿里云TSDB自研引擎的未来四个发展方向主要包含四点: 冷热数据异构存储;对于用户而言,往往希望将时序数据全部保存下来,因此需要考虑写入、存储以及查询成本等。而对于时序数据而言,用户往往对于热数据更感兴趣,因此可能需要考虑冷热数据异构存储。目前,TSDB正在尝试与阿里云OSS存储解决方案进行打通。 Serverless读写能力;希望能够赋予阿里云TSDB以Serverless读写能力,拥有该项能力就可以进一步降低存储和计算成本。 拥抱时序生态;未来,TSDB还将更加紧密地拥抱时序生态。TSDB目前已经对接了Prometheus的生态,而在未来会进一步对接更多开源生态,希望能够通过与生态的对接使得阿里云TSDB中一些比较有意义的特性能够发挥更大的价值。 时序智能分析;阿里云TSDB也希望能够在时序智能方面训练更多的模型,并且深入到各个具体的行业中,为行业解决特定的问题。 时序时空领域的新尝试 阿里数据库团队除了在自研时序数据库方面做了大量的工作之外,还在其他方面进行大量的尝试。比如开源版时序数据库InfluxDB的云端产品化——TSDB for InfluxDB,其瞄准的痛点就是如果用户使用开源版本的自建InfluxDB时,会感觉到内存管理不稳定,在进行一些稍微复杂的查询时就会触发OOM。TSDB for InfluxDB会优化和重构InfluxDB的内存管理模块,提高稳定性。 TSDB for InfluxDB 因为时序数据库在整个业界而言都是比较新的技术,可以想象如果使用自建的InfluxDB,运维成本就会非常高,而如果使用了TSDB for InfluxDB的基于阿里云服务,运维成本就会极大地降低,除了能够得到99.9%的SLA承诺,并能够得到InfluxDB专家的在线技术支持。虽然阿里云对于InfluxDB做了改动和产品化,但是不影响两者生态的兼容。TSDB for InfluxDB可以无缝地对接到用户的Telegraf、Chronograf以及Kapacitor工具链中。TSDB for InfluxDB在上月月底正式上线进行公测,公测期间免费使用,因此大家如果感兴趣可以尝试,并且也提供了数据的零感知迁移工具,能够帮助用户一键式实现数据迁移。 TSDB for Spatial Temporal 阿里云数据库团队还在做的另外一项尝试就是TSDB for Spatial Temporal,其属于存储时空数据的数据库。TSDB for Spatial Tempora为基于时空数据构建大规模应用提供了两大利器——自研的S3时空索引和高性能电子围栏。S3 时空索引实现千万级时空数据的秒级查询能力,助力用户实现时空数据的低延迟查询分析,满足实时交互查询及实时数据分析场景。而且TSDB for Spatial Temporal还能够支持海量围栏,同时检测大规模移动目标。 最后,欢迎大家关注阿里云时序时空数据库公众号: https://yq.aliyun.com/teams/395 。 作者:七幕 原文链接 本文为云栖社区原创内容,未经允许不得转载。 数据库 2019-06-04 11:40:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 前言 为什么会突然写这个blog呢?因为之前有只青蛙小姐姐问我,能不能教她join,当时上大学老师怎么教她也不会。然后本来想面对面交流给她说明,后面阴错阳差,就延误到了现在。 所以我想,我可以提前准备好我想说的东西,记录下来,顺便自己也回忆下join(ps:为什么我需要回忆?因为之前的公司都是面向互联网的、高并发的业务,用join的话,很容易导致数据库出现异常问题,我已经很久没用过了)。 当然有机会的话,我觉得还是要当面给她讲。 join是什么,为什么要有join 这个就很重要了,学一个东西你都不知道它是什么,有什么应用场景的话,基本是学完就忘。 SQL join 用于根据两个或多个表中的列之间的关系,从这些表中查询数据。 举个🌰: 栗子当然是要用学校的栗子啊,在学校的时候吃栗子还是蛮有感觉的,现在吃个🌰,都习以为常了。 学生表(student) uid,name 成绩表(achievement) uid,score 这个时候我们要怎么去查询学生的成绩呢? 如果不用连表,我们可能就需要2条sql. select * from student where uid = x select * from achievement where uid = x 这样是不是很繁琐。 假如我们有join了,那就很好操作了。 select * from student join achievement on student.uid = achievement.uid where uid = x join的分类 join分为3种情况,分别是inner、left、right。从字面上,我们就能理解为全、左、右 inner join inner join(等值连接) 只返回两个表中联结字段相等的行。 怎么理解呢?还是拿学生来举例。 select * from student inner join achievement on student.uid = achievement.uid where uid = x 比如一个班有30个学生,但是只有28个人参加的考试。 如果使用inner会展示出参加考试的学生及其成绩 left join left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 怎么理解呢?还是拿学生来举例。 select * from student left join achievement on student.uid = achievement.uid where uid = x 比如一个班有30个学生,但是只有28个人参加的考试。 如果使用left会展示出所有的学生,没有成绩的会补充为Null。 right join right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录 right join一般用于右表数据比较多的情况。 还是拿学生来举例吧。 select * from student right join achievement on student.uid = achievement.uid where uid = x 比如一个班去年有33个学生,但是有3个人休学了。如果查询去年的成绩的话。 就会得到33条记录,而休学的3个同学的姓名是无法获知了,会和left join一样补充为null。 总结 因此很明显的,可以看出来 inner返回是完全匹配 left和right返回的数据是部分匹配但是会将其中一张表数据匹配到的完全返回,而将不完全的表的值填充为空。 至于left和right表,就这么理解,如果是left,那left左边的表名就是完全返回的,如果是right,则反之。 数据库 2019-06-04 11:28:00 「深度学习福利」大神带你进阶工程师,立即查看>>> InnoDB在设计lock-free的log system时,除了已有的参数外,还通过宏控制隐藏了一些参数,如果你使用源码编译时,打开cmake选项-DENABLE_EXPERIMENT_SYSVARS=1, 就可以看到这些参数了。本文主要简单的过一下这些隐藏的参数所代表的含义 A. innodb_log_write_events innodb_log_flush_events 两者的含义类似,表示用来唤醒等待log write/flush的event的个数,默认值都是2048 比如你要等待的位置在lsnA,那么计算的slot为: slot = (lsnA - 1) /OS_FILE_LOG_BLOCK_SIZE & (innodb_log_write/flush_events - 1) 这意味着:如果事务的commit log的end lsn落在相同block里,他们可能产生event的竞争 当然如果不在同一个block的时候,如果调大参数,就可以减少竞争,但也会有无效的唤醒 唤醒操作通常由后台线程log_write_notifier 或者log_flush_notifier异步来做,但如果推进的log write/flush还不足一个block的话,那就log_writter/flusher 自己去唤醒了。 B. innodb_log_recent_written_size, 默认1MB 表示recent_written这个link_buf的大小,其实控制了并发往log buffer中同时拷贝的事务日志量,向前由新的日志加入,后面由log writer通过写日志向前推进,如果写的慢的话,那这个link_buf很可能用满,用户线程就得spin等待。再慢io的系统上,我们可以稍微调大这个参数 innodb_Log_recent_closed_size, 默认2MB 表示recent closed这个link_buf的大小,也是维护可以并发往flush list上插入脏页的并罚度,如果插入脏页速度慢,或者lin_buf没有及时合并推进,就会spin wait 简单说下link_buf, 这本质上是一个数组,但使用无锁的使用方式来维护lsn的推进,比如获得一个lsn开始和结束,那就 通过设置buf[start_lsn] = end_lsn的类似方式来维护lsn链,基于lsn是连续值的事实,最终必然不会出现空洞,所以在演化的过程中,可以从尾部 推进连续的lsn,头部插入新的值. 如果新插入的值超过了尾部,表示buf满了,就需要spin wait了 C. innodb_log_wait_for_write_spin_delay, innodb_log_wait_for_write_timeout 从8.0版本开始用户线程不再自己去写redo,而是等待后台线程去写,这两个变量控制了spin以及condition wait的timeout时间,当spin一段时间还没推进到某个想要的lsn点时,就会进入condition wait 另外两个变量 innodb_log_wait_for_flush_spin_delay innodb_log_wait_for_flush_timeout 含义类似,但是是等待log flush到某个指定lsn 注意在实际计算过程中,最大spin次数,会考虑到cpu利用率,以及另外两个参数: innodb_log_spin_cpu_abs_lwm innodb_log_spin_cpu_pct_hwm 如果是等待flush操作的话,还收到参数innodb_log_wait_for_flush_spin_hwm限制,该参数控制了等待flush的时间上限,如果平均等待flush的时间超过了这个上限的话, 就没必要去spin,而是直接进入condition wait 关于spin次数的计算方式在函数 log_max_spins_when_waiting_in_user_thread 中": 函数的参数即为配置项innodb_log_wait_for_write_spin_delay或innodb_log_wait_for_flush_spin_delay值 static inline uint64_t log_max_spins_when_waiting_in_user_thread( uint64_t min_non_zero_value) { uint64_t max_spins; /* Get current cpu usage. */ const double cpu = srv_cpu_usage.utime_pct; /* Get high-watermark - when cpu usage is higher, don't spin! */ const uint32_t hwm = srv_log_spin_cpu_pct_hwm; if (srv_cpu_usage.utime_abs < srv_log_spin_cpu_abs_lwm || cpu >= hwm) { /* Don't spin because either cpu usage is too high or it's almost idle so no reason to bother. */ max_spins = 0; } else if (cpu >= hwm / 2) { /* When cpu usage is more than 50% of the hwm, use the minimum allowed number of spin rounds, not to increase cpu usage too much (risky). */ max_spins = min_non_zero_value; } else { /* When cpu usage is less than 50% of the hwm, choose maximum spin rounds in range [minimum, 10*minimum]. Smaller usage of cpu is, more spin rounds might be used. */ const double r = 1.0 * (hwm / 2 - cpu) / (hwm / 2); max_spins = static_cast D. 以下几个参数是后台线程等待任务时spin及condition wait timeout的值 log_writer线程: innodb_log_writer_spin_delay, innodb_log_writer_timeout log_flusher线程: innodb_ log_flusher_spin_delay innodb_log_flusher_timeout log_write_notifier线程: innodb_ log_write_notifier_spin_delay innodb_log_write_notifier_timeout log_flush_notifier线程 innodb_log_flush_notifier_spin_delay innodb_log_flush_notifier_timeout log_closer线程(用于推进recent_closed这个link_buf的专用线程) innodb_log_closer_spin_delay innodb_log_closer_timeout E innodb_ log_write_max_size 表示允许一个write操作最大的字节数,默认为4kb, 这个是在推进recent_written这个link buf时计算的,个人认为这个限制太小了,可以适当调大这个参数。(然而8.0的最大写入限制还受到innodb_log_write_ahead_size限制,两者得综合起来看) F innodb_log_checkpoint_every 默认1000毫秒(1秒),表示至少每隔这么长时间log_checkpointer线程会去尝试做一次checkpoint. 当然是否做checkpoint还受到其他因素的影响,具体见函数 log_should_checkpoint : a) more than 1s elapsed since last checkpoint b) checkpoint age is greater than max_checkpoint_age_async c) it was requested to have greater checkpoint_lsn, and oldest_lsn allows to satisfy the request G. 参考: MySQL8.0.16源代码 作者:zhaiwx_yinfeng 原文链接 本文为云栖社区原创内容,未经允许不得转载。 数据库 2019-06-04 11:10:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 在Oracle中,自定义函数格式如下: CREATE OR REPLACE FUNCTION 函数名 ( argu1 [mode1] datatype1) -- 定义参数变量 RETURN 返回值类型 IS 声明部分; BEGIN 函数体; RETURN 变量; END; 函数解析: 函数的参数有3种类型: (1)in参数类型:表示输入给函数的参数,该参数只能用于传值,不能被赋值。 (2)out参数类型:表示参数在函数中被赋值,可以传给函数调用程序,该参数只能用于赋值,不能用于传值。 (3)in out参数类型:表示参数既可以传值,也可以被赋值。 示例: create or replace function sf_score_pm( p_in_stuid in varchar2,--学号 p_in_courseid in varchar2 --课程ID ) return number is ls_pm number:=0; ls_score number:=0; begin --获取该学生的成绩 select t.score into ls_score from score t where t.stuid = p_in_stuid and t.courseid = p_in_courseid; --获取成绩比该学生高的人数 select count(1) into ls_pm from score t where t.courseid = p_in_courseid and t.score>ls_score; --得到该学生的成绩排名 ls_pm:=ls_pm+1; return ls_pm; exception when no_data_found then dbms_output.put_line('该学生的课程:'||p_in_courseid|| '的成绩在成绩表中找不到'); end; 函数查看语法: select * from user_source t where t.name='SF_SCORE_PM'; 删除自定义函数语法: drop function 函数名; 数据库 2019-06-04 11:06:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 生物学 门 Divisio (Phylum) 纲 Classis (class) 目 Ordo (order) 科 Familia (Family) 族 Tribus (Tribe) 属 Genus (Genus) 组 Sectio (Section) 系 Series (Series) 种 Species (Species) 变种 Varietas(Variety) 变型 Forma (Form) 分类学 数据库 2019-06-04 00:31:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 今天从社区邮件看到一个这样的问题,感觉很有意思,在这分享给大家~具体如下: 问题现象: 作者有一个很老的Java应用,当时后端采用的PostgreSQL数据库版本为8.x,该系统除了正常的数据增删改查违,有一项功能是把图片存储到pgsql中,存储类行为bytea,然后再读取使用。 作者近期做了数据库的迁移,将pgsql8.x中的数据迁移到pgsql11版本上,迁移完成后,Java应用端的其他功能都正常,只有图片无法正常读取显示了,但是写入是没问题的。这个问题困扰了作者很久。 解决办法: 经过社区大神指点后,得知bytea的默认输出格式从pgsql9.x版本开始改变了,在9.x版本之前,bytea_output的默认值为escape,9.x之后,bytea_out的默认值就变成了hex。所以如果要与老版本的前端应用适配,可以修改数据库参数为escape即可解决这个问题。 数据库 2019-05-24 21:55:00 「深度学习福利」大神带你进阶工程师,立即查看>>> substring_index(str,delim,count) str:要处理的字符串 delim:分隔符 count:计数 例子:str=www.google.com 1.count是正数,那么就是从左往右数,第N个分隔符的左边的全部内容 SELECT SUBSTRING_INDEX('www.google.com','.',1); 结果是:www SELECT SUBSTRING_INDEX('www.google.com','.',2); 结果是:www.google 2.count是负数,那么就是从右边开始数,第N个分隔符右边的所有内容,如: SELECT SUBSTRING_INDEX('www.google.com','.',-2); 结果为:google.com 如果我呀中间的的google怎么办? 很简单的,两个方向: 从右数第二个分隔符的右边全部,再从左数的第一个分隔符的左边: SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('www.google.com','.',-2),'.',1); 结果为:google 总结:SUBSTRING_INDEX可以理解为java里面按照分隔符使用split进行分组,从所得数组的下标+1处一直到开始算起的位置的所有内容即为最终结果,如果count 不等于0,那么即使 str中没有找到delim分隔,那么返回的结果就是str本身,即只要count!=0,那么返回的最差结果也是str本身(没有找到一个delim分隔符...的情况) 例如: (1) SELECT SUBSTRING_INDEX('www.google.com','AAA',-1); 和 SELECT SUBSTRING_INDEX('www.google.com','AAA',-99); 两者的结果都为:www.google.com (2)在项目中利用CONCAT来做字符串合并,发现一个一个有意思的事情, SELECT CONCAT(SUBSTRING_INDEX('www.google.com','.',1),'-','AAAABB') 结果为:www-AAAABB (3) SELECT CONCAT(SUBSTRING_INDEX('www.google.com','AAA',1),'-','AAAABB') 结果为:www.google.com-AAAABB (4) SELECT CONCAT(SUBSTRING_INDEX('www.google.com','AAA',1)-1,'-','AAAABB') 结果为:-1-AAAABB 结果咋这样呢?发现Mysql的字符串在参与运算的时候很有意思: 以下内容摘自: https://www.jianshu.com/p/2ab2c0dc3cb5 MYSQL截取函数: 1、从左开始截取字符串 left(str, length) 说明:left(被截取字段,截取长度) 例:select left(content,200) as abstract from my_content_t 2、从右开始截取字符串 right(str, length) 说明:right(被截取字段,截取长度) 例:select right(content,200) as abstract from my_content_t 3、截取字符串 substring(str, pos) substring(str, pos, length) 说明:substring(被截取字段,从第几位开始截取) substring(被截取字段,从第几位开始截取,截取长度) 例:select substring(content,5) as abstract from my_content_t select substring(content,5,200) as abstract from my_content_t (注:如果位数是负数 如-5 则是从后倒数位数,到字符串结束或截取的长度) 4、按关键字截取字符串 substring_index(str,delim,count) 说明:substring_index(被截取字段,关键字,关键字出现的次数) 例:select substring_index("blog.jb51.net","。",2) as abstract from my_content_t 结果:blog.jb51 (注:如果关键字出现的次数是负数 如-2 则是从后倒数,到字符串结束) 函数简介: SUBSTRING( str , pos ) , SUBSTRING( str FROM pos ) SUBSTRING( str , pos , len ) , SUBSTRING( str FROM pos FOR len ) 不带有 len 参数的格式从字符串 str 返回一个子字符串,起始于位置 pos 。带有 len 参数的格式从字符串 str 返回一个长度同 len 字符相同的子字符串,起始于位置 pos 。 使用 FROM的格式为标准 SQL 语法。也可能对 pos 使用一个负值。假若这样,则子字符串的位置起始于字符串结尾的 pos 字符,而不是字符串的开头位置。在以下格式的函数中可以对 pos 使用一个负值。 mysql之字符串进行运算或大小比较 在mysql当中,字符串类型间进行加减乘除运算的时候,如果字符串以数字开头,则会截取字符串以数字开头的那一部分数字进行运算,如果字符串没将诶有以数字开头数字,那么就只能截取的数值为0,那么进行加减的时候结果都是0,进行乘除的时候结果都是NULL(即如果以字符串开头,则会从数字一直截取到非数字为止;如果不以数字开头,那么取0) 如下几个结果说明问题: mysql> select '1a'+'1b';//字符串分别以1开头,所以运算的时候,分别为1,结果为1+1=2 +-----------+ | '1a'+'1b' | +-----------+ | 2 | +-----------+ 1 row in set, 2 warnings (0.00 sec) mysql> select '2a'-'1b';//字符串分别以2和1开头,所以结果为2-1=1 +-----------+ | '2a'-'1b' | +-----------+ | 1 | +-----------+ 1 row in set, 2 warnings (0.00 sec) mysql> select '1a'/'2b';//字符串分别以1和2开头,所以结果为:1/2=0.5 +-----------+ | '1a'/'2b' | +-----------+ | 0.5 | +-----------+ 1 row in set, 2 warnings (0.00 sec) mysql> select 'a'/'b';//字符串分别以'a'和'b'开头,所以结果为0/0=NULL +---------+ | 'a'/'b' | +---------+ | NULL | +---------+ 1 row in set, 3 warnings (0.00 sec) mysql> select 'a' - 'b';//字符串分别以'a'和'b'开头,结果为0-0=0 +-----------+ | 'a' - 'b' | +-----------+ | 0 | +-----------+ 1 row in set, 2 warnings (0.00 sec) mysql> select 'a' + 'b';//字符串分别以'a'和'b'开头,结果为0+0=0 +-----------+ | 'a' + 'b' | +-----------+ | 0 | +-----------+ 1 row in set, 2 warnings (0.00 sec) 如果一个字符串以数字开头,后面有非数字和数字组合的话,在进行运算的时候,会省略掉非数字和数字组合那一段,也就是只会截取开头的数字 mysql> select '2015-2-1' - '2015-1-1';////字符串分别以'2015'和'2015'开头,结果为2015-2015=2015 +-------------------------+ | '2015-2-1' - '2015-1-1' | +-------------------------+ | 0 | +-------------------------+ 1 row in set, 2 warnings (0.00 sec) 以上相当于2015-2015=0 mysql当字符串进行大小比较的时候 mysql> select '2015-2-1' > '2015-1-1'; +-------------------------+ | '2015-2-1' > '2015-1-1' | +-------------------------+ | 1 | +-------------------------+ 1 row in set (0.00 sec) mysql> select '2015-2-1' < '2015-1-1'; +-------------------------+ | '2015-2-1' < '2015-1-1' | +-------------------------+ | 0 | +-------------------------+ 1 row in set (0.00 sec) 在这里非常奇怪, '2015-2-1' - '2015-1-1' = 0 ,为什么大小比较的时候会有大小之分呢? 原来对于数字与非数字混合的字符串,在进行大小比较的时候,如果两字符串长度相等,那么两字符串就会比较相同位置的字符,比较时若字符是数字,则直接比较,若字符是非数字那么会转换为ascii码进行比较,若在某位置上已经有大小之分,那么就不会再进行比较。 mysql> select '2017-03-20 15:27:49' > '2017-03-20 15:27:48'; +-----------------------------------------------+ | '2017-03-20 15:27:49' > '2017-03-20 15:27:48' | +-----------------------------------------------+ | 1 | +-----------------------------------------------+ 1 row in set (0.00 sec) 看起来像日期的字符串可以用date_format函数提取当中的年月日,看如下: mysql> select date_format('2017/03/20 15:27:49','%Y') 年,date_format('2017/03/20 15:27:49','%c') 月,date_format('2017/03/20 15:27:49','%d') 日; +------+------+------+ | 年 | 月 | 日 | +------+------+------+ | 2017 | 3 | 20 | +------+------+------+ 1 row in set (0.00 sec) 非数字字符在比较大小的时候,就例如: mysql> select 'a' < 'b'; +-----------+ | 'a' < 'b' | +-----------+ | 1 | +-----------+ 1 row in set (0.00 sec) 当中的字母会转成ascii码,再进行比较,以上是单字母字符串比较,如果是多字母数字混合字符串比较呢? mysql> select '1c' > 'bc'; +-------------+ | '1c' > 'bc' | +-------------+ | 0 | +-------------+ 1 row in set (0.00 sec) mysql> select '1yz' > 'abc999'; +------------------+ | '1yz' > 'abc999' | +------------------+ | 0 | +------------------+ 1 row in set (0.00 sec) 其实从上面的结果大概可以猜测得到,为让解释更清晰更有说服力,再看下图: mysql> select ascii('1c'),ascii('bc'); +-------------+-------------+ | ascii('1c') | ascii('bc') | +-------------+-------------+ | 49 | 98 | +-------------+-------------+ 1 row in set (0.00 sec) mysql> select ascii('1yz'),ascii('abc999'); +--------------+-----------------+ | ascii('1yz') | ascii('abc999') | +--------------+-----------------+ | 49 | 97 | +--------------+-----------------+ 1 row in set (0.00 sec) 字符串大小比较的时候,会从左向右将两个字符串第一个不相等的两个字符的ascii码的比较结果作为最终结果 有时候我们需要直接用MySQL的字符串函数截取字符,毕竟用程序截取(如PHP)还得先写个脚本连接数据库之类的,所以在这里做一个记录,希望对大家有用。 MySQL截取字符串函数 1、从左开始截取字符串 left(str, length) 说明:left(被截取字段,截取长度) 例: select left(content,200) as abstract from my_content_t 2、从右开始截取字符串 right(str, length) 说明:right(被截取字段,截取长度) 例: select right(content,200) as abstract from my_content_t 3、截取字符串 substring(str, pos) substring(str, pos, length) 说明:substring(被截取字段,从第几位开始截取) substring(被截取字段,从第几位开始截取,截取长度) 例: select substring(content,5) as abstract from my_content_t select substring(content,5,200) as abstract from my_content_t (注:如果位数是负数 如-5 则是从后倒数位数,到字符串结束或截取的长度) 4、按关键字截取字符串 substring_index(str,delim,count) 说明:substring_index(被截取字段,关键字,关键字出现的次数) 例: select substring_index("blog.jb51.net",".",2) as abstract from my_content_t 结果:blog.jb51 数据库 2019-05-29 11:21:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 阿里妹导读:云计算大潮来袭,传统数据库市场正面临重新洗牌的情境,包括云数据库在内的一批新生力量崛起,动摇了传统数据库的垄断地位,而由云厂商主导的云原生数据库则将这种“改变”推向了高潮。 云时代的数据库将面临怎样的变革?云原生数据库有哪些独特优势?在 DTCC 2019大会上,阿里巴巴副总裁 李飞飞博士就《下一代云原生数据库技术与趋势》进行了精彩分享。 李飞飞(花名:飞刀),阿里巴巴集团副总裁,高级研究员,达摩院首席数据库科学家,阿里云智能事业群数据库产品事业部负责人,ACM 杰出科学家。 大势所趋:云数据库市场份额增速迅猛 如下图所示的是 Gartner 关于全球数据库市场份额的报告,该报告指出目前全球数据库市场份额大约为400亿美金,其中,中国数据库市场份额占比为3.7%,大约为14亿美金。 具体到数据库市场分布,传统五大数据库厂商 Oracle、Microsoft、IBM、SAP、Teradata 占比达到了80%,云数据库的份额占比接近10%,并且云数据库市场份额占比每年也在快速增长,因此, Oracle、MongoDB 等也在大力布局其在云数据库市场的竞争态势。 根据 DB-Engines 数据库市场分析显示,数据库系统正朝着多样化、多元化的方向发展,从传统的 TP 关系型数据库发展到今天的多源异构的数据库形态。目前,处于主流位置的还是大家耳熟能详的数据库系统,比如商业数据库 Oracle、SQL Server以及开源的 MySQL、PostgreSQL 等。而一些比较新的数据库系统,比如MongoDB、Redis 则开辟了一个新的赛道。数据库 License 的传统销售方式在逐渐走下坡路,而开源以及云上数据库 License 的流行程度却在不断提升。 数据库:云上应用关键的一环 正如 AWS 创始人 Jeff Bezos 所说:“The real battle will be in databases”。因为云最早是从 IaaS 做起来的,从虚拟机、存储、网络,到现在如火如荼的语音识别、计算机视觉以及机器人等智能化应用,都是基于 IaaS 的,而数据库就是连接 IaaS 与智能化应用 SaaS 最为关键的一环。从数据产生、存储到消费的各个环节,数据库都至关重要。 数据库主要包括四大板块,即 OLTP、OLAP、NoSQL 以及数据库服务和管理类工具,也是云数据库厂商发力的四个方向。对于 OLTP 而言,技术发展已经历经了40年,而如今大家还在做的一件事情就是“加10元和减10元”,也就是所谓的事务处理。当数据量变得越来越大和读写冲突的原因,对数据进行在线实时分析的需求衍生出了 OLAP。由于需要 Scale out,而数据强一致性不能够得到保证,就有了NoSQL 。而最近又出现了一个新名词—— NewSQL,这是因为 NoSQL 也有所不足,故将传统 OLTP 的 ACID 保证与 NoSQL 的 Scale out 能力进行了整合,变成了NewSQL。 数据库系统架构演进:All depends on what is shared 纵观数据库40年来的发展历史,从最早的关系型数据库时期,衍生出了 SQL、OLTP 等技术;到数据量急剧增长,需要避免读写冲突,通过 ETL、数据仓库以及 Data Cube 等技术实现了 OLAP;再到今天,面对异构多源的数据结构,从图到时序、时空到向量等,也就诞生了 NoSQL、NewSQL 等数据库,同时也出现了一些新的技术,比如 Multi-Model 和 HTAP 等。 数据库系统最为主流的架构是 Shared Memory:共享处理器内核,共享内存并且具有共享的本地磁盘,这样的单机架构属于非常主流的架构,传统的数据库厂商基本采用的也是这样的架构。 而随着互联网企业的大规模发展,如 Google、Amazon 以及阿里巴巴,大家发现原来的单机架构有很多限制,其可扩展性以及吞吐量无法满足业务发展需求,于是就衍生出了 Shared Disk/Storage 架构,即共享存储架构。也就是说数据库底层可能是分布式存储,通过利用 RDMA 这样的快速网络让上层的数据库内核看起来像是在使用本地的磁盘,但实际上是分布式存储。上面可以有多个独立计算节点,一般是一写多读,但是也可以做多写多读,这就是共享存储架构,其中比较典型的代表就是阿里云的 POLARDB 数据库。 另外一种架构是 Shared Nothing 。共享存储虽然有诸多优点,解决了很多问题,但是 RDMA 网络也存在很多的限制,比如其跨越 Switch 甚至是跨 AZ 和 Region 的时候性能都会有所损失。分布式的共享存储达到一定的节点数量之后,性能会出现一定的损耗,所以不能保证访问远程数据和访问本地数据的性能完全相同,所以共享存储的架构当扩展到十几个节点之后就达到了 scale out 扩展的上限了。此时,如果应用需要继续扩展怎么办呢?那就需要实现分布式架构了,比较典型的就是 Google Spanner,其利用原子钟技术能够实现跨数据中心的数据一致性和事务一致性。而在阿里云,基于 POLARDB 实现的分布式版本 POLARDB-X 采用的也是 Shared Nothing 架构。 这里需要注意的一点就是:Shared Nothing 和 Shared Storage 可以结合。可以在上层做 Shared Nothing,而对于下层的 Shard 分片采用 Shared Storage 架构。这样混合架构的好处在于能够减轻分出太多 Shard 的痛点问题,减少分布式事务distributed commit的 概率,因为 distributed commit 的代价非常昂贵。 总结三种架构设计,如果在 Shared Storage 架构上做到多写多读而不是一写多读,实际上也就实现了 SharedEverything 。将 Shared nothing 和 Sharedstorage 架构进行结合的 hybrid 架构应该是后续数据库系统发展方向的一个重要突破点。 云原生数据库核心四要素 上面从架构方面分析了云时代的主流数据库架构。从技术上来讲,除了架构上的不同,云原生时代还有一些不同点。 多模(Multi-model) 其一是多模(Multi-model),多模主要有两种,即北向和南向。南向表示存储结构是多种多样的,数据结构可以是结构化的也可以是非结构化的,可以是图、向量、文档等,但对于用户只提供一个 SQL 的查询接口或者 SQL-Like 的接口,这部分业界比较典型的就是各种各样的数据湖服务。而北向的多模就是存储只有一种,一般是通过 KV 存储数据形态来支持结构化、半结构化以及非结构化数据,但希望能够提供不同的查询接口,比如 SPARQL、SQL、GQL 等。业界典型的代表是微软 Azure 的CosmosDB。 数据库智能化+自动化管控平台 数据库的自治化也是非常重要的发展方向,从数据库的内核以及管控平台两个角度都有很多技术点可以做。在数据库自治化部分,阿里巴巴认为,需要做到自感知、自决策、自恢复以及自优化。自优化比较简单,就是在内核中利用机器学习的方法来进行优化。而自感知、自决策、自恢复更多的是针对管控平台的,比如如何保证实例的巡检,当出现问题后如何能够自动快速修复或者自动切换等。 新硬件: 软硬件一体化设计 云原生数据库的第三大核心点是软硬件一体化设计。数据库首先是一个系统,而系统就需要能够安全高效地使用有限的硬件资源。所以数据库系统的设计和发展一定是和硬件性能和发展紧密相关的,我们不能够面对硬件的变化而坚持旧有数据库设计不改变,比如 NVM 出来之后就可能对传统的数据库设计有一些冲击。而新硬件所带来的变化也是数据库系统设计需要考虑的。 RDMA、NVM 以及 GPU/FPGA 等新硬件或者架构的出现,对于数据库的设计都会提供新的思路。 高可用 高可用是云原生最基本的要求之一,上云的用户势必不希望业务出现中断。高可用最简单的解决方案就是冗余,可以做 Table 级别的冗余,也可以做 Partition 级别的冗余。无论是使用哪一种,基本上都是三副本,甚至更多的时候需要做四副本或者五副本,比如金融级别的高可用可能需要做两地三中心或者两地四中心。 对于高可用的多副本而言,如何保证副本之间的数据一致性?在数据库里面有一个经典的CAP理论,其理论结果是在 Consistency、Availability 和 Partition Tolerant 三者之间只能选择两个。现在大家的一般选择都是 C+P,同时对于 A 而言,通过三副本技术和分布式一致性协议,使得 A 达到6个9或者7个9,这样基本上就做到了100%的 CAP。 云原生数据库 POLARDB:极致弹性+兼容性 为海量数据和海量并发而生 前面介绍了数据库市场背景和云原生数据库的基本要素,接下来我将结合阿里云 POLARDB 以及 AnalyticDB 两款数据库系统,分享以上技术的具体落地情况。POLARDB 是阿里云的云原生数据库,目前已有非常深厚的技术积累。我们在VLDB 2018,SIGMOD 2019等国际学术会议上发表了相关论文,主要介绍存储引擎等方面的技术创新。 POLARDB 采用共享存储架构,一写多读。共享存储架构有多个优势,首先是计算和存储分离,计算节点和存储节点可以分开实现弹性缩扩容;其次,POLARDB 突破了 MySQL、PG 等数据库对于单节点规格和可扩展性的限定,能够实现 100TB 存储容量以及每个节点100万 QPS 的性能;此外,POLARDB 能够提供极致的弹性能力,备份恢复能力也有很大提升。在存储层,每个数据块都采用三副本高可用技术,同时对于 Raft 协议进行了修改,通过实现并行式的 Raft 协议保证了三副本数据块之间的数据一致性,提供了金融级高可用。POLARDB 还能做到100%兼容 MySQL 以及 PG 等数据库生态,可以帮助用户实现无感知的应用迁移。 由于底层是共享的分布式存储,PolarDB 属于 Active-Active 的架构,主节点负责写入数据,从节点负责读取数据,因此,对于进入数据库的事务而言,主备节点都处于Active 状态,其好处在于通过一份物理存储避免了在主从之间不停地做数据同步。 具体而言,POLARDB 有一个 PolarProxy,也就是前面的网关代理,下面有 POLARDB 的内核以及 PolarFS,最下面对接的是 PolarStore,利用 RDMA 网络管理底层的分布式共享存储。PolarProxy 会对客户需求做分发,将写请求分配到主节点,而对于读请求而言,则会根据负载均衡以及读节点的状态实现对于读请求的分配,这样就能够尽可能地实现资源的最大化利用以及性能的提升。 POLARDB 共享存储采用分布式+三副本。其中 Primary 节点负责写,其他节点负责读,其下层是 PolarStore,每部分都会有三副本的备份,通过分布式一致性协议保证数据一致性。这样设计的优势在于能够实现存储与计算分离,同时能够做到无锁备份,所以备份可做到秒级。 在一写多读的情况下,POLARDB 能够实现快速伸缩。举例而言,从2核 vCPU 升级到32核或者从两个节点扩展到4个节点,都能够在5分钟之内生效。存储和计算分离能够带来的另一大好处是降低成本,因为存储和计算节点可以独立地进行弹性伸缩,充分体现成本优势。 下图展示了 POLARDB 如何利用物理日志实现持续恢复。左侧是传统数据库的架构,而在 POLARDB 里面,由于采用了共享存储,因此可基本保留类似传统数据库利用物理日志进行恢复的过程,通过共享存储实现持续恢复,做事务的 Snapshot 恢复。 对比一下,如果 MySQL 做主备架构,首先需要在主库里面有一个逻辑日志和物理日志,在备库里面要重放主库的逻辑日志,然后再按照主库的方式做逻辑日志和物理日志。而在 POLARDB 里面,因为是共享存储,可直接通过一份日志实现数据恢复,备库能够直接将所需要的数据恢复出来,而不需要去重放主库的逻辑日志。 POLARDB一写多读集群的另一大优势是动态 DDL 的支持。在 MySQL 架构下,如要对数据的 Schema 进行修改,需要通过 Binlog 去 Replay 到备库,因此备库会存在Blocking 的阶段,需要一定时间 Replay 动态的 DDL。而在 POLARDB共享存储架构下,所有 Schema 信息以及 metadata 均以表的形式直接存储在存储引擎里面,只要主库改完了,那么备库的元信息也实时同步更新,因此不会存在 Blocking 的过程。 POLARDB 的 Proxy 最主要的作用就是做读写分离、负载均衡、高可用切换以及安全防护等。POLARDB 是一写多读架构,当请求进来之后,需要进行读写的判断,将写请求分发到写节点,将读请求分发到读节点上去,并且对于读请求做一定的负载均衡。这样就能保证会话的一致性,并且彻底解决了读不到最新数据的问题。 无损弹性是 POLARDB 监控的模块之一。分布式存储需要知道分配多少磁盘量 /Chunk,POLARDB 会监控未使用的 Chunk 量。比如当可用量低于30%的时候,就会在后台自动地对其进行扩容,这使得应用基本不受影响,可连续写数据。 对于云数据库 POLARDB 而言,以上技术带来的最大优势是极致的弹性。这里我们以一个具体的客户案例进行说明。如下图所示,红线部分指离线资源的消耗情况,这些成本是客户无论如何都需要付出的,而其上面的部分则是计算资源的需求。 比如客户在3、4月有新品上市,5月还有促销活动,这两个时期计算需求会非常大。如按照传统架构方式,可能需要在新品上市之前就将容量弹到更大的规模,并且保持这样的水位,到了后面的促销阶段又需要弹到更高的规格,成本非常高昂。但如果能够做到极致弹性,比如 POLARDB 的存储与计算分离,实现快速弹性扩容,那么用户就只需在蓝色方块出现之前将容量弹上去,之后再弹下来即可,这样就能大幅降低成本。 除了云原生数据库 POLARDB ,阿里云数据库团队在其他方向还有众多探索。 分布式版本 POLARDB-X : 高并发+跨域高可用 支持水平拓展 如果企业需要极致的 Scale out 能力,像阿里巴巴以及传统行业中的银行、电力等对高并发、海量数据支撑要求极高的用户,共享存储架构只能支持弹至十几个节点,肯定是不够的。因此,阿里云数据库团队也采用 Shared Nothing 做水平拓展,将Shared Nothing 与 Shared Storage 相结合,形成 POLARDB-X 。POLARDB-X 支持金融级跨可用区数据强一致, 对支持海量数据下的高并发事务处理有着极好的性能表现。目前,POLARDB-X 在阿里内部已上线应用,利用存储计算分离、硬件加速、分布式事务处理和分布式查询优化等技术,成功支持了在双11这样的场景下阿里巴巴所有业务核心链路数据库洪峰的挑战,我们后续将推出商业化版本,敬请期待。 OLAP 数据库标杆—— AnalyticDB:海量数据 实时高并发在线分析 此外在 OLAP 分析型数据库方向,阿里云数据库团队自主研发了数据库产品——AnalyticDB,在阿里云的公有云和专有云上均有售卖。AnalyticDB 拥有几大核心架构特点: 行列混存引擎,能够支持高吞吐写入和高并发查询; 支持海量数据处理,对于海量数据能实现秒级分析,完美支持多表、中文以及复杂分析; 利用向量化技术,支持结构化数据和非结构化数据的融合处理。 近日,AnalyticDB 打榜 TPC-DS,在性价比方面达到了全球第一,通过了 TPC 官方的严苛认证。同时,介绍 AnalyticDB 系统的论文即将在 VLDB 2019 会议上展现。AnalyticDB 的常用应用场景是从 OLTP 应用我们的数据传输与同步工具 DTS 至AnalyticDB 进行实时的数据分析。 自治数据库平台:智能调参上线 iBTune (individualized Buffer Tuning) 云原生数据库的特点之一是自治化,阿里云内部有个平台叫 SDDP(Self-Driving Database Platform——自治化数据库平台),SDDP 会对各个数据库实例进行实时的性能数据采集,并使用机器学习方法建模进行实时调配。 iBTune 的基本思想是,每个数据库实例都包含一个 Buffer Size,传统数据库里面的Buffer Size 是提前分配好的,不能变化。而在大型企业里,Buffer 是一个资源池,需要消耗内存,因此希望做到弹性自动调配每个实例里的 BufferSize。比如淘宝商品库的数据库实例晚上不需要那么大的 Buffer,那么就可以自动将其 Buffer Size 弹下来,到早上再自动弹上去,同时要求不影响其 RT。为了满足上述需求并进行自动Buffer 优化,阿里云数据库团队构建了 iBTune 系统,目前监控近 7000个数据库实例,通过长期运营,可平均节省20TB 内存。介绍 iBTune 项目的核心技术论文也发表在了今年的 VLDB 2019大会上。 安全上云是关键 多重加密护航数据安全 云上的数据安全是非常重要的内容,阿里云数据库团队在数据安全方面也做了大量的工作。首先,数据落盘加密,在数据存储的时候就进行加密。此外,阿里云数据库也支持 BYOK,用户可以将自己的密钥拿到云上来实现落盘加密以及传输级别的加密。未来,阿里云数据库还将在内存处理时实现全程加密,对日志实现可信验证等。 阿里云企业级数据库云服务:全方位运维 全链路布局 阿里云数据库按照工具产品、引擎产品以及运营管控的全程数据库产品分类提供服务。下图展现的是阿里云——云数据库常用链路,通过 DTS 工具将线下数据库迁移到线上,基于数据需求/分类,分发至关系型数据库、图数据库以及 AnalyticDB 等。 阿里云数据库:客户第一,一切价值来自于服务用户 目前 POLARDB 数据库的增势迅猛,已经服务于通用行业、互联网金融、游戏、教育、新零售、多媒体等多个领域的龙头企业。 而 AnalyticDB 在分析型数据库市场也有非常出众的表现,支持实时分析以及可视化应用。 基于阿里云数据库技术,阿里巴巴支持了城市大脑等一系列关键项目及云上云下的大量客户。截止目前为止,阿里云数据库已经累计支持了近40万数据库实例成功上云。 云原生是数据库的新战场,它为发展了40多年的数据库行业带来了许多令人激动的新挑战和新机遇,阿里巴巴希望与国内外数据库行业的各位技术同仁一起,将数据库技术推向更高的境界。 作者:李飞飞 原文链接 本文为云栖社区原创内容,未经允许不得转载。 数据库 2019-05-29 11:09:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 作者:何志勇 本文转载自公众号「平安科技数据库产品团队」。 2019 年 5 月 9 日,平安科技数据库产品资深工程师何志勇在第十届数据库技术大会 DTCC 上分享了《TiDB 在平安核心系统的引入及应用》,通过对 TiDB 进行 POC 测试,详细解析如何选择适用于金融行业级别的开源分布式数据库,以及平安“财神节”活动中引入 TiDB 的全流程应用实践案例分享。本文根据演讲内容整理。 一、TiDB 引入的 POC 测试 作为一名运维人员,引入一个新的数据库产品前必须要明确几点: 从业务的角度,引入的产品能否满足业务基本需求和使用场景。 从运维管理角度看,这产品必须是可运维、可管理的,并且我们需要对其相应的功能与特性,要有一个很好的了解。 产品性能稳定。 所以在我们引入前从以下六个方面分别对 TiDB 进行测试验证,其中功能与架构、配置与管理、备份与恢复都是针对我们运维管理,SQL 特性、基准测试、应用场景测试则是应对业务需求和业务场景的。 1. 功能与架构 TiDB 事务隔级别为 SI,支持 Spark 生态,支持动态扩容,跨数据中心部署。 这是 TiDB 官网最新的架构图: 从左至右看,可以通过 MySQL 或 MySQL 客户端接入 TiDB,TiDB 有 TiDB、PD、TiKV 三个组件,组件之间功能相互独立,需独立部署,分别负责计算、调度、存储功能;同时又相互协作,共同完成用户请求处理。在 TiKV 层各节点是使用 Raft 协议保证节点间数据的一致性,同时它还提供 Spark 接口供大数据分析。 从上往下看,可通过 Data Miaration 工具从 MySQL 迁移到 TiDB,同时提供备份恢复功能、内部性能监控监测及诊断、支持容器化部署。 TiDB 从架构及生态上基本上具备了传统数据库应有的功能。 2. SQL 特性 兼容 mysql 语法,2.0 版本不支持窗口函数、分区表、视图、trigger 等。 3. 配置与管理 支持在线 DDL,2.0 只支持串行的 DDL、不支持并发,在优化器上支持 RBO 与 CBO,能对单会话进行管理,可以支持复杂的 SQL。 4. 备份与恢复 备份恢复工具均为开源,支持多线程备份恢复,当前版本不支持物理备份,loader 恢复时间偏长。 5. 基准测试 TiDB 在单条 SQL 的性能较好,高并发场景下性能较稳定,但 DML 事务大小有限制。 6. 应用场景测试 支持标量子查询,能支持非常复杂的查询,查询引擎可朔性强。 这个应用场景是我们的产险的实际分析场景,表数据量不大但是 SQL 较为复杂,是典型的星型查询。在 Oracle 用了 134 秒,但是 TiDB 用了 50 分钟,我们觉得很诧异,与 TiDB 的同事咨询后,他们通过现场支持我们优化底层代码后 34 秒可以跑出来。 二、“财神节”活动中 TiDB 的应用实战 “财神节”是中国平安综合性年度线上金融狂欢节。2019 年平安集团“财神节”活动于 1 月 8 日正式启动,涉及寿险、产险、银行、养老险、健康险、普惠、证券、基金、健康互联、陆金所、壹钱包、互娱、不动产等多个领域,活动参与的 BU 数量与推广的力度是历年之最。单日成交额超过 1000 亿,在单日交易额破千亿背后是几百个后台数据库实例的运维保障。 我们看下活动业务场景的特点: 参与门槛低 :暖宝保这个业务保费价格低至 19.9,所以人人都可以参与。 我们的推广力度很大 :以微服务的方式对接如平安健康、好福利、平安银行、陆金所等所有 APP 端,同时配合各种合作伙伴的宣传。 典型的互联网活动形式 : 如秒杀、红包雨,所以对数据库的要求是高并发、低延迟、高响应、高可用,2-5 年在线数据存储量预计达到 20~50TB,而这些只是预估,有可能远远大于以上评估值。 平安在用的开源数据库有很多,那在这么多数据库中,我们选择什么数据库呢? 综合对比考量最终我们选择 TiDB,在选择的同时也面临着挑战: 时间紧迫 2018 年 12 月 17 日~2019 年 1 月 7 日,20 天时间内完成开发测试到生产上线,时间短,风险大 开发零使用经验 现有开发大都是基于传统 Oracle 保险业务,对于 TiDB 没有使用经验 并发量与扩容 互联网业务并发需求前期不可完全需求,前期不能很好的以实际压力进行测试,与资源准备 DB 运维管理 TiDB 还处于生产落地阶段,一类系统尚未使用过 TiDB,没有大规模应用运维经验 基于以上挑战,我们在 9 台 PC 服务器上做了验证测试,测试工具是 jmeter,TiKV 节点数我们是逐步增加的,具体的测试过程如下: 总结一下,就是: TiDB 吞吐 :在 select 中即 point select,TiDB 的吞吐比较好。 弹性扩容 :在 insert 场景下随着节点数的增加,TPS 也会相应的增加,每增加 3 个节点 TPS 可提升 12%~20% 左右,同时在相同 TiKV 节点数下,TPS 与响应时间,此消彼长。 批量提交性能尤佳 :业务中一个保单需要同时写 7 个表,7 个表同时 commit 比单表 commit TPS 高,相同 TPS 场景下延迟更小。 初始化 region 分裂耗时长 :因在测试时没有预热数据(表为空表),对空表写入前几分钟,响应时间会比较大,约 5~8 分钟后响应时间趋于稳定。在前几分钟内响应时间大,是因为每个表初始化完都是一个 region,大量 insert 进来后需要进行分裂,消耗时间比较大。 Raftstore cpu 高问题 :由于 Raftstore 还是单线程,测试中从监控指标看到 CPU 达到瓶颈是raftrestore 线程。 TiKV 性能中的“木桶原理” :TiKV 中一个节点的写入性能变慢会影响到整个集群的 TPS 与响应时间。 上线时我们做了以下两方面改善: 1. 优化表的定义与索引 表定义:不使用自增长列(自增长的 rowid)作为主键,避免大量 INSERT 时把数据集中写入单个 Region,造成写入热点。 索引:使用有实际含义的列作为主键,同时减少表不必要的索引,以加快写入的速度。 2. 对表的 region 进行强制分裂 查找表对应的 region:curl http://$tidb_ip:$status_port /tables/$schema/$table_name/regions 使用 pd-ctl 工具 split 对应表的 region:operator add split-region $region_id 打散表的隐式 id,打散表的数据分布:alter table $table_name shard_row_id_bits=6; 我们使用了 25 台机器,后面还临时准备了 10 台机器去应对高并发的不时之需。 在使用过程中遇到如下问题: (1) 2.0.10 版本下 in 不能下推到表过渡问题 大家看到我们两个相同的表结构,同时写入一些数据,在两个表进行关联的时候,发现过滤条件 t1.id=1 时,上面那个执行计划可以下推到两个表进行过滤,两个表可以完全精准的把数据取出来,但是下面把等号后改成 in 的时候,对 t2 表进行全表扫描,如果 t2 表数据量很大时就会很慢,这是 TiDB 的一个 bug,解决方案就是不要用 in, 在 2.1 版本修复了这个 bug。 (2) 2.0.10 下时区设置导致客户端不能连 我们在跑命令的时候没有问题,并且结果是可以的,但是跑完后就断掉了,从后台看也是有问题的,重启 TiDB 组件也不行,后来找到代码我们发现这是一个 bug。 原因 :这个 bug 会在你连接时 check 这个时区,导致用户不能连接。 解决办法 :我们找研发同事重新编译一个 tidb-server 登入服务器,把时区设置为正确的,然后使用最初的 TiDB 组件登录, 2.1 版本后这个 bug 修复。 (3) Spring 框架下 TiDB 事务 这个问题是比较重要的问题,有个产品需要生成一个唯一的保单号,业务是批量生成的,当时在 TiDB 中我们建了一个表,表中只有一条数据,但是我们发现会有重复保单号出来。 原因 :TiDB 使用乐观事务模型,在高并发执行 Update 语句对同一条记录更新时,不同事务拿的版本值可能是相同的,由于不同事务只有在提交时,才会检查冲突,而不是像 Oracle、MySQL、PG 那样,使用锁机制来实现对一记录的串行化更改。 解决办法 :Spring 开发框架下,对事务的管理是使用注解式的,无法捕获到 TiDB commit 时的返回状态。因此需要将 spring 注解式事务改成编程式事务,并对 commit 状态进行捕获,根据状态来决定是重试机制,具体步骤: 利用 redis 实现分布式锁,执行 SQL。 捕获事务 commit 状态,并判断更新成功还是失败: 失败:影响行数为 0 || 影响行数为 1 && commit 时出现异常。 成功:影响行数为 1 && commit 时无异常。 数据库 2019-05-29 10:25:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 加法 select sysdate,add_months(sysdate,12) from dual; --加1年 select sysdate,add_months(sysdate,1) from dual; --加1月 select sysdate,to_char(sysdate+7,'yyyy-mm-dd HH24:MI:SS') from dual; --加1星期 select sysdate,to_char(sysdate+1,'yyyy-mm-dd HH24:MI:SS') from dual; --加1天 select sysdate,to_char(sysdate+1/24,'yyyy-mm-dd HH24:MI:SS') from dual; --加1小时 select sysdate,to_char(sysdate+1/24/60,'yyyy-mm-dd HH24:MI:SS') from dual; --加1分钟 select sysdate,to_char(sysdate+1/24/60/60,'yyyy-mm-dd HH24:MI:SS') from dual; --加1秒 减法 select sysdate,add_months(sysdate,-12) from dual; --减1年 select sysdate,add_months(sysdate,-1) from dual; --减1月 select sysdate,to_char(sysdate-7,'yyyy-mm-dd HH24:MI:SS') from dual; --减1星期 select sysdate,to_char(sysdate-1,'yyyy-mm-dd HH24:MI:SS') from dual; --减1天 select sysdate,to_char(sysdate-1/24,'yyyy-mm-dd HH24:MI:SS') from dual; --减1小时 select sysdate,to_char(sysdate-1/24/60,'yyyy-mm-dd HH24:MI:SS') from dual; --减1分钟 select sysdate,to_char(sysdate-1/24/60/60,'yyyy-mm-dd HH24:MI:SS') from dual; --减1秒 ORACLE时间函数(SYSDATE)简析 1:取得当前日期是本月的第几周 SQL> select to_char(sysdate,'YYYYMMDD W HH24:MI:SS') from dual; TO_CHAR(SYSDATE,'YY ------------------- 20030327 4 18:16:09 SQL> select to_char(sysdate,'W') from dual; T - 4 2:取得当前日期是一个星期中的第几天,注意星期日是第一天 SQL> select sysdate,to_char(sysdate,'D') from dual; SYSDATE T --------- - 27-MAR-03 5 完整请参考: https://blog.csdn.net/wulex/article/details/79294363 数据库 2019-05-28 09:45:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 介绍 如今使用移动互联网的年轻人开始越来越多使用短视频展示自我,而流媒体则是支撑在线视频播放的核心技术。当我们开始构建流媒体站点时,往往面临最大的难题在于大量媒体音视频文件所占用的海量磁盘空间。譬如说,一个标准高清短视频可能需要30-50MB的存储空间,那么存储百万短视频的系统就需要几十TB的存储。而如果我们更进一步需要播放高清电影等视频内容,每个电影需要大概2GB左右的空间,十万部各种电影剧集则需要200TB的存储容量。 为了满足如此海量的音视频文件存储需求,大部分流媒体服务器使用外接NAS甚至分布式文件系统进行PB级海量数据存储。 通过阅读本文,用户可以了解到如何使用SequoiaDB巨杉数据库的分布式文件系统功能,为开源流媒体服务器Emby构建后端可弹性扩展的分布式存储。 Emby是一款被广泛使用的流媒体服务器,允许用户简单一键式部署自己的视频播放系统,将家庭内部或企业存储的图片、音视频文件进行统一管理,并能够在几乎全部设备上在线播放观看。 用户可以在Emby的服务端设置服务器路径指定其所访问的文件系统。一般来说,使用PC机或NAS设备内置的磁盘存储容量有限,因此如果希望将Emby后端的存储使用分布式文件系统替换传统内置盘,可以使用SequoiaDB所提供的分布式文件系统SequoiaFS,提供用户近乎无限可弹性扩展存储容量。 安装SequoiaDB 本文使用Linux Ubuntu Server 18.10作为服务器,SequoiaDB巨杉数据库版本为3.2。 本教程默认使用sudo用户名密码为“sequoiadb:sequoiadb”,默认home路径为/home/sequoiadb。 对于使用CentOS等其他Linux版本的用户,本文所描述的流程可能略有不同,需要根据实际情况自行调整。 更多安装细节可以查看SequoiaDB信息中心 下载并安装SequoiaDB巨杉数据库
使用数据库实例用户创建默认实例
本教程使用Emby版本为4.1.1。 1)登录Emby官网 https://emby.media 下载安装包 或登录sequoiadb用户,使用wget下载安装包
2)安装Emby Server
4)按照向导进行配置,用户名密码建议均使用sdbadmin 5)在添加媒体页面添加SequoiaFS挂载点作为外接分布式文件系统 6)在添加媒体库页面,内容类型选择Movies,输入显示名后点击目录旁边的“+”号 7)使用 /opt/sequoiadb/mountpoint 作为分布式文件系统挂载点路径,并点击OK 8)点击OK进入下一步配置 9)按照向导完成后续配置 上传并使用流媒体服务器 1)将电影视频拷贝入 /opt/sequoiadb/mountpoint 目录
2)在Emby首页的媒体库点击“刷新元数据”进行后台元数据同步 进入电影库可以看到上传的电影 3)点击电影可以在线播放 结论 SequoiaDB巨杉数据库作为一款分布式数据库,提供包括结构化SQL、与非结构化文件系统和对象存储的机制。 使用SequoiaDB的非结构化存储能力,用户可以轻松基于分布式文件系统接口构建多媒体应用程序,并保持与传统Posix文件系统的完整兼容。 本文向读者展示了如何通过SequoiaDB的文件系统接口能力构建与Posix文件系统兼容的分布式文件系统,同时搭建Emby流媒体服务器。通过使用基于SequoiaDB的分布式文件系统能力,流媒体服务器能够使后台的存储空间得到近无限的弹性扩展能力。 数据库 2019-05-27 17:58:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 在项目中,遇到了一个存储文本的字段, 页面上展示的时候, 是一个查询字段 之前用的 的是 ilike 这样的查询方法, 在数据量过100万的时候, 查询基本没有效率. 例如 " where content ilike '%more%'; 通过学习pg的手册, 12 章节中的内容, 尝试了如下2中方法: SELECT 'a fat cat sat on a more and ate a fat rat' :: tsvector @@ 'more' :: tsquery; 这种返回的结果为 t, ① SELECT to_tsvector('dfsjalfjsl more dflasjfl top') @@ plainto_tsquery(' more '); 这种返回的结果为f, ② 特别注意如上的写法,经过大量数据的测试下, 使用了① 方法, 如果要写在 where 条件中, 例如 : wher content::tsvector@@'more'::tsquery 说明 : content 这个是你的文本存储的字段, 大概意思就是, 将字段做一次数据上的处理, tsvector 是那个做数据处理的函数, 'more' 是你要搜索的关键词, 这个没啥好解释的 使用的场景 : 1 ilike 比较适合模糊的匹配, 而本文提供的方法是一种比较精确的, 欢迎大家去测试 2 效率的问题, ilike 在数据量大的情况, 查询速度不是一般慢,数据量过100万的时候, 而本文提供的方法则速度很快, 暂时还没有测试出有没有其他未知的隐患, 欢迎大家来信交流学习. 由于没有经过大量的测试,导致本文中以上的结论失效了, 在实际的项目中, 我用了 where to_tsvector(content)@@to_tsquery('more'); content 为要搜索 的表字段, more 为搜索的关键词, 数据库 2019-05-27 16:35:00 「深度学习福利」大神带你进阶工程师,立即查看>>> 作者:张文博 Kubernetes (K8s)是一个开源容器编排系统,可自动执行应用程序部署、扩展和管理。它是云原生世界的操作系统。 K8s 或操作系统中的任何缺陷都可能使用户进程存在风险。作为 PingCAP EE(效率工程)团队,我们在 K8s 中测试 TiDB Operator (一个创建和管理 TiDB 集群的工具)时,发现了两个 Linux 内核错误。这些错误已经困扰我们很长一段时间,并没有在整个 K8s 社区中彻底修复。 经过广泛的调查和诊断,我们已经确定了处理这些问题的方法。在这篇文章中,我们将与大家分享这些解决方法。不过,尽管这些方法很有用,但我们认为这只是权宜之策,相信未来会有更优雅的解决方案,也期望 K8s 社区、RHEL 和 CentOS 可以在不久的将来彻底修复这些问题。 Bug #1: 诊断修复不稳定的 Kmem Accounting 关键词:SLUB: Unable to allocate memory on node -1 社区相关 Issue: https://github.com/kubernetes/kubernetes/issues/61937 https://github.com/opencontainers/runc/issues/1725 https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006 问题起源 薛定谔平台是我司开发的基于 K8s 建立的一套自动化测试框架,提供各种 Chaos 能力,同时也提供自动化的 Bench 测试,各类异常监控、告警以及自动输出测试报告等功能。我们发现 TiKV 在薛定谔平台上做 OLTP 测试时偶尔会发生 I/O 性能抖动,但从下面几项来看未发现异常: TiKV 和 RocksDB 的日志 CPU 使用率 内存和磁盘等负载信息 只能偶尔看到 dmesg 命令执行的结果中包含一些 “SLUB: Unable to allocate memory on node -1” 信息。 问题分析 我们使用 perf-tools 中的 funcslower trace 来执行较慢的内核函数并调整内核参数 hung_task_timeout_secs 阈值,抓取到了一些 TiKV 执行写操作时的内核路径信息: 从上图的信息中可以看到 I/O 抖动和文件系统执行 writepage 有关。同时捕获到性能抖动的前后,在 node 内存资源充足的情况下, dmesg 返回的结果也会出现大量 “SLUB: Unable to allocate memory on node -1” 的信息。 从 hung_task 输出的 call stack 信息结合内核代码发现,内核在执行 bvec_alloc 函数分配 bio_vec 对象时,会先尝试通过 kmem_cache_alloc 进行分配, kmem_cache_alloc 失败后,再进行 fallback 尝试从 mempool 中进行分配,而在 mempool 内部会先尝试执行 pool->alloc 回调进行分配, pool->alloc 分配失败后,内核会将进程设置为不可中断状态并放入等待队列中进行等待,当其他进程向 mempool 归还内存或定时器超时(5s) 后,进程调度器会唤醒该进程进行重试 ,这个等待时间和我们业务监控的抖动延迟相符。 但是我们在创建 Docker 容器时,并没有设置 kmem limit,为什么还会有 kmem 不足的问题呢?为了确定 kmem limit 是否被设置,我们进入 cgroup memory controller 对容器的 kmem 信息进行查看,发现 kmem 的统计信息被开启了, 但 limit 值设置的非常大。 我们已知 kmem accounting 在 RHEL 3.10 版本内核上是不稳定的,因此怀疑 SLUB 分配失败是由内核 bug 引起的,搜索 kernel patch 信息我们发现确实是内核 bug, 在社区高版本内核中已修复: slub: make dead caches discard free slabs immediately 同时还有一个 namespace 泄漏问题也和 kmem accounting 有关: mm: memcontrol: fix cgroup creation failure after many small jobs 那么是谁开启了 kmem accounting 功能呢?我们使用 bcc 中的 opensnoop 工具对 kmem 配置文件进行监控,捕获到修改者 runc 。从 K8s 代码上可以确认是 K8s 依赖的 runc 项目默认开启了 kmem accounting。 解决方案 通过上述分析,我们要么升级到高版本内核,要么在启动容器的时候禁用 kmem accounting 功能,目前 runc 已提供条件编译选项,可以通过 Build Tags 来禁用 kmem accounting,关闭后我们测试发现抖动情况消失了,namespace 泄漏问题和 SLUB 分配失败的问题也消失了。 操作步骤 我们需要在 kubelet 和 docker 上都将 kmem account 功能关闭。 kubelet 需要重新编译,不同的版本有不同的方式。 如果 kubelet 版本是 v1.14 及以上,则可以通过在编译 kubelet 的时候加上 Build Tags 来关闭 kmem account: $ git clone --branch v1.14.1 --single-branch --depth 1 [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) $ cd kubernetes $ KUBE_GIT_VERSION=v1.14.1 ./build/run.sh make kubelet GOFLAGS="-tags=nokmem" 但如果 kubelet 版本是 v1.13 及以下,则无法通过在编译 kubelet 的时候加 Build Tags 来关闭,需要重新编译 kubelet,步骤如下。 首先下载 Kubernetes 代码: $ git clone --branch v1.12.8 --single-branch --depth 1 https://github.com/kubernetes/kubernetes $ cd kubernetes 然后手动将开启 kmem account 功能的 两个函数 替换成 下面这样 : func EnableKernelMemoryAccounting(path string) error { return nil } func setKernelMemory(path string, kernelMemoryLimit int64) error { return nil } 之后重新编译 kubelet: $ KUBE_GIT_VERSION=v1.12.8 ./build/run.sh make kubelet 编译好的 kubelet 在 ./_output/dockerized/bin/$GOOS/$GOARCH/kubelet 中。 同时需要升级 docker-ce 到 18.09.1 以上,此版本 docker 已经将 runc 的 kmem account 功能关闭。 最后需要重启机器。 验证方法是查看新创建的 pod 的所有 container 已关闭 kmem,如果为下面结果则已关闭: $ cat /sys/fs/cgroup/memory/kubepods/burstable/pod Bug #2:诊断修复网络设备引用计数泄漏问题 关键词:kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1 社区相关 Issue: https://github.com/kubernetes/kubernetes/issues/64743 https://github.com/projectcalico/calico/issues/1109 https://github.com/moby/moby/issues/5618 问题起源 我们的薛定谔分布式测试集群运行一段时间后,经常会持续出现“kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1” 问题,并会导致多个进程进入不可中断状态,只能通过重启服务器来解决。 问题分析 通过使用 crash 工具对 vmcore 进行分析,我们发现内核线程阻塞在 netdev_wait_allrefs 函数,无限循环等待 dev->refcnt 降为 0。由于 pod 已经释放了,因此怀疑是引用计数泄漏问题。我们查找 K8s issue 后发现问题出在内核上,但这个问题没有简单的稳定可靠复现方法,且在社区高版本内核上依然会出现这个问题。 为避免每次出现问题都需要重启服务器,我们开发一个内核模块,当发现 net_device 引用计数已泄漏时,将引用计数清 0 后移除此内核模块(避免误删除其他非引用计数泄漏的网卡)。为了避免每次手动清理,我们写了一个监控脚本,周期性自动执行这个操作。但此方案仍然存在缺陷: 引用计数的泄漏和监控发现之间存在一定的延迟,在这段延迟中 K8s 系统可能会出现其他问题; 在内核模块中很难判断是否是引用计数泄漏, netdev_wait_allrefs 会通过 Notification Chains 向所有的消息订阅者不断重试发布 NETDEV_UNREGISTER 和 NETDEV_UNREGISTER_FINAL 消息,而经过 trace 发现消息的订阅者多达 22 个,而去弄清这 22 个订阅者注册的每个回调函数的处理逻辑来判断是否有办法避免误判也不是一件简单的事。 解决方案 在我们准备深入到每个订阅者注册的回调函数逻辑的同时,我们也在持续关注 kernel patch 和 RHEL 的进展,发现 RHEL 的 solutions:3659011 有了一个更新,提到 upstream 提交的一个 patch: route: set the deleted fnhe fnhe_daddr to 0 in ip_del_fnhe to fix a race 在尝试以 hotfix 的方式为内核打上此补丁后,我们持续测试了 1 周,问题没有再复现。我们向 RHEL 反馈测试信息,得知他们已经开始对此 patch 进行 backport。 操作步骤 推荐内核版本 Centos 7.6 kernel-3.10.0-957 及以上。 安装 kpatch 及 kpatch-build 依赖: UNAME=$(uname -r) sudo yum install gcc kernel-devel-${UNAME%.*} elfutils elfutils-devel sudo yum install pesign yum-utils zlib-devel \ binutils-devel newt-devel python-devel perl-ExtUtils-Embed \ audit-libs audit-libs-devel numactl-devel pciutils-devel bison # enable CentOS 7 debug repo sudo yum-config-manager --enable debug sudo yum-builddep kernel-${UNAME%.*} sudo debuginfo-install kernel-${UNAME%.*} # optional, but highly recommended - enable EPEL 7 sudo yum install ccache ccache --max-size=5G 安装 kpatch 及 kpatch-build: git clone https://github.com/dynup/kpatch && cd kpatch make sudo make install systemctl enable kpatch 下载并构建热补丁内核模块: curl -SOL https://raw.githubusercontent.com/pingcap/kdt/master/kpatchs/route.patch kpatch-build -t vmlinux route.patch (编译生成内核模块) mkdir -p /var/lib/kpatch/${UNAME} cp -a livepatch-route.ko /var/lib/kpatch/${UNAME} systemctl restart kpatch (Loads the kernel module) kpatch list (Checks the loaded module) 总结 虽然我们修复了这些内核错误,但是未来应该会有更好的解决方案。对于 Bug#1,我们希望 K8s 社区可以为 kubelet 提供一个参数,以允许用户禁用或启用 kmem account 功能。对于 Bug#2,最佳解决方案是由 RHEL 和 CentOS 修复内核错误,希望 TiDB 用户将 CentOS 升级到新版后,不必再担心这个问题。 数据库 2019-05-27 13:38:00 |
---|