2016年8月18日 随笔档案 -凯发k8网页登录

那些青春的岁月

  凯发k8网页登录-凯发天生赢家一触即发官网 :: 凯发k8网页登录首页 :: 联系 :: 聚合  :: 管理
  500 posts :: 0 stories :: 135 comments :: 0 trackbacks

2016年8月18日 #

     摘要: 前阵子从支付宝转账1万块钱到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加1万,数据就会出现不一致状况了。上述场景在各个类型的系统中都能找到相似影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量必须减1吧,怎么保证?!在搜索广告系统中,当用户点击某广告后,除了在点击...  阅读全文
posted @ 2018-01-04 00:01 abin 阅读(530) | 评论 (0)编辑 收藏

微服务架构采用scale cube方法设计应用架构,将应用服务按功能拆分成一组相互协作的服务。每个服务负责一组特定、相关的功能。每个服务可以有自己独立的数据库,从而保证与其他服务解耦。
微服务优点
1、通过分解巨大单体式应用为多个服务方法解决了复杂性问题,每个微服务相对较小
2、每个单体应用不局限于固定的技术栈,开发者可以自由选择开发技术,提供api服务。
3、每个微服务独立的开发,部署
4、单一职责功能,每个服务都很简单,只关注于一个业务功能
5、易于规模化开发,多个开发团队可以并行开发,每个团队负责一项服务
6、改善故障隔离。一个服务宕机不会影响其他的服务
微服务缺点:
1.开发者需要应对创建分布式系统所产生的额外的复杂因素
l  目前的ide主要面对的是单体工程程序,无法显示支持分布式应用的开发
l  测试工作更加困难
l  需要采用服务间的通讯机制
l  很难在不采用分布式事务的情况下跨服务实现功能
l  跨服务实现要求功能要求团队之间的紧密协作
2.部署复杂
3.内存占用量更高
posted @ 2017-12-31 16:41 abin 阅读(249) | 评论 (0)编辑 收藏

jdk 的 hashmap 中使用了一个 hash 方法来做 bit shifting,在注释中说明是为了防止一些实现比较差的hashcode() 方法,请问原理是什么?jdk 的源码参见:grepcode: java.util.hashmap (.java)
/**
 * applies a supplemental hash function to a given hashcode, which
 * defends against poor quality hash functions.  this is critical
 * because hashmap uses power-of-two length hash tables, that
 * otherwise encounter collisions for hashcodes that do not differ
 * in lower bits. note: null keys always map to hash 0, thus index 0.
 */
