zoukankan      html  css  js  c++  java
  • MySQL锁

    一.什么是锁

    数据库的锁是为了对共享资源进行并发访问,提供数据的完整性和一致性,这样才能保证在高并发场景下,访问数据库时,数据不会出现问题.

    二.锁机制

    1.按锁的粒度划分:行级锁、表级锁、页级锁、间隙锁

    MyISAM和MEMORY采用表级锁(table-level locking)
    InnoDB支持行级锁(row
    -level locking)和表级锁,默认为行级锁
    BDB采用页面锁(page
    -level locking)或表级锁,默认为页面锁

    默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。

    1)行级锁

    描述:
    行级锁是mysql中锁粒度最小的一种锁。表示只针对当前操作的行进行加锁。行级锁分为共享锁和排他锁
    
    特点:
    开销大,加锁慢,会出现死锁。发生锁冲突的概率最低,并发度也最高。

    2)表级锁

    描述:
    表级锁是mysql中锁定粒度最大的一种锁,表示对当前操作的整张表加锁.
    最常使用的MyISAM与InnoDB都支持表级锁。
    表级锁分为表共享读锁(共享锁)与表独占写锁(排他锁).
    
    特点:
    开销小,加锁快,不会出现死锁。发生锁冲突的概率最高,并发度也最低。

    3)页级锁

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

    2.按照锁的共享策略来分:共享锁、排他锁、意向共享锁、意向排他锁

    1)读锁(共享锁):Shared Locks(S锁),其他事务可以读,但不能写

    2)写锁(排它锁):Exclusive Locks(X锁),其他事务不能读取,也不能写

    3)IS锁:意向共享锁、Intention Shared Lock。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。

    4)IX锁:意向排他锁、Intention Exclusive Lock。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。

    IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录。
    就是说当对一个行加锁之后,如果有打算给行所在的表加一个表锁,必须先看看该表的行有没有被加锁,否则就会出现冲突。
    IS锁和IX锁就避免了判断表中行有没有加锁时对每一行的遍历。直接查看表有没有意向锁就可以知道表中有没有行锁。 注意:如果一个表中有多个行锁,他们都会给表加上意向锁,意向锁和意向锁之间是不会冲突的。

    3.按加锁策略上分:乐观锁和悲观锁

    1)悲观锁:

    认为对于同一个数据的并发操作,一定是会发生修改的(或者增删改多,查少),哪怕没有修改,也会认为修改。

    因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

    2)乐观锁:

    则认为对于同一个数据的并发操作,是不会发生修改的(或者增删改少,查多)。

    在更新数据的时候,会采用不断尝试更新的方式来修改数据。也就是先不管资源有没有被别的线程占用,直接取申请操作,如果没有产生冲突,那就操作成功,如果产生冲突,有其他线程已经在使用了,那么就不断地轮询。

    乐观的认为,不加锁的并发操作是没有事情的。就是通过记录一个数据历史记录的多个版本,如果修改完之后发现有冲突再将版本返回到没修改的样子,乐观锁就是不加锁。好处就是减少上下文切换,坏处是浪费CPU时间。

     三.MyISAM的锁模式

    1.MyISAM表级锁模式

    表共享读锁 (Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
    表独占写锁 (Table Write Lock):会阻塞其他用户对同一表的读和写操作;
    MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后, 只有持有锁的线程可以对表进行更新操作。 其他线程的读、 写操作都会等待,直到锁被释放为止。

    2.MyISAM加表锁方法

    MyISAM 在执行查询语句(SELECT)前,会自动给涉及的表加读锁,
    在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。

    显示加锁:
    LOCK TABLE my_table_name READ;
    用读锁锁表,会阻塞其他事务修改表数据。
    LOCK TABLE my_table_name WRITE; 用写锁锁表,会阻塞其他事务读和写。
    在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,这也正是 MyISAM 表不会出现死锁(Deadlock Free)的原因。

    四.InnoDB的锁模式

    1.InnoDB行级锁和表级锁

    InnoDB 实现了以下两种类型的行锁:
    共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
    排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
    
    为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
    意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
    意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

    2.InnoDB加锁方法

    意向锁是 InnoDB 自动加的, 不需用户干预。
    
    对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB会自动给涉及数据集加排他锁(X);
    
    对于普通 SELECT 语句,InnoDB 不会加任何锁;
    
    事务可以通过以下语句显式给记录集加共享锁或排他锁:
    共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。
    
    排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其他 session 可以查询该记录,但是不能对该记录加共享锁或排他锁,而是等待获得锁

    3.InnoDB 行锁实现方式

    InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。
    InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!
    不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。

    4.InnoDB有三种行锁的算法

    1,Record Lock(记录锁):单个行记录上的锁。这个也是我们日常认为的行锁。
    
    2,Gap Lock(间隙锁):间隙锁,锁定一个范围,但不包括记录本身(只不过它的锁粒度比记录锁的锁整行更大一些,他是锁住了某个范围内的多个行,包括根本不存在的数据)。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。该锁只会在隔离级别是RR或者以上的级别内存在。间隙锁的目的是为了让其他事务无法在间隙中新增数据。
    
    3,Next-Key Lock(临键锁):它是记录锁和间隙锁的结合,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。next-key锁是InnoDB默认的锁
    
    next-Key锁规定是左开右闭区间,效果相当于一个记录锁加一个间隙锁。当next-key lock加在某索引上,则该记录和它前面的区间都被锁定
    假设有记录1, 3, 5, 7,现在记录5上加next-key lock,则会锁定区间(3, 5],任何试图插入到这个区间的记录都会阻塞

    五.死锁

    1.死锁的产生和检测

    死锁产生:
    死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。
    当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁。
    锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会

    死锁有双重原因:真正的数据冲突;存储引擎的实现方式。 检测死锁:数据库系统实现了各种死锁检测和死锁超时的机制。InnoDB存储引擎能检测到死锁的循环依赖并立即返回一个错误。

    2.避免死锁

    MyISAM避免死锁:
    在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,所以 MyISAM 表不会出现死锁。
    InnoDB避免死锁: 1)为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个元祖(行)使用SELECT ... FOR UPDATE语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。 2)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁 3)如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会 4)通过SELECT ... LOCK IN SHARE MODE获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。 5)改变事务隔离级别
    如果出现死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。

    3.一些锁性能优化建议

    1)尽量使用较低的隔离级别;
    2)精心设计索引, 并尽量使用索引访问数据, 使加锁更精确, 从而减少锁冲突的机会
    3)选择合理的事务大小,小事务发生锁冲突的几率也更小
    4)给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁
    5)不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会
    6)尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响
    7)不要申请超过实际需要的锁级别
    8)除非必须,查询时不要显示加锁。 MySQL的MVCC可以实现事务中的查询不用加锁,优化事务性能;MVCC只在COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作
    9)对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能
  • 相关阅读:
    值传递
    抽象类
    面向对象三大特征(二)--继承
    单例设计模式
    神奇的main方法详解
    面向对象的三大特征 ---- 封装
    变量、方法以及静态和非静态
    面向对象编程-类和对象
    数组
    力扣题库刷题(随时记录)
  • 原文地址:https://www.cnblogs.com/Baker-Street/p/15019971.html
Copyright © 2011-2022 走看看