精华内容
下载资源
问答
  • 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和...
  • 数据库通过以及协议来进行并发控制,解决并发事务带来的问题,本篇博文主要是解析数据库的协议和Mysql的默认存储引擎InnoDB的机制。 如果对事务隔离级别以及并发事务带来的问题不熟悉可以翻阅我的另外一篇...
  • 一、悲观 比较悲观,担心拿数据时被别人修改,所以查询时先加锁在修改,保证操作时别人修改不了,期间需要访问该数据的都会等待。 select version from user where id=1 for update  update user set version=2 ...
  • 重要声明:本人之前对java中的读写也不是非常了解,用的也不是很多,尤其在读写的策略原理一块没有深究过,本篇文章是在学习【玩转Java并发工具,精通JUC,成为并发多面手】课程后写的,故文章类型选择为"转载",...

    重要声明:本人之前对java中的读写锁也不是非常了解,用的也不是很多,尤其在读写锁的策略原理一块没有深究过,本篇文章是在学习【玩转Java并发工具,精通JUC,成为并发多面手】课程后写的,故文章类型选择为"转载",因为本文的很多结论都是来自于那门课程,请知悉~。希望对各位同仁有帮助~


    books 读写锁的基本使用

    在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和排他锁:ReentrantReadWriteLock

    在ReentrantReadWriteLock中包含读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁其实是一把锁,所以会有同一时刻不允许读写锁共存的规定。之所以要细分读锁和写锁也是为了提高效率,将读和写分离,对比ReentrantLock就可以发现,无论并发读还是写,它总会先锁住全部再说。

    接着先来个代码演示下,读锁是共享锁,写锁是排他锁:

    /**
     * ReentrantReadWriteLock读写锁示例
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放写锁");
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> read(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
            new Thread(() -> write(), "Thread3").start();
            new Thread(() -> write(), "Thread4").start();
        }
    }

    输出结果如下,线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁:

    Thread1获取读锁,开始执行
    Thread2获取读锁,开始执行
    Thread1释放读锁
    Thread2释放读锁
    Thread3获取写锁,开始执行
    Thread3释放写锁
    Thread4获取写锁,开始执行
    Thread4释放写锁

    books 读锁的插队策略

    设想如下场景:在非公平的ReentrantReadWriteLock锁中,线程2和线程4正在同时读取,线程3想要写入,拿不到锁(同一时刻是不允许读写锁共存的),于是进入等待队列,线程5不在队列里,现在过来想要读取策略1是如果允许读插队,就是说线程5读先于线程3写操作执行,因为读锁是共享锁,不影响后面的线程3的写操作,这种策略可以提高一定的效率,却可能导致像线程3这样的线程一直在等待中,因为可能线程5读操作之后又来了n个线程也进行读操作,造成线程饥饿策略2是不允许插队,即线程5的读操作必须排在线程3的写操作之后,放入队列中,排在线程3之后,这样能避免线程饥饿

    事实上ReentrantReadWriteLock在非公平情况下,读锁采用的就是策略2:不允许读锁插队,避免线程饥饿。更加确切的说是:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程。

    以上还在非公平ReentrantReadWriteLock锁中,在公平锁中,读写锁都是是不允许插队的,严格按照线程请求获取锁顺序执行。

    下面用代码演示一下结论:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程

    /**
     * ReentrantReadWriteLock读写锁插队策略测试
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(20);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(40);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> write(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
            new Thread(() -> read(), "Thread3").start();
            new Thread(() -> write(), "Thread4").start();
            new Thread(() -> read(), "Thread5").start();
            new Thread(() -> {
                Thread[] threads = new Thread[1000];
                for (int i = 0; i < 1000; i++) {
                    threads[i] = new Thread(() -> read(), "子线程创建的Thread" + i);
                }
                for (int i = 0; i < 1000; i++) {
                    threads[i].start();
                }
            }).start();
        }
    }

    以上测试代码就演示了,在非公平锁时,其一:同一时刻读写锁不能同时存在, 其二,读锁非常容易插队,但前提是队列中的头结点不能是想获取写锁的线程。

    books 锁的升降级

    升降级是指读锁升级为写锁,写锁降级为度锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁,如下代码测试所示:

    /**
     * ReentrantReadWriteLock锁升降级测试
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(20);
                System.out.println(Thread.currentThread().getName()+ "尝试升级读锁为写锁");
                //读锁升级为写锁(失败)
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() +"读锁升级为写锁成功");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(40);
                System.out.println(Thread.currentThread().getName() +"尝试降级写锁为读锁");
                //写锁降级为读锁(成功)
                readLock.lock();
                System.out.println(Thread.currentThread().getName()+ "写锁降级为读锁成功");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放写锁");
                writeLock.unlock();
                readLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> write(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
        }
    }

    运行控制台输出:

    Thread1开始尝试获取写锁
    Thread1获取写锁,开始执行
    Thread1尝试降级写锁为读锁
    Thread1写锁降级为读锁成功
    Thread1释放写锁
    
    Thread2开始尝试获取读锁
    Thread2获取读锁,开始执行
    Thread2尝试升级读锁为写锁

    之所以ReentrantReadWriteLock不支持锁的升级(其它锁可以支持),主要是避免死锁,例如两个线程A和B都在读, A升级要求B释放读锁,B升级要求A释放读锁,互相等待形成死循环。如果能严格保证每次都只有一个线程升级那也是可以的。

    books 总结

    1. 读写锁特点特点:读锁是共享锁,写锁是排他锁,读锁和写锁不能同时存在
    2. 插队策略:为了防止线程饥饿,读锁不能插队
    3. 升级策略:只能降级,不能升级
    4. ReentrantReadWriteLock适合于读多写少的场合,可以提高并发效率,而ReentrantLock适合普通场合

    books 引申阅读:

    展开全文
  • mysql共享锁 排他锁 意向锁

    千次阅读 2019-04-01 13:34:36
    mysql锁机制分为表锁和行锁,其中行锁又包括了共享锁与排他锁。 共享锁: 又称为读锁(S锁),当有多个事务时,多个事务对于同一数据可以共享一个锁,都能访问到数据,但是其他事务只能读不能写。 排他锁 又称为...

    mysql锁机制分为表锁和行锁,其中行锁又包括了共享锁与排他锁。

    共享锁:

    又称为读锁(S锁),当有多个事务时,多个事务对于同一数据可以共享一个锁,都能访问到数据,但是其他事务只能读不能写。

    排他锁

    又称为写锁(X锁),当有多个事务时,排他锁不能与其他锁并存,一个事务获取了一行数据的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁与排他锁。  但是获取排他锁的事务是可以对数据进行读取和修改的。

    共享锁与排他锁的区别:

    共享锁就是多个事务只能读数据不能改数据。排他锁是指一个事务在一行数据加上排他锁后,其他事务不能再在其上面加其他的锁。Innodb引擎默认的修改数据语句,update,insert,delete都会自动给涉及到的数据加上排他锁,select语句默认不加锁,如果加共享锁可以用select ... lock in share mode语句。 如果加排他锁可以使用select ... for update语句。 加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select ... from ...查询数据,因为普通查询没有任何锁机制。

    下面来测试并验证一下:

    首先关闭自动提交:

    测试数据如下(一切从简哈),然后打开两个查询窗口。

    首先进行共享锁测试: 下面的都是先执行左边再执行右边

    共享锁与共享锁:

    两个窗口都使用共享锁查询,能够共享这条数据。

    共享锁与排他锁:

    当一条数据别共享锁锁住时,不能对数据进行修改,排他锁不能与共享锁共存,会处于堵塞状态等待共享锁事务提交。

    事务提交后就可以进行修改了

    共享锁与普通查询:

    都能查询到数据

    再进行排他锁测试: 下面的都是先执行左边再执行右边

    排他锁与排他锁:

    排他锁与共享锁:

    可以看到都处于阻塞状态,id=1的数据已经被加上了排他锁,必须等到锁释放才能加上别的锁。

    不加锁的查询是可以查询到数据的,因为没有锁机制不与排他锁互斥。

    查询其他行也可以查询到数据。

    意向锁:

    InnoDB所用的表级锁,其设计目的主要是为了在一个事务中揭示下一步将要被请求的锁的类型。

    InnoDB中的两个表锁:

    意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁

    意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

    意向锁是InnoDB自动加的,不需要用户干预。

     

    表级锁

    MySQL表级锁分为读锁和写锁。

    读锁


    用法:LOCK TABLE table_name [ AS alias_name ] READ

    释放锁使用UNLOCK tables.可以为表使用别名,如果一旦使用别名在使用的时候也必须采用别名。成功申请读锁的前提是当前没有线程对该表使用写锁,否则该语句会被阻塞。申请读锁成功后,其他线程也可以对该表进行读操作,但不允许有线程对其进行写操作,就算是当前线程也不允许。当锁住了A表之后,就只能对A表进行读操作,对其他表进行读操作会出现错误(tablename was not locked with LOCK TABLES)

    写锁


    用法: LOCK TABLE table_name [AS alias_name] [ LOW_PRIORITY ] WRITE

    同样也可以使用别名,与读锁不同的是,写锁中可以指定锁的优先级。LOW_PRIORITY是一种比读锁更低优先级的锁,当多个线程同时申请多种锁(LOW_PRIORITY,READ,WRITE)时,LOW_PRIORITY的优先级最低。读锁申请成功的前提是没有线程对表加读锁和其他写锁,否则会被阻塞。

    表级锁在MyISAM和innoDB中都有用到,创建锁的开销小,不会出现死锁,由于锁定的是整张表,所以并发度低。当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时,推荐使用表级锁。 

     

    结束。

    我的其他关于mysql的博客,可以看下哦。

    mysql架构与存储引擎 (Myisam与Inoodb)

    https://blog.csdn.net/qq_37113604/article/details/88831254

     

     

     

     

     

     

    展开全文
  • 共享锁、排他锁、互斥锁、悲观锁、乐观锁、行锁、表锁、页面锁、不可重复读、丢失修改、读脏数据 内容来自:https://mp.weixin.qq.com/s/hODjqbax1EX0T0IJh7_yNg 共享锁(S锁): 又称为读锁,可以查看但无法修改和...

    共享锁、排他锁、互斥锁、悲观锁、乐观锁、行锁、表锁、页面锁、不可重复读、丢失修改、读脏数据

    内容来自:https://mp.weixin.qq.com/s/hODjqbax1EX0T0IJh7_yNg

    共享锁(S锁): 又称为读锁,可以查看无法修改和删除的一种数据锁

    如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享。

    排它锁(X锁):  又称为写锁、独占锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。

    互斥锁: 在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

    悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

    乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    行级锁: 行级锁是 MySQL 中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突,其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁和排他锁。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

    表级锁: 表级锁是 MySQL 中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分 MySQL 引擎支持。最常使用的 MyISAM 与 InnoDB 都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低

    页级锁:  页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 支持页级锁。开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

    其它相关概念:

    • 丢失修改: 指事务1和事务2同时读入相同的数据并进行修改,事务2提交的结果破坏了事务1提交的结果,导致事务1进行的修改丢失。

    • 不可重复读: 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了。

    • 读脏数据: 事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤消,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为"脏"数据,即不正确的数据。

    • 死锁: 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    死锁四个产生条件:

    1)互斥条件:  指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

    2)请求和保持条件: 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放

    3)不剥夺条件: 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

    4)环路等待条件: 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

    预防死锁打破上述之一的条件。

    在这里插入图片描述

    初次编写于2021年8月20日。

    展开全文
  • MySQL锁共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 表级锁(锁定整个表) 页级锁(锁定一页) 行级锁(锁定一行) 共享锁(S锁,MyISAM 叫做读锁) 排他锁(X锁,MyISAM 叫做写锁) 悲观锁(抽象性...

    MySQL锁共享锁、排他锁、悲观锁、乐观锁及其使用场景

    一、相关名词

    表级锁(锁定整个表)
    页级锁(锁定一页)
    行级锁(锁定一行)
    共享锁(S锁,MyISAM 叫做读锁)
    排他锁(X锁,MyISAM 叫做写锁)
    悲观锁(抽象性,不真实存在这个锁)
    乐观锁(抽象性,不真实存在这个锁)

    二、InnoDB与MyISAM

    Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB 。查看当前存储引擎:

    show variables like '%storage_engine%';
    

    MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。

    而 InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB支持事务;二是 InnoDB 采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。

    在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

    InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

    三、共享锁与排他锁

    1.首先说明:数据库的增删改操作默认都会加排他锁,而查询(select)不会加任何锁

    共享锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。语法为:

    select * from table lock in share mode

    排他锁:对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。语法为:
    select * from table for update增删改自动加了排他锁

    2.下面援引例子说明(援自:http://blog.csdn.net/samjustin1/article/details/52210125):

    这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程。

    例1:

    T1: select * from table lock in share mode
    (假设查询会花很长时间,下面的例子也都这么假设)

    T2: update table set column1='hello'

    过程:

    T1运行(并加共享锁)
    T2运行
    If T1还没执行完
    T2等......
    else锁被释放
    T2执行
    endif
    

    T2 之所以要等,是因为 T2 在执行 update 前,试图对 table 表加一个排他锁,而数据库规定同一资源上不能同时共存共享锁和排他锁。所以 T2 必须等 T1 执行完,释放了共享锁,才能加上排他锁,然后才能开始执行 update 语句。

    例2:

    T1: select * from table lock in share mode

    T2: select * from table lock in share mode

    这里T2不用等待T1执行完,而是可以马上执行。

    分析:
    T1运行,则 table 被加锁,比如叫lockAT2运行,再对 table 加一个共享锁,比如叫lockB两个锁是可以同时存在于同一资源上的(比如同一个表上)。这被称为共享锁与共享锁兼容。这意味着共享锁不阻止其它人同时读资源,但阻止其它人修改资源。

    例3:

    T1: select * from table lock in share mode

    T2: select * from table lock in share mode

    T3: update table set column1='hello'

    T2 不用等 T1 运行完就能运行,T3 却要等 T1 和 T2 都运行完才能运行。因为 T3 必须等 T1 和 T2 的共享锁全部释放才能进行加排他锁然后执行 update 操作。

    例4:(死锁的发生)

    T1: begin transelect * from table lock in share modeupdate table set column1='hello'

    T2: begin transelect * from table lock in share modeupdate table set column1='world'

    假设 T1 和 T2 同时达到 select,T1 对 table 加共享锁,T2 也对 table 加共享锁,当 T1 的 select 执行完,准备执行 update 时,根据锁机制,T1 的共享锁需要升级到排他锁才能执行接下来的 update.在升级排他锁前,必须等 table 上的其它共享锁(T2)释放,同理,T2 也在等 T1 的共享锁释放。于是死锁产生了。

    例5:

    T1: begin tranupdate table set column1='hello' where id=10

    T2: begin tranupdate table set column1='world' where id=20

    这种语句虽然最为常见,很多人觉得它有机会产生死锁,但实际上要看情况

    如果id是主键(默认有主键索引),那么T1会一下子找到该条记录(id=10的记录),然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录,然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不需要等。

    如果id是普通的一列,没有索引。那么当T1对id=10这一行加排他锁后,T2为了找到id=20,需要对全表扫描。但因为T1已经为一条记录加了排他锁,导致T2的全表扫描进行不下去(其实是因为T1加了排他锁,数据库默认会为该表加意向锁,T2要扫描全表,就得等该意向锁释放,也就是T1执行完成),就导致T2等待。

    死锁怎么解决呢?一种办法是,如下:

    例6:

    T1: begin transelect * from table for updateupdate table set column1='hello'

    T2: begin transelect * from table for updateupdate table set column1='world'

    这样,当 T1 的 select 执行时,直接对表加上了排他锁,T2 在执行 select 时,就需要等 T1 事物完全执行完才能执行。排除了死锁发生。但当第三个 user 过来想执行一个查询语句时,也因为排他锁的存在而不得不等待,第四个、第五个 user 也会因此而等待。在大并发情况下,让大家等待显得性能就太友好了。

    所以,有些数据库这里引入了更新锁(如Mssql,注意:Mysql不存在更新锁)。

    例7:

    T1: begin transelect * from table (加更新锁)update table set column1='hello'

    T2: begin transelect * from table (加更新锁)update table set column1='world'

    更新锁其实就可以看成排他锁的一种变形,只是它也允许其他人读(并且还允许加共享锁)。但不允许其他操作,除非我释放了更新锁。T1 执行 select,加更新锁。T2 运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。当后来有 user3、user4…需要查询 table 表中的数据时,并不会因为 T1 的 select 在执行就被阻塞,照样能查询,相比起例6,这提高了效率。

    后面还有意向锁和计划锁:意向锁即是:某行修改时,自动加上了排他锁,同时会默认给该表加意向锁,表示里面有记录正被锁定,这时,其他人就不可以对该表加表锁了。如果没有意向锁这个类似指示灯的东西存在,其他人加表锁之前就得扫描全表,查看是否有记录正被锁定,效率低下。而计划锁这些,和程序员关系不大,就没去了解了。

    四、乐观锁与悲观锁

    案例:

    某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为n,之后进行了一些操作,最后将均执行update table set库存数=n-1,那么,很显然这是错误的。

    解决:

    1.使用悲观锁(其实说白了也就是排他锁)

    程序A在查询库存数时使用排他锁(select * from table where id=10 for update

    然后进行后续的操作,包括更新库存数,最后提交事务。

    程序B在查询库存数时,如果A还未释放排他锁,它将等待。

    程序C同B……

    2.使用乐观锁(靠表设计和代码来实现)

    一般是在该商品表添加version版本字段或者timestamp时间戳字段

    程序A查询后,执行更新变成了:
    update table set num=num-1 where id=10 and version=23

    这样,保证了修改的数据是和它查询出来的数据是一致的,而其他执行程序未进行修改。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。

    总结:对于以上,可以看得出来乐观锁和悲观锁的区别。

    1.悲观锁使用了排他锁,当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。

    2.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。

    也就是一句话:读用乐观锁,写用悲观锁。

    行锁和表锁

    案例分析

    目前,MySQL常用的存储引擎是InnoDB,相对于MyISAM而言。InnoDB更适合高并发场景,同时也支持事务处理。我们通过下面这个案例(坑),来了解行锁和表锁。 业务:因为订单重复导入,需要用脚本将订单状态为”待客服确认”且平台是”xxx”的数据批量修改为”已关闭”。 说明:避免直接修改订单表造成数据异常。这里用innodb_lock 表演示InnoDB的行锁。表中有三个字段:id,k(key值),v(value值)。 步骤: 第一步:连接数据库,这里为了方便区分命名为Transaction-A,设置autocommit为零,表示需手动提交事务。 第二步:Transaction-A,执行update修改id为1的命令。 第三步:新增一个连接,命名为Transaction-B,能正常修改id为2的数据。再执行修改id为1的数据命令时,却发现该命令一直处理阻塞等待中。 第四步:Transaction-A,执行commit命令。Transaction-B,修改id为1的命令自动执行,等待37.51秒。

    总结:多个事务操作同一行数据时,后来的事务处于阻塞等待状态。这样可以避免了脏读等数据一致性的问题。后来的事务可以操作其他行数据,解决了表锁高并发性能低的问题。

    # Transaction-A
    mysql> set autocommit = 0;
    mysql> update innodb_lock set v='1001' where id=1;
    mysql> commit;
    
    # Transaction-B
    mysql> update innodb_lock set v='2001' where id=2;
    Query OK, 1 row affected (0.37 sec)
    mysql> update innodb_lock set v='1002' where id=1;
    Query OK, 1 row affected (37.51 sec)
    

    有了上面的模拟操作,结果和理论又惊奇的一致,似乎可以放心大胆的实战。但现实真的很残酷。

    现实:当执行批量修改数据脚本的时候,行锁升级为表锁。其他对订单的操作都处于等待中

    原因:InnoDB只有在通过索引条件检索数据时使用行级锁,否则使用表锁!而模拟操作正是通过id去作为检索条件,而id又是MySQL自动创建的唯一索引,所以才忽略了行锁变表锁的情况。

    步骤:

    第一步:还原问题,Transaction-A,通过k=1更新v。Transaction-B,通过k=2更新v,命令处于阻塞等待状态。

    第二步:处理问题,给需要作为查询条件的字段添加索引。用完后可以删掉。

    总结:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。索引失效的原因在上一章节中已经介绍:http://www.cnblogs.com/itdragon/p/8146439.html

    Transaction-A
    mysql> update innodb_lock set v='1002' where k=1;
    mysql> commit;
    mysql> create index idx_k on innodb_lock(k);
    
    Transaction-B
    mysql> update innodb_lock set v='2002' where k=2;
    Query OK, 1 row affected (19.82 sec)
    

    从上面的案例看出,行锁变表锁似乎是一个坑,可MySQL没有这么无聊给你挖坑。这是因为MySQL有自己的执行计划。 当你需要更新一张较大表的大部分甚至全表的数据时。而你又傻乎乎地用索引作为检索条件。一不小心开启了行锁(没毛病啊!保证数据的一致性!)。可MySQL却认为大量对一张表使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突问题,性能严重下降。所以MySQL会将行锁升级为表锁,即实际上并没有使用索引。 我们仔细想想也能理解,既然整张表的大部分数据都要更新数据,在一行一行地加锁效率则更低。其实我们可以通过explain命令查看MySQL的执行计划,你会发现key为null。表明MySQL实际上并没有使用索引,行锁升级为表锁也和上面的结论一致。

    行锁

    行锁的劣势:开销大;加锁慢;会出现死锁 行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强 加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:

    1. 共享锁:select * from tableName where … + lock in share mode,也称读锁,多用于判断数据是否存在,多个读操作可以同时进行而不会互相影响。当如果事务对读锁进行修改操作,很可能会造成死锁。
    2. 排它锁:select * from tableName where … + for update ,也称写锁,独占锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

    参考资料:https://www.jianshu.com/p/1ab3cd5551b9

    展开全文
  • 排他锁(ExculusiveLocks) 又称为写锁或独占锁。是一种锁类型,如果事务T1对数据对象O加上了排他锁。 那么在整个加锁期间,只有事务T1可以对数据对象O进行读写操作,其它任何事务都不能对数据对象O进行任何类型的...
  • 文章目录1、数据库为什么要有锁机制2、锁的分类3、锁详细介绍3.1、表-共享锁(S)3.2、表-排他锁(X)3.3、表-自增锁(Auto-Inc Lock)3.4、行 - 记录锁(Record Lock)3.5、表 - 意向共享锁(IS)/ 意向排他锁(IX...
  • mysql共享锁排他锁

    2019-11-26 15:00:55
    数据库简单来说,是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。mysql存在多种搜索引擎,每种搜索引擎所针对的应用场景特点不太一样。每种搜索引擎的锁定机制都是为各自...
  • Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁...
  • 在已经开启事务的前提下: 共享锁 ...A用户使用了排他锁,B用户就使用排他锁或者共享锁来获取数据,会一直等待中 A用户更新数据并提交事务,此时B用户用户获得锁成功并查得数据 ##InnoDb的锁的一些...
  • Mysql共享锁和排他锁

    万次阅读 多人点赞 2019-05-27 15:10:41
    mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据...
  • 参见:《【mysql】MySQL中的锁(表锁、行锁、间隙锁、共享锁、排他锁)》
  • Mysql锁之共享锁(读锁)和排他锁(写锁) InnoDB和MyISAM Mysql在5.5之前默认使用MyISAM存储引擎,之后使用InnoDB。查看当前存储引擎: show variables like ‘%storage_engine%’; MyISAM操作数据都是使用的表锁,...
  • sql server 数据库新建数据库时出现异常 无法获得数据库'model'上的排他锁 这是解决办法
  • 共享(S):又称为读,可以查看但无法修改和删除的一种数据。如果事务T对数据A加上共享后,则其他事务只能对A再加共享,不能加排它。获准共享的事务只能读数据,不能修改数据。共享下其它用户可以...
  • 共享锁与排他锁 乐观锁与悲观锁 一 名词解释 行级锁:锁定整行 页级锁:锁定一页 表级锁:锁定整个表 共享锁:S锁,MyISAM 叫做读锁 排他锁:X锁,MyISAM 叫做写锁 悲观锁:抽象性,不真实存在这个锁 ...
  • Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 |–表级锁(锁定整个表) |–页级锁(锁定一页) |–行级锁(锁定一行) |–共享锁(S锁,MyISAM 叫做读锁) |–排他锁(X锁,MyISAM 叫做写锁) |–...
  • MySQL共享锁与排他锁

    千次阅读 2018-10-22 12:50:53
    mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,...
  • 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。 排他锁 排他锁(X锁):用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时...
  • 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数...
  • 1.表级与行级 表级: table-level locking,住整个表。 开销小,加锁快。 不会死锁(一次性加载所需的所有表)。 粒度大,发生冲突概率大,并发效率低。 适合查询。 行级: row-level loking,住一...
  • 排他锁:本事务加锁后,其他事务既不可以读,也不可以写。 行级锁与表级锁 表级锁:粒度打,以表为单位上锁,允许很低的并发度。 行级锁:粒度小,以行为单位上锁,允许较高的并发度。但是上锁、解锁开销大,...
  • 接下来来介绍悲观锁和乐观锁以及共享锁和排他锁 乐观锁(靠表的设计和代码) 乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据...
  • 分布式是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干...
  • 共享锁(读锁)、排他锁(写锁) 状态锁 意向共享锁、意向排他锁 一、乐观锁和悲观锁 1.乐观锁介绍 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,399
精华内容 18,959
关键字:

排他锁