static int hash(int h) {
    // this function ensures that hashcodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
ps:网上看见有人说作者本人说原理需要参见圣经《计算机程序设计艺术》的 vol.3 里头的介绍,不过木有看过神书,求达人介绍





这段代码叫“扰动函数”。
题主贴的是java 7的hashmap的源码,java 8中这步已经简化了,只做一次16位右位移异或混合,而不是四次,但原理是不变的。下面以java 8的源码为例解释,

//java 8中的散列值优化函数staticfinalinthash(objectkey){inth;return(key==null)?0:(h=key.hashcode())^(h>>>16);//key.hashcode()为哈希算法,返回初始哈希值}
大家都知道上面代码里的key.hashcode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。理论上散列值是一个int型,如果直接拿散列值作为下标访问hashmap主数组的话,考虑到2进制32位带符号的int表值范围从-2147483648到2147483648。前后加起来大概40亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。你想,hashmap扩容之前的数组初始大小才16。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来访问数组下标。源码中模运算是在这个indexfor( )函数里完成的。

bucketindex = indexfor(hash, table.length);indexfor的代码也很简单,就是把散列值和数组长度做一个"与"操作,

static int indexfor(int h, int length) {        return h & (length-1);}顺便说一下,这也正好解释了为什么hashmap的数组长度要取2的整次幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。
10100101 11000100 00100101& 00000000 00000000 00001111---------------------------------- 00000000 00000000 00000101    //高位全部归零,只保留末四位
但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,就无比蛋疼。这时候“扰动函数”的价值就体现出来了,说到这里大家应该猜出来了。看下面这个图,


右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。最后我们来看一下peterlawley的一篇专栏文章《an introduction to optimising a hashing strategy》里的的一个实验:他随机选取了352个字符串,在他们散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标。


结果显示,当hashmap数组长度为512的时候,也就是用掩码取低9位的时候,在没有扰动函数的情况下,发生了103次碰撞,接近30%。而在使用了扰动函数之后只有92次碰撞。碰撞减少了将近10%。看来扰动函数确实还是有功效的。但明显java 8觉得扰动做一次就够了,做4次的话,多了可能边际效用也不大,所谓为了效率考虑就改成一次了。
------------------------------------------------------








https://www.zhihu.com/question/20733617



posted @ 2017-12-24 22:38 abin 阅读(300) | 评论 (0)编辑 收藏

go语言没有沿袭传统面向对象编程中的诸多概念,比如继承、虚函数、构造函数和析构函数、隐藏的this指针等。

 

方法

go 语言中同时有函数和方法。方法就是一个包含了接受者(receiver)的函数,receiver可以是内置类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。

如下面的这个例子,定义了一个新类型integer,它和int一样,只是为它内置的int类型增加了个新方法less()

type integer int   func (a integer) less(b integer) bool {     return a < b  }  func main() {     var a integer = 1       if a.less(2) {         fmt.println("less then 2")     }    }

可以看出,go语言在自定义类型的对象中没有c /java那种隐藏的this指针,而是在定义成员方法时显式声明了其所属的对象。

 

method的语法如下:

func (r receivertype) funcname(parameters) (results)

当调用method时,会将receiver作为函数的第一个参数:

funcname(r, parameters);

所以,receiver是值类型还是指针类型要看method的作用。如果要修改对象的值,就需要传递对象的指针。

指针作为receiver会对实例对象的内容发生操作,而普通类型作为receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。

func (a *ingeger) add(b integer) {     *a  = b }  func main() {     var a integer = 1      a.add(3)     fmt.println("a =", a)     //  a = 4 }

如果add方法不使用指针,则a返回的结果不变,这是因为go语言函数的参数也是基于值传递。

注意:当方法的接受者是指针时,即使用值类型调用那么方法内部也是对指针的操作。

 

之前说过,go语言没有构造函数的概念,通常使用一个全局函数来完成。例如:

func newrect(x, y, width, height float64) *rect {     return &rect{x, y, width, height} }     func main() {     rect1 := newrect(1,2,10,20)     fmt.println(rect1.width) }

 

 


匿名组合

go语言提供了继承,但是采用了组合的语法,我们将其称为匿名组合,例如:

type base struct {     name string }  func (base *base) set(myname string) {     base.name = myname }  func (base *base) get() string {     return base.name }  type derived struct {     base     age int  }  func (derived *derived) get() (nm string, ag int) {     return derived.name, derived.age }   func main() {     b := &derived{}      b.set("sina")     fmt.println(b.get()) }

例子中,在base类型定义了get()和set()两个方法,而derived类型继承了base类,并改写了get()方法,在derived对象调用set()方法,会加载基类对应的方法;而调用get()方法时,加载派生类改写的方法。

 

组合的类型和被组合的类型包含同名成员时, 会不会有问题呢?可以参考下面的例子:

type base struct {     name string     age int }  func (base *base) set(myname string, myage int) {     base.name = myname     base.age = myage }  type derived struct {     base     name string }  func main() {     b := &derived{}      b.set("sina", 30)     fmt.println("b.name =",b.name, "\tb.base.name =", b.base.name)     fmt.println("b.age =",b.age, "\tb.base.age =", b.base.age) }

 

 

 


值语义和引用语义

值语义和引用语义的差别在于赋值,比如

b = a b.modify()

如果b的修改不会影响a的值,那么此类型属于值类型;如果会影响a的值,那么此类型是引用类型。

go语言中的大多数类型都基于值语义,包括:

  • 基本类型,如byte、int、bool、float32、string等;
  • 复合类型,如arry、struct、pointer等;

 

c语言中的数组比较特别,通过函数传递一个数组的时候基于引用语义,但是在结构体定义数组变量的时候基于值语义。而在go语言中,数组和基本类型没有区别,是很纯粹的值类型,例如:

var a = [3] int{1,2,3} var b = a b[1]   fmt.println(a, b)   // [1 2 3] [1 3 3]

从结果看,b=a赋值语句是数组内容的完整复制,要想表达引用,需要用指针:

var a = [3] int{1,2,3} var b = &a    // 引用语义 b[1]   fmt.println(a, b)   // [1 3 3] [1 3 3]

 

 


接口

interface 是一组抽象方法(未具体实现的方法/仅包含方法名参数返回值的方法)的集合,如果实现了 interface 中的所有方法,即该类/对象就实现了该接口。

interface 的声明格式:

type interfacename interface {       //方法列表   }  

interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface;
interface的变量可以持有任意实现该interface类型的对象。

 如下面的例子:

package main      import "fmt"      type human struct {         name string         age int         phone string     }      type student struct {         human //匿名字段         school string         loan float32     }      type employee struct {         human //匿名字段         company string         money float32     }      //human实现sayhi方法     func (h human) sayhi() {         fmt.printf("hi, i am %s you can call me on %s\n", h.name, h.phone)     }      //human实现sing方法     func (h human) sing(lyrics string) {         fmt.println("la la la la...", lyrics)     }      //employee重载human的sayhi方法     func (e employee) sayhi() {         fmt.printf("hi, i am %s, i work at %s. call me on %s\n", e.name,             e.company, e.phone)         }      // interface men被human,student和employee实现     // 因为这三个类型都实现了这两个方法     type men interface {         sayhi()         sing(lyrics string)     }      func main() {         mike := student{human{"mike", 25, "222-222-xxx"}, "mit", 0.00}         paul := student{human{"paul", 26, "111-222-xxx"}, "harvard", 100}         sam := employee{human{"sam", 36, "444-222-xxx"}, "golang inc.", 1000}         tom := employee{human{"tom", 37, "222-444-xxx"}, "things ltd.", 5000}          //定义men类型的变量i         var i men          //i能存储student         i = mike             fmt.println("this is mike, a student:")         i.sayhi()         i.sing("november rain")          //i也能存储employee         i = tom         fmt.println("this is tom, an employee:")         i.sayhi()         i.sing("born to be wild")          //定义了slice men         fmt.println("let's use a slice of men and see what happens")         x := make([]men, 3)         //这三个都是不同类型的元素,但是他们实现了interface同一个接口         x[0], x[1], x[2] = paul, sam, mike          for _, value := range x{             value.sayhi()         }     }

 

空接口

空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于c语言的void*类型。

// 定义a为空接口     var a interface{}     var i int = 5     s := "hello world"     // a可以存储任意类型的数值     a = i     a = s

 

interface的变量里面可以存储任意类型的数值(该类型实现了interface),那么我们怎么反向知道这个interface变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:switch测试、comma-ok断言。

 

switch测试如下:

type element interface{} type list [] element  type person struct {     name string     age int  }  //打印 func (p person) string() string {     return "(name: "   p.name   " - age: " strconv.itoa(p.age)  " years)" }  func main() {     list := make(list, 3)     list[0] = 1 //an int      list[1] = "hello" //a string     list[2] = person{"dennis", 70}       for index, element := range list{         switch value := element.(type) {             case int:                 fmt.printf("list[%d] is an int and its value is %d\n", index, value)             case string:                 fmt.printf("list[%d] is a string and its value is %s\n", index, value)             case person:                 fmt.printf("list[%d] is a person and its value is %s\n", index, value)             default:                 fmt.println("list[%d] is of a different type", index)         }        }    }

 

如果使用comma-ok断言的话:

func main() {     list := make(list, 3)     list[0] = 1 // an int     list[1] = "hello" // a string     list[2] = person{"dennis", 70}      for index, element := range list {         if value, ok := element.(int); ok {             fmt.printf("list[%d] is an int and its value is %d\n", index, value)         } else if value, ok := element.(string); ok {             fmt.printf("list[%d] is a string and its value is %s\n", index, value)         } else if value, ok := element.(person); ok {             fmt.printf("list[%d] is a person and its value is %s\n", index, value)         } else {             fmt.printf("list[%d] is of a different type\n", index)         }     } }

 

 

嵌入接口

正如struct类型可以包含一个匿名字段,interface也可以嵌套另外一个接口。

如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。

 

 

反射

所谓反射(reflect)就是能检查程序在运行时的状态。

使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.type或者reflect.value,根据不同的情况调用不同的函数)。这两种获取方式如下:

 t := reflect.typeof(i)    //得到类型的元数据,通过t我们能获取类型定义里面的所有元素  v := reflect.valueof(i)   //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值

 

转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如

tag := t.elem().field(0).tag  //获取定义在struct里面的标签 name := v.elem().field(0).string()  //获取存储在第一个字段里面的值

 

获取反射值能返回相应的类型和数值

var x float64 = 3.4 v := reflect.valueof(x) fmt.println("type:", v.type()) fmt.println("kind is float64:", v.kind() == reflect.float64) fmt.println("value:", v.float())

 

最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误

var x float64 = 3.4 v := reflect.valueof(x) v.setfloat(7.1)

 

如果要修改相应的值,必须这样写

var x float64 = 3.4 p := reflect.valueof(&x) v := p.elem() v.setfloat(7.1)

上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。

 

 

参考文档:

 http://www.cnblogs.com/chenny7/p/4497969.html





posted @ 2017-08-03 11:34 abin 阅读(266) | 评论 (0)编辑 收藏

不可或缺的函数,在go中定义函数的方式如下:

func (p mytype ) funcname ( a, b int , c string ) ( r , s int ) {     return } 

通过函数定义,我们可以看到go中函数和其他语言中的共性和特性

共性

  • 关键字——func
  • 方法名——funcname
  • 入参——— a,b int,b string
  • 返回值—— r,s int
  • 函数体—— {}

特性

go中函数的特性是非常酷的,给我们带来不一样的编程体验。

为特定类型定义函数,即为类型对象定义方法

在go中通过给函数标明所属类型,来给该类型定义方法,上面的 p mytype 即表示给mytype声明了一个方法, p mytype 不是必须的。如果没有,则纯粹是一个函数,通过包名称访问。packagename.funcationname

如:

//定义新的类型double,主要目的是给float64类型扩充方法 type double float64  //判断a是否等于b func (a double) isequal(b double) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  //判断a是否等于b func isequal(a, b float64) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  func main() {     var a double = 1.999999     var b double = 1.9999998     fmt.println(a.isequal(b))     fmt.println(a.isequal(3))     fmt.println( isequal( (float64)(a), (float64)(b) ) )  } 

上述示例为 float64 基本类型扩充了方法isequal,该方法主要是解决精度问题。 其方法调用方式为: a.isequal(double) ,如果不扩充方法,我们只能使用函数isequal(a, b float64)

入参中,如果连续的参数类型一致,则可以省略连续多个参数的类型,只保留最后一个类型声明。

如 func isequal(a, b float64) bool 这个方法就只保留了一个类型声明,此时入参a和b均是float64数据类型。 这样也是可以的: func isequal(a, b float64, accuracy int) bool

变参:入参支持变参,即可接受不确定数量的同一类型的参数

如 func sum(args ...int) 参数args是的slice,其元素类型为int 。经常使用的fmt.printf就是一个接受任意个数参数的函数 fmt.printf(format string, args ...interface{})

支持多返回值

前面我们定义函数时返回值有两个r,s 。这是非常有用的,我在写c#代码时,常常为了从已有函数中获得更多的信息,需要修改函数签名,使用out ,ref 等方式去获得更多返回结果。而现在使用go时则很简单,直接在返回值后面添加返回参数即可。

如,在c#中一个字符串转换为int类型时逻辑代码

int v=0;  if ( int.trypase("123456",out v) ) {     //code } 

而在go中,则可以这样实现,逻辑精简而明确

if v,isok :=int.trypase("123456") ; isok {     //code } 

同时在go中很多函数充分利用了多返回值

  • func (file *file) write(b []byte) (n int, err error)
  • func sincos(x float64) (sin, cos float64)

那么如果我只需要某一个返回值,而不关心其他返回值的话,我该如何办呢? 这时可以简单的使用符号下划线”_“ 来忽略不关心的返回值。如:

_, cos = math.sincos(3.1415) //只需要cos计算的值 

命名返回值

前面我们说了函数可以有多个返回值,这里我还要说的是,在函数定义时可以给所有的返回值分别命名,这样就能在函数中任意位置给不同返回值复制,而不需要在return语句中才指定返回值。同时也能增强可读性,也提高godoc所生成文档的可读性

如果不支持命名返回值,我可能会是这样做的

func readfull(r reader, buf []byte) (int, error) {     var n int     var err error      for len(buf) > 0  {         var nr int         nr, err = r.read(buf)          n  = nr         if err !=nil {             return n,err         }         buf = buf[nr:]     }     return n,err } 

但支持给返回值命名后,实际上就是省略了变量的声明,return时无需写成return n,err 而是将直接将值返回

func readfull(r reader, buf []byte) (n int, err error) {     for len(buf) > 0 && err == nil {         var nr int         nr, err = r.read(buf)         n  = nr         buf = buf[nr:]     }     return } 

函数也是“值”

和go中其他东西一样,函数也是值,这样就可以声明一个函数类型的变量,将函数作为参数传递。

声明函数为值的变量(匿名函数:可赋值个变量,也可直接执行)

//赋值 fc := func(msg string) {     fmt.println("you say :", msg) } fmt.printf("%t \n", fc) fc("hello,my love") //直接执行 func(msg string) {     fmt.println("say :", msg) }("i love to code") 

输出结果如下,这里表明fc 的类型为:func(string)

func(string)  you say : hello,my love say : i love to code 

将函数作为入参(回调函数),能带来便利。如日志处理,为了统一处理,将信息均通过指定函数去记录日志,且是否记录日志还有开关

func log(title string, getmsg func() string) {     //如果开启日志记录,则记录日志     if true {         fmt.println(title, ":", getmsg())     } } //---------调用-------------- count := 0 msg := func() string {     count       return "您没有即使提醒我,已触犯法律" } log("error", msg) log("warring", msg) log("info", msg) fmt.println(count) 

这里输出结果如下,count 也发生了变化

error : 您没有即使提醒我,已触犯法律 warring : 您没有即使提醒我,已触犯法律 info : 您没有即使提醒我,已触犯法律 3 

函数也是“类型”

你有没有注意到上面示例中的 fc := func(msg string)... ,既然匿名函数可以赋值给一个变量,同时我们经常这样给int赋值 value := 2 ,是否我们可以声明func(string) 类型 呢,当然是可以的。

//一个记录日志的类型:func(string) type savelog func(msg string)  //将字符串转换为int64,如果转换失败调用savelog func stringtoint(s string, log savelog) int64 {      if value, err := strconv.parseint(s, 0, 0); err != nil {         log(err.error())         return 0     } else {         return value     } }  //记录日志消息的具体实现 func mylog(msg string) {     fmt.println("find error:", msg) }  func main() {     stringtoint("123", mylog) //转换时将调用mylog记录日志     stringtoint("s", mylog) } 

这里我们定义了一个类型,专门用作记录日志的标准接口。在stringtoint函数中如果转换失败则调用我自己定义的接口函数进行日志处理,至于最终执行的哪个函数,则无需关心。

defer 延迟函数

defer 又是一个创新,它的作用是:延迟执行,在声明时不会立即执行,而是在函数return后时按照后进先出的原则依次执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行到,这样就能做很多我们想做的事,如释放资源,清理数据,记录日志等

这里我们重点来说明下defer的执行顺序

func deferfunc() int {     index := 0      fc := func() {          fmt.println(index, "匿名函数1")         index            defer func() {             fmt.println(index, "匿名函数1-1")             index           }()     }      defer func() {         fmt.println(index, "匿名函数2")         index       }()      defer fc()      return func() int {         fmt.println(index, "匿名函数3")         index           return index     }() }  func main() {     deferfunc() } 

这里输出结果如下,

0 匿名函数3 1 匿名函数1 2 匿名函数1-1 3 匿名函数2 

有如下结论:

  • defer 是在执行完return 后执行
  • defer 后进先执行

另外,我们常使用defer去关闭io,在正常打开文件后,就立刻声明一个defer,这样就不会忘记关闭文件,也能保证在出现异常等不可预料的情况下也能关闭文件。而不像其他语言:try-catch 或者 using() 方式进行处理。

file , err :=os.open(file) if err != nil {     return err } defer file.close()  //dosomething with file 

后续,我将讨论: 作用域、传值和传指针 以及 保留函数init(),main()

本笔记中所写代码存储位置:

    posted @ 2017-08-02 16:39 abin 阅读(421) | 评论 (0)编辑 收藏

    mysql触发器trigger实例篇
    发表于668 天前 ⁄  ⁄ 

    以前关注的数据存储过程不太懂其中奥妙,最近遇到跨,同时对多个表进行curd(create增、update改、read读、delete删),怎么才能让繁琐的数据curd同步变得更容易呢?相信很多人会首先想到了存储过程、触发器,这种想法确实不错。于是饶有兴趣地亲自写了cud(增、改、删)触发器的实例,用触发器实现多表数据同步更新。

    定义: 何为mysql触发器?

    在mysql server里面也就是对某一个表的一定的操作,触发某种条件(insert,update,delete 等),从而自动执行的一段程序。从这种意义上讲触发器是一个特殊的存储过程。下面通过mysql触发器实例,来了解一下触发器的工作过程吧!

    一、创建mysql实例数据表:

    在mysql的默认的测试test数据库下,创建两个表t_a与t_b:



        /*table structure for table `t_a` */
        drop table if exists `t_a`;
        create table `t_a` (
          `id` smallint(1) unsigned not null auto_increment,
          `username` varchar(20) default null,
          `groupid` mediumint(8) unsigned not null default '0',
          primary key (`id`)
        ) engine=myisam auto_increment=16 default charset=latin1;
         
        /*data for the table `t_a` */
        lock tables `t_a` write;
        unlock tables;
         
        /*table structure for table `t_b` */
        drop table if exists `t_b`;
        create table `t_b` (
          `id` smallint(1) unsigned not null auto_increment,
          `username` varchar(20) default null,
          `groupid` mediumint(8) unsigned not null default '0',
          primary key (`id`)
        ) engine=myisam auto_increment=57 default charset=latin1;
         
        /*data for the table `t_b` */
        lock tables `t_b` write;
        unlock tables;

    在t_a表上分创建一个cud(增、改、删)3个触发器,将t_a的表数据与t_b同步实现cud,注意创建触发器每个表同类事件有且仅有一个对应触发器,为什么只能对一个触发器,不解释啦,看mysql的说明帮助文档吧。

    二、创建mysql实例触发器:

    在实例数据表t_a上依次按照下面步骤创建tr_a_insert、tr_a_update、tr_a_delete三个触发器

    1、创建insert触发器trigger_a_insert:



        delimiter $$
         
        use `test`$$
         
        --判断数据库中是否存在tr_a_insert触发器
        drop trigger /*!50032 if exists */ `tr_a_insert`$$
        --不存在tr_a_insert触发器,开始创建触发器
        --trigger触发条件为insert成功后进行触发
        create
            /*!50017 definer = 'root'@'localhost' */
            trigger `tr_a_insert` after insert on `t_a`
            for each row begin
                --trigger触发后,同时对t_b新增同步一条数据
                insert into `t_b` set username = new.username, groupid=new.groupid;
            end;
        $$
         
        delimiter;
    2、创建update触发器trigger_a_update:


        delimiter $$
         
        use `test`$$
        --判断数据库中是否存在tr_a_update触发器
        drop trigger /*!50032 if exists */ `tr_a_update`$$
        --不存在tr_a_update触发器,开始创建触发器
        --trigger触发条件为update成功后进行触发
        create
            /*!50017 definer = 'root'@'localhost' */
            trigger `tr_a_update` after update on `t_a`
            for each row begin
            --trigger触发后,当t_a表groupid,username数据有更改时,对t_b表同步一条更新后的数据
              if new.groupid != old.groupid or old.username != new.username then
                update `t_b` set groupid=new.groupid,username=new.username whereusername=old.username and groupid=old.groupid;
              end if;
                  
            end;
        $$
         
        delimiter ;
    3、创建delete触发器trigger_a_delete:


        delimiter $$
         
        use `test`$$
        --判断数据库中是否存在tr_a_delete触发器
        drop trigger /*!50032 if exists */ `tr_a_delete`$$
        --不存在tr_a_delete触发器,开始创建触发器
        --trigger触发条件为delete成功后进行触发
        create
            /*!50017 definer = 'root'@'localhost' */
            trigger `tr_a_delete` after delete on `t_a`
            for each row begin
                --t_a表数据删除后,t_b表关联条件相同的数据也同步删除
                delete from `t_b` where username=old.username and groupid=old.groupid;
            end;
        $$
         
        delimiter ;

    三、测试mysql实例触发器:

    分别测试实现t_a与t_b实现数据同步cud(增、改、删)3个triggers

    1、测试mysql的实例tr_a_insert触发器:

    在t_a表中新增一条数据,然后分别查询t_a/t_b表的数据是否数据同步,测试触发器成功标志,t_a表无论在何种情况下,新增了一条或多条记录集时,没有t_b表做任何数据insert操作,它同时新增了一样的多条记录集。

    下面来进行mysql触发器实例测试:



        --t_a表新增一条记录集
            insert into `t_a` (username,groupid) values ('sky54.net',123)
           
            --查询t_a表
            select id,username,groupid from `t_a`
           
            --查询t_b表
            select id,username,groupid from `t_b`

    2、测试mysql的实例tr_a_update、tr_a_delete触发器:

    这两个mysql触发器测试原理、步骤与tr_a_insert触发器一样的,先修改/删除一条数据,然后分别查看t_a、t_b表的数据变化情况,数据变化同步说明trigger实例成功,否则需要逐步排查错误原因。

    世界上任何一种事物都其其优点和缺点,优点与缺点是自身一个相对立的面。当然这里不是强调“世界非黑即白”式的“二元论”,“存在即合理”嘛。当然 mysql触发器的优点不说了,说一下不足之处,mysql trigger没有很好的调试、管理环境,难于在各种系统环境下测试,测试比mysql存储过程要难,所以建议在生成环境下,尽量用存储过程来代替 mysql触发器。

    本篇结束前再强调一下,支持触发器的mysql版本需要5.0以上,5.0以前版本的mysql升级到5.0以后版本方可使用触发器哦!








    http://blog.csdn.net/hireboy/article/details/18079183



    posted @ 2016-08-18 17:25 abin 阅读(796) | 评论 (0)编辑 收藏

    网站地图