------------数据库的加锁操作(上)
从事一个项目,需要考虑数据的安全性,之前对于数据库这部分的数据操作学习的比较零散,由于手头的项目,于是系统的
学习了下数据库操作加锁的知识:
--------------------------------------------------华丽丽的分割线-----------------------------------------------------------
学习一个知识,我们大致都会经历这么几个过程(what this ? why to use ? how to use?),首先,我们需要搞懂,下面几个知识点:
一: 什么是数据库加锁 ?
数据库加锁: 简单的意思就是对于在执行一个操作(比如修改)时,对这个操作的对象加锁,放置其他操作读取到脏数据或者幽灵数据。
或者术语来说就是一种排他锁,当写的时候不允许其他程序写,这样就可以保证数据一致性了
二:为什么要给数据加锁?
对于这点,我们需要简单的了解几个概念:
(1).什么是事务?
事务: 是用户定义的数据库操作系列,这些操作作为一个完整的工作单元执行。一个事务内的所有语句作为一个整体。要么全部执行,要么全部不执行。
事务的几个特点: 原子性,一致性,隔离性,持久性, 简称ACID特征
一般来讲: 事务的这几个特点被遭到破坏的有下列几个情况:
(1) 多事务并行运行时,不同事务的操作有交叉情况。(->_-> 如果你想不到列子:就想想多线程问题)
(2) 事务在运行过程中被强迫停止。
(2)什么是脏读:
脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
(3)什么是不可重复读?
不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
(4)什么是幻读?
幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
因为在上述的情况下,数据会出现脏数据。对于一个考虑安全性的系统而言,加锁自然是十分必要.
(三)如何对数据加锁:
对于数据加锁: 一般分为如下两种,第一类,就是数据库自己加锁,第二类,就是线程锁。
第一种: 数据库自己加锁
对于锁的级别: 库级锁,表级锁,页级锁,行级锁。(这篇文章提供了较多的说明)
http://blog.csdn.net/cspyb/article/details/1865538
举几个列子:对于数据表的加锁:
方法一: 使用SQL语句进行加锁
public void test() { String sql = "lock tables Gxjun write"; // 或String sql = "lock tables Gxjun read"; // 如果想锁多个表 lock tables Gxjun read stu write , ..... String sql1 = "select * from Gxjun "; String sql2 = "unlock tables"; try { this.pstmt = conn.prepareStatement(sql); this.pstmt1 = conn.prepareStatement(sql1); this.pstmt2 = conn.prepareStatement(sql2); pstmt.executeQuery(); pstmt1.executeQuery(); pstmt2.executeQuery(); } catch (Exception e) { System.out.println("异常" + e.getMessage()); } }
方法二 , 采用记录锁加锁:
public void test() { String sql = "select * from Gxjun for update"; try { conn.setAutoCommit(false); this.pstmt = conn.prepareStatement(sql); pstmt.executeQuery(); } catch (Exception e) { System.out.println("异常" + e.getMessage()); } }
需要标注的几点就是:(摘录自思绪飞帆,鸣谢----思绪飞帆)
/* 1.for update 与 lock in share mode 属于行级锁和页级锁 2.for update 排它锁,lock in share mode 共享锁 3.对于记录锁.必须开启事务. 4.行级锁定事实上是索引记录的锁定.只要是用索引扫描的行(或没索引全表扫描的行),都将被锁住. 5.在不同的隔离级别下还会使用next-key locking算法.即所扫描的行之间的“间隙”也会也锁住(在Repeatable read和Serializable隔离级别下有间隙锁). 6.在mysql中共享锁的含义是:在被共享锁锁住的行,即使内容被修改且并没有提交.在另一个会话中依然看到最新修改的信息. 在同一会话中加上了共享锁.可以对这个表以及这个表以外的所有表进行增、删、改、查的操作. 在不同的会话中.可以查到共享锁锁住行的最新消息.但是在Read Uncommitted隔离级别下不能对锁住的表进行删, 改操作.(需要等待锁释放才能操作...) 在Read Committed隔离级别下不能对锁住的表进行删,改操作.(需要等待锁释放才能操作...) 在Repeatable read隔离级别下不能对锁住行进行增、删、改操作.(需要等待锁释放才能操作...) 在Serializable隔离级别下不能对锁住行进行增、删、改操作. (需要等待锁释放才能操作...) 7.在mysql中排他锁的含义是:在被排它锁锁住的行,内容修改并没提交,在另一个会话中不会看到最新修改的信息。 在不同的会话中.可以查到共享锁锁住行的最新消息.但是Read Uncommitted隔离级别下不能对锁住的表进行删, 改操作.(需要等待锁释放才能操作...) 在Read Committed隔离级别下不能对锁住的表进行删,改操作.(需要等待锁释放才能操作...) 在Repeatable read隔离级别下不能对锁住行进行增、删、改操作.(需要等待锁释放才能操作...) 在Serializable隔离级别下不能对锁住行进行增、删、改操作. (需要等待锁释放才能操作...) 8.在同一个会话中的可以叠加多个共享锁和排他锁.在多个会话中,需要等待锁的释放. 9.SQL中的update 与 for update是一样的原理. 10.等待超时的参数设置:innodb_lock_wait_timeout=50 (单位秒). 11.任何可以触发事务提交的命令,都可以关闭共享锁和排它锁. */
第二种: 就是线程锁,这是我重点学习的地方,(额,需呀注解的是,是我重点学习的地方,然后每个人注重点不同)
引用到的材料:
1. http://lavasoft.blog.51cto.com/62575/99155
2.http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html
3. http://www.blogjava.net/zhangwei217245/archive/2010/04/08/315526.html
-------------------------------------------------------------------------------------------------------------------------------------- 鸣谢上述作者
0-----------------------------------------------------------------华丽丽的分割线----------------------------------------------------------------------0
举列子:
在不采用同步锁的情况下
1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步锁 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加载线程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public int getX() { 27 return x; 28 } 29 30 public int fix(int y) { 31 x = x - y; 32 return x; 33 } 34 } 35 36 class MyThread implements Runnable { 37 private Foo foo = new Foo(); 38 39 public void run() { 40 for (int i = 0; i < 3; i++) { 41 this.fix(30); 42 try { 43 Thread.sleep(1); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX()); 48 } 49 } 50 51 public int fix(int y) { 52 return foo.fix(y); 53 } 54 }
结果为:
王小二 : 当前foo对象的x值= 40
王小三 : 当前foo对象的x值= 40
王小二 : 当前foo对象的x值= -20
王小三 : 当前foo对象的x值= -50
王小二 : 当前foo对象的x值= -80
王小三 : 当前foo对象的x值= -80
但是对于上述问题,采用线程池,却能得到完满的解决。
将其改成同步锁之后:
1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步锁 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加载线程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public int getX() { 27 //改为同步锁 28 synchronized (this) { 29 return x; 30 } 31 } 32 33 public int fix(int y) { 34 x = x - y; 35 return x; 36 } 37 } 38 39 class MyThread implements Runnable { 40 private Foo foo = new Foo(); 41 42 public void run() { 43 for (int i = 0; i < 3; i++) { 44 this.fix(30); 45 try { 46 Thread.sleep(1); 47 } catch (InterruptedException e) { 48 e.printStackTrace(); 49 } 50 System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX()); 51 } 52 } 53 54 public int fix(int y) { 55 return foo.fix(y); 56 } 57 }
结果:
王小二 : 当前foo对象的x值= 40
王小三 : 当前foo对象的x值= 40
王小三 : 当前foo对象的x值= -20
王小二 : 当前foo对象的x值= -20
王小三 : 当前foo对象的x值= -80
王小二 : 当前foo对象的x值= -80
这个结果和采用线程池得到结果是一样的........
采用非同步锁结果也是一样的.....
1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步锁 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加载线程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public synchronized int getX() { 27 return x; 28 } 29 30 public int fix(int y) { 31 x = x - y; 32 return x; 33 } 34 } 35 36 class MyThread implements Runnable { 37 private Foo foo = new Foo(); 38 39 public void run() { 40 for (int i = 0; i < 3; i++) { 41 this.fix(30); 42 try { 43 Thread.sleep(1); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX()); 48 } 49 } 50 51 public int fix(int y) { 52 return foo.fix(y); 53 } 54 }