一、索引的概念
索引就是加快检索表中数据的方法。
数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。
二、索引的特点
1.索引可以加快数据库的检索速度
2.索引降低了数据库插入、修改、删除等维护任务的速度
3.索引创建在表上,不能创建在视图上
4.索引既可以直接创建,也可以间接创建
5.可以在优化隐藏中,使用索引
6.使用查询处理器执行
SQL语句,在一个表上,一次只能使用一个索引
7.其他
三、索引的优点
1.创建唯一性索引,保证数据库表中每一行数据的唯一性
2.大大加快数据的检索速度,这也是创建索引的最主要的原因
3.加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
4.在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
5.通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。
四、索引的缺点
1.创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
2.索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大
3.当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
五、索引分类
1.直接创建索引和间接创建索引
直接创建索引: CREATE INDEX mycolumn_index ON mytable (myclumn)
间接创建索引:定义主键约束或者唯一性键约束,可以间接创建索引
2.普通索引和唯一性索引
普通索引:
CREATE INDEX mycolumn_index ON mytable (myclumn)
唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
3.单个索引和复合索引
单个索引:即非复合索引
复合索引:又叫组合索引,在索引建立语句中同时包含多个字段名,最多16个字段
CREATE INDEX name_index ON username(firstname,lastname)
4.聚簇索引和非聚簇索引(聚集索引,群集索引)
聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH
ALLOW_DUP_ROW(允许有重复记录的聚簇索引)
非聚簇索引:
CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)
六、索引的使用
1.当字段数据更新频率较低,查询使用频率较高并且存在大量重复值是建议使用聚簇索引
2.经常同时存取多列,且每列都含有重复值可考虑建立组合索引
3.复合索引的前导列一定好控制好,否则无法起到索引的效果。如果查询时前导列不在查询条件中则该复合索引不会被使用。前导列一定是使用最频繁的列
4.多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案
5.where子句中对列的任何操作结果都是在sql运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被sql优化器优化,使用索引,避免表搜索。
例:
select * from record where substring(card_no,1,4)=’5378’ && select * from record where card_no like ’5378%’
任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边
6.where条件中的’in’在逻辑上相当于’or’,所以语法分析器会将in ('0','1')转化为column='0' or column='1'来执行。我们期望它会根据每个or子句分别查找,再将结果相加,这样可以利用column上的索引;但实际上它却采用了"or策 略",即先取出满足每个or子句的行,存入临时数据库的工作表中,再建立唯一索引以去掉重复行,最后从这个临时表中计算结果。因此,实际过程没有利用 column上索引,并且完成时间还要受tempdb数据库性能的影响。in、or子句常会使用工作表,使索引失效;如果不产生大量重复值,可以考虑把子 句拆开;拆开的子句中应该包含索引
7.要善于使用存储过程,它使sql变得更加灵活和高效
基于TCP(面向连接)的socket编程,分为服务器端和客户端
服务器端的流程如下:
(1)创建套接字(socket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
(5)用返回的套接字和客户端进行通信(send/recv)
(6)返回,等待另一个客户请求。
(7)关闭套接字。
客户端的流程如下:
(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(send/recv)
(4)关闭套接字
下面通过一个具体例子讲解一下具体的过程和相关的函数,环境是suse linux。
#include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <memory.h> #include <unistd.h> //#include <linux/in.h> #include <netinet/in.h> //#include <linux/inet_diag.h> #include <arpa/inet.h> #include <signal.h> /** 关于 sockaddr sockaddr_in socketaddr_un说明 http://maomaozaoyue.blog.sohu.com/197538359.html */ #define PORT 11910 //定义通信端口 #define BACKLOG 5 //定义侦听队列长度 #define buflen 1024 void process_conn_server(int s); void sig_pipe(int signo); int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符 int main(int argc,char *argv[]) { struct sockaddr_in server_addr; //存储服务器端socket地址结构 struct sockaddr_in client_addr; //存储客户端 socket地址结构 int err; //返回值 pid_t pid; //分叉进行的ID /*****************socket()***************/ ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流 if(ss<0) { printf(" server : server socket create error\n"); return -1; } //注册信号 sighandler_t ret; ret = signal(SIGTSTP,sig_pipe); if(SIG_ERR == ret) { printf("信号挂接失败\n"); return -1; } else printf("信号挂接成功\n"); /******************bind()****************/ //初始化地址结构 memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; //协议族 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 server_addr.sin_port = htons(PORT); err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr)); if(err<0) { printf("server : bind error\n"); return -1; } /*****************listen()***************/ err = listen(ss,BACKLOG); //设置监听的队列大小 if(err < 0) { printf("server : listen error\n"); return -1; } /****************accept()***************/ /** 为类方便处理,我们使用两个进程分别管理两个处理: 1,服务器监听新的连接请求;2,以建立连接的C/S实现通信 这两个任务分别放在两个进程中处理,为了防止失误操作 在一个进程中关闭 侦听套接字描述符 另一进程中关闭 客户端连接套接字描述符。注只有当所有套接字全都关闭时 当前连接才能关闭,fork调用的时候父进程与子进程有相同的 套接字,总共两套,两套都关闭掉才能关闭这个套接字 */ for(;;) { socklen_t addrlen = sizeof(client_addr); //accept返回客户端套接字描述符 sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数 if(sc < 0) //出错 { continue; //结束此次循环 } else { printf("server : connected\n"); } //创建一个子线程,用于与客户端通信 pid = fork(); //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID if(pid == 0) //子进程,与客户端通信 { close(ss); process_conn_server(sc); } else { close(sc); } } } /** 服务器对客户端连接处理过程;先读取从客户端发送来的数据, 然后将接收到的数据的字节的个数发送到客户端 */ //通过套接字 s 与客户端进行通信 void process_conn_server(int s) { ssize_t size = 0; char buffer[buflen]; //定义数据缓冲区 for(;;) { //等待读 for(size = 0;size == 0 ;size = read(s,buffer,buflen)); //输出从客户端接收到的数据 printf("%s",buffer); //结束处理 if(strcmp(buffer,"quit") == 0) { close(s); //成功返回0,失败返回-1 return ; } sprintf(buffer,"%d bytes altogether\n",size); write(s,buffer,strlen(buffer)+1); } } void sig_pipe(int signo) { printf("catch a signal\n"); if(signo == SIGTSTP) { printf("接收到 SIGTSTP 信号\n"); int ret1 = close(ss); int ret2 = close(sc); int ret = ret1>ret2?ret1:ret2; if(ret == 0) printf("成功 : 关闭套接字\n"); else if(ret ==-1 ) printf("失败 : 未关闭套接字\n"); exit(1); } } |
客户端代码:
#include <stdio.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> //#include <linux/in.h> #include <stdlib.h> #include <memory.h> #include <arpa/inet.h> #include <netinet/in.h> #include <signal.h> //添加信号处理 防止向已断开的连接通信 /** 信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号, 信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认 响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行 */ #define PORT 11910 #define Buflen 1024 void process_conn_client(int s); void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值 int s; //全局变量 , 存储套接字描述符 int main(int argc,char *argv[]) { sockaddr_in server_addr; int err; sighandler_t ret; char server_ip[50] = ""; int port = 0; strcpy(server_ip, argv[1]); port = atoi(argv[2]); /********************socket()*********************/ s= socket(AF_INET,SOCK_STREAM,0); if(s<0) { printf("client : create socket error\n"); return 1; } printf("client : socket fd = %d\n", s); //信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号 ret = signal(SIGTSTP,sig_pipe); if(SIG_ERR == ret) { printf("信号挂接失败\n"); return -1; } else printf("信号挂接成功\n") ; /*******************connect()*********************/ //设置服务器地址结构,准备连接到服务器 memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*将用户数入对额字符串类型的IP格式转化为整型数据*/ //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr); printf("please input server ip address : \n"); read(0,server_ip,50); //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr); server_addr.sin_addr.s_addr = inet_addr(server_ip); err = connect(s,(struct sockaddr *)&server_addr,sizeof(sockaddr)); if(err == 0) { printf("client : connect to server\n"); } else { printf("client : connect error\n"); return -1; } //与服务器端进行通信 process_conn_client(s); close(s); } void process_conn_client(int s) { ssize_t size = 0; char buffer[Buflen]; for(;;) { memset(buffer,'\0',Buflen); /*从标准输入中读取数据放到缓冲区buffer中*/ size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error if(size > 0) { //当向服务器发送 “quit” 命令时,服务器首先断开连接 write(s,buffer,strlen(buffer)+1); //向服务器端写 //等待读取到数据 for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) ); write(1,buffer,strlen(buffer)+1); //向标准输出写 } } } void sig_pipe(int signo) //传入套接字描述符 { printf("Catch a signal\n"); if(signo == SIGTSTP) { printf("接收到 SIGTSTP 信号\n"); int ret = close(s); if(ret == 0) printf("成功 : 关闭套接字\n"); else if(ret ==-1 ) printf("失败 : 未关闭套接字\n"); exit(1); } } |
工作中很少用mysql插入数据,今天正好遇到,
学习下:
在toad mysql工具里面,除了像execl表格一样手动插入数据之外,最好用insert语句插入数据,怎么快速生成数据呢?
1. 右击需要插入数据表,这里是cd_financing_income,弹出Generate SQL--->选择TO editor——>Insert statement,以今天需要用到的cd_financing_income为例:
生成
INSERT INTO `51qian`.cd_financing_income (service_range_id,trac_id, ic_user_id,principal,interest, status,create_time,income_time,service_fee,order_id, bid_id,real_income_time) VALUES ('trac_id', ic_user_id,principal,interest, 'status','create_time','income_time',service_fee,'order_id', bid_id,'real_income_time'); |
2 按照需求,我们需要添加以下数据:
idservice_range_idtrac_idic_user_idprincipal,intereststatuscreate_timeincome_timeservice_feeorder_idbid_idreal_income_time 15102147M20131113000020548044105200478602014/2/13 13:082014/3/13 12:321795205485961 15103147M20131113000020548053105200529902014/2/13 13:082014/4/13 12:321987205485961 |
关于id:a 可以自己添加设置id号(id号切记不能重复);也可以不设置,默认为空,就是待插入数据之后,系统自动生成id号;
通常情况下,若添加的数据id与几张表相关联,则id号需要自己设置添加(id不能重复);
在这里,我们让系统自动生成id号,故选择缺省;
b 字段赋值有引号的加引号(字段名只有单引号,没有双引号),没引号的不要加;
3 最后一个字段real_income_time为空,在这里,我们需要双击该表,打开Script,若real_income_time字段注释为DEFAULT NULL,我们就把为空的字段去掉;
若字段注释为DEFAULT '0',我们就‘0’(表示空);
这里的real_income_time字段注释为DEFAULT NULL,我们则直接把该字段进行缺省(字段名和字段值直接缺省);
4 第一个括号后面表示字段名,values后面的括号是字段值,我们只需要改字段值即可(除非是缺省的值,字段名需要另外去掉);
下面就写成:
INSERT INTO cd_financing_income(service_range_id, trac_id, ic_user_id, principal, interest, status, create_time, income_time, service_fee, order_id, bid_id) VALUES (147, 'M20131113000020548044', 10520, 0, 4786, '0', '2014/2/13 13:08:04', '2014/3/13 12:32:33', 1795, '20548', 5961); INSERT INTO cd_financing_income(service_range_id, trac_id, ic_user_id, principal, interest, status, create_time, income_time, service_fee, order_id, bid_id) VALUES (147, 'M20131113000020548053', 10520, 0, 5299, '0', '2014/2/13 13:08:04', '2014/4/13 12:32:33', 1987, '20548', 5961); |
说明:1. 51qian是数据库名,若是在线上插入数据,库名不一样,需要省略;
2 为了使插入数据清晰,不易出错,我们可以选择格式化图标format SQL;
-------------------------------
在此说明下数据库的连接,因为平时测试时候需要导数据,这时候就需要用到多个库;
目前我们连接的是51qian库,若改用test库,我们就在51qian上选择右击disconnect;
再连接test;
说到线程的概念, 自然离不开另外两个词: 程序和进程.
从最基本的程序讲起:
一. 什么是程序(Program)
所谓程序, 就是1个严格有序的指令集合. 程序规定了完成某一任务时,计算机所需要做的各种操作, 以及操作的顺序.
1.1 单道程序运行环境
所谓单道程序环境就是指, 计算机除了
操作系统之外, 只允许运行1个用户程序.
以前的DOS系统就是1个典型的单道程序运行环境.
单道程序有如下特点:
1. 资源的独占性: 任何时候, 内存内只有1个用户程序, 这个程序独享系统的所有资源.
2. 执行顺序性: 内存中只执行1个程序, 各程序是按次序执行的, 即是做完1件事后, 才做另一件事.
绝不可能执行1个程序的中途暂停, 然后去执行另1个程序.
3.结果的再现性: 只要执行环境和初始条件相同, 重复执行1个程序, 获得的结果总是一样的.
1.2 多道程序运行环境
所谓多道程序运行环境是指, 计算机除了操作系统之外, 允许同时运行多个用户程序.
当今的
unix, linux都是典型的多道程序运行环境.
多道程序有如下特点:
1. 间断性: 也就是cpu会在多个程序之间切换执行, 例如cpu执行程序A一段时间后, 会切换去执行程序B一段时间.
由于这个时间段很短, 所以对用户造成1个程序A和程序B同时执行的假象.
2. 失去封闭性: 程序执行受外界影响, 也就是多个程序之间共享资源, 互相制约.
3. 不可再现性: 重复执行时, 可能得到不同的结果. 原因就是上面的点, 执行时可能会受到内存中其他程序的影响.
二. 什么是进程(process)
进程这个概念是基于多道程序运行环境来讲的.
1个进程就是程序在内存中运行的1个实例
由于在多道程序运行环境中, 同1个程序可以同时产生两个实例在内存里运行.
举个简单例子: 例如你可以在操作系统打开两个gvim 文本编辑器, 同时编辑两个不同的文件.
这时执行ps -ef | grep gvim 就会见到系统中有两个名字是gvim的进程, 但是它们的进程id是不同的.
也就是将, gvim这个程序在内存中生成两个进程同时运行.
在多道程序运行环境中. 引用进程这个概念来描叙程序在内存里事例, 用来区别于程序本身.
真正深入了解进程并不简单, 进程其实算是属于操作系统这门课的范畴. 1个进程包括程序段, 数据段, 程序控制块等, 但是作为一个普通的程序猿, 没必要理解得这么深入.
三. 什么是线程(thread).
相对地, 我认为作为1个普通的程序猿, 必须深入了解线程这个概念.
其实, 线程就是1个进程的执行路径.
一般的java程序都是从启动类的main函数入口开始执行, main函数的结束而停止. 这条执行路径就是java程序的主线程.
也就是讲:
线程是相对于进程来讲的, 1个进程至少存在1条线程.
而java允许多线程编程, 也就是1个进程除主线程外, 还允许存在1个或若干个其他线程, cpu会在这些线程当中间断切换执行, 给用户造成同时执行的假象.
四. 1个单线程java程序的简单例子.
例子如下:
package Thread_kng; class S_thrd_1{ public void f(){ while (true){ System.out.printf("Thread main is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } public class S_thread_expl{ public static void g(){ S_thrd_1 s = new S_thrd_1(); s.f(); System.out.printf("g() is done!\n"); } } |
上面的例子中, g() 函数调用了f() 函数,
g()函数在最后尝试输出"g() is done!" , 但是因为f() 是一个无限循环. 所以g() 调用f()后, 根本没机会执行后面的语句!
也就是说, 虽然g() 跟 f() 是两个不同类的函数, 但是它们是在程序中的同一个线程(主线程)内执行.
在同一个线程内, 语句总是按程序猿编写的顺序依次执行, 一条语句未执行完时, 不会跳到后面执行其他的语句.
五. 1个多线程java程序的简单例子.
例子如下:
package Thread_kng; class M_thrd_1 extends Thread{ //extends public M_thrd_1(String name){ super(name); } public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("Thread " + this.getName()+ " is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } public class M_thread_expl{ public static void g(){ M_thrd_1 s = new M_thrd_1("T1"); s.start(); //start()method is extended from superclass, it will call the method run() M_thrd_1 s2 = new M_thrd_1("T2"); s2.start(); System.out.printf("g() is done!\n"); } |
上面例子中, 类 M_thrd_1继承了线程类Thread. 并重写了run方法.
在下面的g()函数中.
实例化了两个类M_thrd_1的对象s, s1 的对象.
执行了start()方法.
start()方法继承字Thread, 它会启动1新线程, 并调用run()函数.
而g()后面本身也在不断循环输出"THread Main is running"
所以输出如下:
可以 见到, 输出结果是T1,T2 和Main 交替输出在屏幕上.
实际上, 这个程序有3个线程.
其中1个就是主线程.
主线程main通过实例化两个M_thrd_1的对象, 调用其start()函数开启了两个子线程.
其中1个子线程不断输出T1
另1个子线程不断输出T2
但是主线程并不会等待其子线程执行完成, 会继续执行下面的代码,所以不断循环输出Main!
所以这个例子实际上要3个线程在输出信息到屏幕了!
六. Java 开启一条新进程的两种方式.
java开启一条新线程, 有两种方式. 但是都要利用java的线程基类Thread.
6.1 方式1. 创建Thread的派生类
这个就类似上面的例子:
具体步骤如下:
6.1.1 线程准备: 新建1个类, 继承线程类Thread. 将业务代码写入重写后的run() 方法中.
例如上面的例子中的M_thrd_1类
class M_thrd_1 extends Thread{ //extends public M_thrd_1(String name){ super(name); } public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("Thread " + this.getName()+ " is runing!\n"); } //System.out.printf("f() is done!\n"); //compilation fail } } |
可以见到, 我们将线程要执行的业务代码写入了run() 方法.
6.1.2 启动线程: 新建1个上述类的对象, 运行start()方法
例如
M_thrd_1 s = new M_thrd();
s. start();
其中start() 是类M_thrd_1 继承自超类Thread的.
我们看看JDK API 对start() 方法的定义:
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
可以见到, 调用了start() 方法后, 会开始执行改线程, 也就是在当前线程下开启一条新线程.
而这条新线程做什么呢, 就是执行run()里面的代码.
所以我们一般只需要把也业务的代码写入run()里面就ok了. 不要重写start()方法.
注意, 如果直接执行s.run(); 则会在当前线程下执行run里面的代码, 并没有开启一条新线程. 区别很大的.
6.2 方式2. 创建1个类, 实现Runnable 接口
步骤跟上面的方式差别不大
6.2.1 线程准备: 新建1个类, 实现接口Runnable. 将业务代码写入重写后的run() 方法中.
例子:
class M_thrd_2 implements Runnable{ //extends public int id = 0; public void run(){ //overwrite the method run() of superclass while (true){ System.out.printf("%s: Thread " + this.id+ " is runing!\n", Thread.currentThread().getName()); } //System.out.printf("f() is done!\n"); //compilation fail } } |
上面的例子 M_thrd_2 就实现了接口Runnable
并重写了接口Runnable 的抽象方法run();
注意. currentThread() 是类Thread的一个静态方法, 作用是获取当前语句所在的线程.
6.2.2 启动线程: 新建1个上述类的对象s, 在新建1个类Thread的对象t, 把s作为一个参数用于t的构造方法. 并执行t.start()
貌似比方式一复杂.
其实非如下:
new Thread(new M_thrd_2()).start()
例子:
public class M_thread_expl2{ public static void g(){ M_thrd_2 s = new M_thrd_2(); s.id = 1; Thread t1 = new Thread(s); t1.start(); Thread t2 = new Thread(s); s.id = 2; t2.start(); } } |
在g() 函数中,
首先新建1个类M_thrd_2的 对象s.
并利用s作为参数 建立了两个线程类Thread的对象t1 和 t2. 并启动这两个线程.
注:
1. 这个例子中有3个线程, 其中1个主线程(也就是g() 函数所在的线程). 开启了两个子线程, 该两个子线程都在不断循环输出信息到屏幕上.
2. Thread(object ob) 是1个属于类Thread的构造方法, 其中的对象ob 必须实现Runnable 接口.
3. 这个例子执行时 , 第一个t1首先会输出id = 1, 但是当第二个线程t2开始执行后, t1会输出id=2, 因为t1, 和t2都是利用同1个对象建立的.
也就是说, 这个对象的变动会同时影响两个线程.
输出如下:
6.3 两种方式的区别
1. 方式1是创建继承类Thread的派生类, 方式2是创建实现Runnable接口的类.
2. 启动方式: 方式1是直接调用业务类的对象的start()方法, 方式2是利用业务类类的对象新建1个类Thread的对象, 并调用该对象的start()方法.
3. 如果要启动多个线程, 通过方式1需要新建多个不同业务类的对象, 方式2 则可以通过业务1个对象 构建多个Thread类对象, 但是业务对象的变动会同时影响对应的多个线程.
七. 关于线程的一些常用方法介绍.
7.1 run()
我们要吧线把要执行的业务代码写入这个方法. 上面提过了. 注意, 如果我们直接执行1个线程对象的run()方法可以合法的, 但是仍然是在当前线程内执行, 并没有开始一条新线程.
7.2 Start()
当1个线程or其派生类执行1个start()方法. 就开启1个新线程, 并调用该类的run()方法.
注意: 1个线程如果已经调用过一次start(), 再调用start()则或抛异常.
7.3 stop()
停止1个1个线程.
7.4 setName() 和 getName()
设置和获得对应线程的名字.
7.5 Thread.currentThread()
注意这个方法是一个static方法, 而上面的都不是静态方法.
这个方法返回当前执行语句所属的线程.
例如1个线程对象A. 它的run() 方法里调用了 Thread.currentThread().getName() 函数.
假如直接执行A.run() 则返回的是当前线程(而不是A线程) 的名字, 因为对象A并没有启动自己的线程.
假如执行难过A.start(), 那么该函数就返回A启动的线程的名字了.
八, 小结
这篇文章只是简介, 很多重要的方法例如 sleep() , wait() 等都没有介绍, 这些是线程控制的内容. 本吊打算在另一篇博文里介绍.
昨天一个同事请我一个问题,在下列代码中如何将
InputStream is 这个元素只初始化一次呢?
/* * * Script Description: * */ import lrapi.lr; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import com.google.common.io.ByteStreams; import com.jd.jfs.JingdongFileSystem; public class Actions { File file = new File("C:/8K.file"); InputStream is =null; // 返回一个byte数组<pre code_snippet_id="145362" snippet_file_name="blog_20140107_2_8044261" name="code" class="java"> byte[] fileBytes = new byte[(int) file.length()]; // 创建一个数据来保存文件数据 JingdongFileSystem jfs =new JingdongFileSystem(); public int init() throws Throwable { is = new FileInputStream(file); ByteStreams.readFully(is, fileBytes); is.close(); //jfs = new JingdongFileSystem(); return 0; } //end of init public int action() throws Throwable { try { lr.start_transaction("jfs-w"); String key = jfs.writeBytes(fileBytes); //上传 System.out.println(key); } catch (Exception e) { e.printStackTrace(); } lr.end_transaction("jfs-w", lr.AUTO); return 0; }//end of action public int end() throws Throwable { return 0; }//end of end } |
我们知道,在loadrunner的java_vuser协议的脚本中,init方法在每个vuer初始化的时候都会被执行一次,换句话说N个用户就会被执行N次,所以上面的脚本中inputStream对象会被初始化N次。我随即做了如下修改
File file = new File("C:/8K.file"); static InputStream is =null; // 返回一个byte数组 <span style="color: #ff0000; "> </span> byte[] fileBytes = new byte[(int) file.length()]; // 创建一个数据来保存文件数据 JingdongFileSystem jfs =new JingdongFileSystem(); public int init() throws Throwable { if(is==null){ is = new FileInputStream(file); } ByteStreams.readFully(is, fileBytes); is.close(); //jfs = new JingdongFileSystem(); //初始化 return 0; }//end of init 理论上来说,上述代码实现了单例模式。但是这个脚本并发下无效。。。 经过和开发探讨最终换了以下的代码来处理: static { File file = new File("C:/8K.file"); fileBytes = new byte[(int) file.length()]; // 创建一个数据来保存文件数据 try { InputStream is = new FileInputStream(file); ByteStreams.readFully(is, fileBytes); is.close(); } catch (Exception e) { e.printStackTrace(); } jfs = new JingdongFileSystem(); } |
静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员.
总结一下:
在java_vuser协议的脚本中:
1. vuser_init模块始终不会被执行;
2. init方法会被初始化合并发数相同的次数
3. 如果想实现多个并发用户公用一个变量,请使用静态初始化块。
1、 拿到测试需求后,分析理解本次测试的具体内容,需要了解的详细测试内容,联系研发进行需求调研
工作。侧重业务理解包括上下游业务,重点业务点,测试指标,测试环境数据量及相关的造数据条件,涉及到的
数据库,线上及测试环境部署,业务架构及提供测试时间;
2、 需求调研完成后,梳理本次测试业务,测试点,测试数据,环境部署,根据已得到的信息,选取测试方法,确定测试计划,编写测试方案并评审;
3、 搭建测试环境,包括修改测试环境的相关参数;
4、 编写测试脚本并调试;
确定
性能测试点:重要功能点,频繁使用功能点,用户关心功能点,与DB密切相关的功能点,模拟用户两三年内的数据量进行测试。
第二阶段:测试执行
1、 按照方案进行执行,包括基准测试、负载测试、并发测试、混合测试、集合点测试、异常测试、稳定性测试、配置测试等。
1) 可通过单条执行,查看日志,看错误日志内容,应用日志、tomcat日志、均衡服务日志、数据库日志等;
2) 通过JVM定位是否内存问题导致;
3) 通过jprofile监控,查看问题主要出在那个方法上,比如查看CPU使用和内存使用等;
4) 通过tcpdump及wireshark工具查看网络包内容,是否为网络造成问题;
5) 通过jstat –gcutil查看JVM内存回收;
6) 通过nmon工具查看服务器资源使用;
7) 换种压力工具,比如jmeter进行压测,是否出现同样的问题;
8) 使用dump命令将内存内容当下来,然后用MAT工具分析是否内存问题;
9) 通过远程配置,使用jconsole或者jvisualvm进行监控查看;
10) Lr_set_debuy_message()调试日志信息,定位问题原因;
3、 保存结果,完成测试;
第三阶段:分析测试结果
1、 分析测试结果包括资源监控结果;
2、 编写报告文档;将问题、调优、风险、结论全部做总结写进文档;
3、对整个项目做总结,通过本次项目学到了什么,有什么不足,及时
学习。
将测试过程,形成文档,记录执行的过程中,出现的问题及解决的方法,方便最后报告的编写。及时做总结。
1.什么是selenium
selenium是ThoughtWork的一款开源
测试框架。
下载selenium2.0lib包,点击http://code.google.com/p/selenium/downloads/list
这是官方文档:http://seleniumhq.org/docs/
2.为什么选择selenium
自动化测试工具有很多了,
QTP作为商业软件功能强大。但是要把QTP整合到已有的测试平台上面非常困难,selenium非常容易的可以整合到已有的测试平台上面去。如果你是一个刚刚不如
职场的新人不懂程序代码上手selenium有点困难,selenium需要一定的程序开发能力。为以后的测试平台长远考虑,故选用selenium测试框架。
1.安装java虚拟机JDK1.5版本以上。
2.下载安装火狐浏览器,并安装。
3.下载selenium支持火狐浏览器的IDE,通过这个IDE可以使用selenium语言编写脚本也可录制操作脚本。(不是必须安装IDE)
4.安装MyEclipse开发环境,并配置MyEclipse的环境。
3.selenium的基本使用
1.在Myeclipse建立一个JavaProject项目名称自定义。
2.将下载的selenium-server-standalone-x.xx.x.jar,导入到项目的Reference Libraries中。
3.从IDE中导出的测试脚本加入工程中。
4.在注解@Test中可以编写自动化测试步骤或脚本了。
选择元素
WebElement element = driver.findElement(By.id("passwd-id"));
在输入框中输入内容:
element.sendKeys(“
test”);
将输入框清空:
element.clear();
获取输入框的文本内容:
element.getText();
找到下拉选择框的元素:
Select select = new Select(driver.findElement(By.id("select"))); 选择对应的选择项:
上传文件的元素操作:
WebElement adFileUpload =driver.findElement(By.id("WAP-upload"));
String filePath = "C:\test\\uploadfile\\media_ads\\test.jpg";
adFileUpload.sendKeys(filePath);
一般来说,登录后建议是先:
driver.switchTo().defaultContent();
切换到某个frame:
driver.switchTo().frame("leftFrame");
从一个frame切换到另一个frame:
driver.switchTo().frame("mainFrame");
切换到某个window:
driver.switchTo().window("windowName");
调用Java Script
Web driver对Java Script的调用是通过JavascriptExecutor来实现的,例如:
JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("(function(){inventoryGridMgr.setTableFieldValue('"+ inventoryId + "','" + fieldName + "','" + value + "');})()"); |
页面等待
页面的操作比较慢,通常需要等待一段时间,页面元素才出现,但webdriver没有提供现成的方法,需要自己写。
等一段时间再对页面元素进行操作:
public void waitForPageToLoad(longtime) { try { Thread.sleep(time); } catch (Exceptione) { } } |
在找WebElement的时候等待:
public WebElementwaitFindElement(By by) { returnwaitFindElement(by, Long.parseLong(CommonConstant.GUI_FIND_ELEMENT_TIMEOUT),Long .parseLong(CommonConstant.GUI_FIND_ELEMENT_INTERVAL)); } public WebElementwaitFindElement(By by, long timeout, long interval) { long start = System.currentTimeMillis(); while (true) { try { return driver.findElement(by); } catch(NoSuchElementException nse) { if (System.currentTimeMillis()- start >= timeout) { throw newError("Timeout reached and element[" + by + "]not found"); } else { try { synchronized(this) { wait(interval); } } catch(InterruptedException e) { e.printStackTrace(); } } } } } |
4.selenium注意点
1.启动浏览器,如果不是默认安装需要指定启动路径 System.setProperty("webdriver.firefox.bin", PrivateDataSource.fireFoxPath);
2.driver在每次结束的时候都需要退出使用driver.quit();可以把driver产生的零时文件删除掉,如果直接关闭浏览器或driver.close();那么零时文件不会被删除,会
一直存在在你的C盘中,非常耗资源。
5.自动化测试策略
目前项目繁多而非产品那样的专业化,如果每一个项目都做自动化测试那么相对的工作量繁重,质量参差不齐无法很好的控制。
自动化测试针对项目应注重以下点进行实施:
1.项目进度督促使用自动化测试核心模块核心流程是否正常。
2.把控自动化测试质量,如果质量不过关还不如不做自动化测试。
3.进行并行开发策略,开发人员根据界面设计进行功能开发,测试人员根据界面设计进行自动化脚本开发。
4.通过jenkins持续集成平台,每次开发人员或测试人员提交代码都会自动去执行一偏测试脚本,把执行中出现的问题暴露出来,让开发人员及时修改。
通过学习使用selenium自动化测试框架,了解到测试不是简单的测试工作,她掺杂了项目管理,需求分析,人际沟通协调等等技能,需要不断的实践增强能力胜任更加严峻的项目工作。
采用easy_install的方式安装nose
1、先安装easy_install
easy_install 方便python 安装第三方扩展包的工具
Easy Install 是一个Python的模块(easy_install),跟 setuptools 绑定在一起,提供自动的程序下载、编译、安装和管理 Python 的包
1)先下载easy_install的安装包 http://pypi.python.org/pypi/setuptools
2)python setup.py install
2、安装nose
在命令行下输入:easy_install nose 回车 就自动安装了
nosetests ***.py 就自动运行当前文件下的testcase了
备注:nosetests 命令就是执行case的。
它会自动查找当前目录下包含“
Test”字样的目录和文件(文件里的python函数也已Test开头),然后执行,返回case执行成功与否。
在做混合场景
测试的时候,常面临一个问题:什么时候是到了应用的瓶颈了呢?
假设有三支交易(三个脚本): register、login、update,业务比例是 15%,75%,10%. 经过比例调配,我们得到三支交易的用户比例是4,2,3.
第一种测试结果:
测试序列用户总数TPS响应时间
第一组9(4/2/3)14/76/1115/20/12
第二组18(8/4/6)29/151/2416/21/12
第三组36(16/8/12)20/120/15100/150/180
那么我们发现第三组的测试结果性能比第二组明显有所下降,那么在这种典型的情况下.我可以很容易知道36并发就是瓶颈了.
第二种测试结果:
测试序列用户总数TPS响应时间
第一组9(4/2/3)14/76/1115/20/12
第二组18(8/4/6)29/151/2416/21/12
第三组36(16/8/12)20/300/47100/22/12
此种情况,我们假设register的响应时间也符合性能要求的话.第三组的时候其余两支交易的TPS仍然维持的比例的增多,但是register反而下降了.此时我们要动态减少login和update的用户数,并增加register的并发直到他们的tps符合比例为止. 正常情况下你会发现调节后的总TPS和第二组交易差不多,也就说此时已经到达了瓶颈.
当然,如果第三组的register的响应时间已经不符合性能要求的话,那么就更好判断了.
Hadoop版本为1.2.1
Linux使用Fedora19并使用hadoop账号安装
第一步:配置ssh本地登录证书(虽然为伪分布模式,Hadoop依然会使用SSH进行通信)
[hadoop@promote ~]$ which ssh /usr/bin/ssh [hadoop@promote ~]$ which ssh-keygen /usr/bin/ssh-keygen [hadoop@promote ~]$ which sshd /usr/sbin/sshd [hadoop@promote ~]$ ssh-keygen -t rsa |
然后一路回车
Generating public/private rsa key pair. Enter file in which to save the key (/home/hadoop/.ssh/id_rsa): Created directory '/home/hadoop/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Passphrases do not match. Try again. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/hadoop/.ssh/id_rsa. Your public key has been saved in /home/hadoop/.ssh/id_rsa.pub. The key fingerprint is: 2f:a9:60:c7:dc:38:8f:c7:bb:70:de:d4:39:c3:39:87 hadoop@promote.cache-dns.local The key's randomart image is: +--[ RSA 2048]----+ | | | | | | | | | S | | o o o o + | | o B.= o E . | | . o Oo+ = | | o.=o. | +-----------------+ |
最终将在/home/hadoop/.ssh/路径下生成私钥id_rsa和公钥id_rsa.pub
[hadoop@promote .ssh]$ cd /home/hadoop/.ssh/
[hadoop@promote .ssh]$ ls
id_rsa id_rsa.pub
修改sshd服务配置文件:
[hadoop@promote .ssh]$ su root
密码:
[root@promote .ssh]# vi /etc/ssh/sshd_config
启用RSA加密算法验证(去掉前面的#号)
RSAAuthentication yes PubkeyAuthentication yes # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 # but this is overridden so installations will only check .ssh/authorized_keys AuthorizedKeysFile .ssh/authorized_keys |
修改mapred-site.xml:
<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>mapred.job.tracker</name> <value>localhost:9001</value> </property> </configuration> |
修改hdfs-site.xml:
<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration> |
master中指定的SNN节点和slaves中指定的从节点位置均为本地
[hadoop@promote conf]$ cat masters localhost [hadoop@promote conf]$ cat slaves localhost |
第五步:启动Hadoop
[hadoop@promote bin]$ cd ../conf/ [hadoop@promote conf]$ cd ../bin [hadoop@promote bin]$ sh start-all.sh starting namenode, logging to /home/hadoop/hadoop-1.2.1/libexec/../logs/hadoop-hadoop-namenode-promote.cache-dns.local.out localhost: starting datanode, logging to /home/hadoop/hadoop-1.2.1/libexec/../logs/hadoop-hadoop-datanode-promote.cache-dns.local.out localhost: starting secondarynamenode, logging to /home/hadoop/hadoop-1.2.1/libexec/../logs/hadoop-hadoop-secondarynamenode-promote.cache-dns.local.out starting jobtracker, logging to /home/hadoop/hadoop-1.2.1/libexec/../logs/hadoop-hadoop-jobtracker-promote.cache-dns.local.out localhost: starting tasktracker, logging to /home/hadoop/hadoop-1.2.1/libexec/../logs/hadoop-hadoop-tasktracker-promote.cache-dns.local.out |
可以看到所有Hadoop守护进程均已启动
保存并退出,然后重启sshd服务
[root@promote .ssh]# service sshd restart Redirecting to /bin/systemctl restart sshd.service [root@promote .ssh]# ps -ef|grep sshd root 1995 1 0 22:33 ? 00:00:00 sshd: hadoop [priv] hadoop 2009 1995 0 22:33 ? 00:00:00 sshd: hadoop@pts/0 root 4171 1 0 23:11 ? 00:00:00 /usr/sbin/sshd -D root 4175 3397 0 23:12 pts/0 00:00:00 grep --color=auto sshd |
然后切换回hadoop用户,将ssh证书公钥拷贝至/home/hadoop/.ssh/authorized_keys文件中
[root@promote .ssh]# su hadoop
[hadoop@promote .ssh]$ cat id_rsa.pub >> authorized_keys
修改authorized_keys文件的权限为644(这步一定要有)
[hadoop@promote .ssh]$ chmod 644 authorized_keys
[hadoop@promote .ssh]$ ssh localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
RSA key fingerprint is 25:1f:be:72:7b:83:8e:c7:96:b6:71:35:fc:5d:2e:7d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
Last login: Thu Feb 13 23:42:43 2014
第一次登陆将会将证书内容保存在/home/hadoop/.ssh/known_hosts文件中,以后再次登陆将不需要输入密码
[hadoop@promote .ssh]$ ssh localhost
Last login: Thu Feb 13 23:46:04 2014 from localhost.localdomain
至此ssh证书部分配置完成
第二步:安装JDK
[hadoop@promote ~]$ java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (fedora-2.3.10.3.fc19-i386)
OpenJDK Client VM (build 23.7-b01, mixed mode)
将OpenJDK换为Oracle的Java SE
[hadoop@promote .ssh]$ cd ~
[hadoop@promote ~]$ uname -i
i386
在Oracle的官网下载jdk-6u45-linux-i586.bin后上传至服务器,赋予权限并进行安装,最后删除安装包
[hadoop@promote ~]$ chmod u+x jdk-6u45-linux-i586.bin
[hadoop@promote ~]$ ./jdk-6u45-linux-i586.bin
[hadoop@promote ~]$ rm -rf jdk-6u45-linux-i586.bin
[hadoop@promote conf]$ export PATH=$PATH:/home/hadoop/jdk1.6.0_45/bin
出现以下结果说明JDK成功安装:
[hadoop@promote ~]$ java -version
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) Client VM (build 20.45-b01, mixed mode, sharing)
第三步:安装Hadoop
在Hadoop官网下载hadoop-1.2.1.tar.gz并上传至服务器/home/hadoop路径下
[hadoop@promote ~]$ tar -xzf hadoop-1.2.1.tar.gz
[hadoop@promote ~]$ rm -rf hadoop-1.2.1.tar.gz
[hadoop@promote ~]$ cd hadoop-1.2.1/conf/
[hadoop@promote conf]$ vi hadoop-env.sh
将JAVA_HOME指向第二步安装的JDK所在目录片
# The java implementation to use. Required.
export JAVA_HOME=/home/hadoop/jdk1.6.0_45
保存并退出
第四步:修改Hadoop配置文件
修改core-site.xml:
<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>fs.default.name</name> <value>hdfs://localhost:9000</value> </property> </configuration> |