锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的 计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。本章我们着重讨论MySQL锁机制 的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议。
Mysql用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。这些锁统称为悲观锁(Pessimistic Lock)。
相对其他数据库而言,MySQL的锁机制比较简单,其最 显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度 来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有 并发查询的应用,如一些在线事务处理(OLTP)系统。
a. 共享锁(读锁) S:当一个事务对数据添加读锁后,其他事务只能对该数据加读锁,不能做修改操作,也就是加写锁。
b. 排他锁(写锁) X:当一个事务对数据添加写锁后,其他事务既不能对该数据加读锁,也不能加写锁。只有等待当前写锁释放后,才能进行后续加锁操作。
意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。意向锁是表锁,不会与行级的共享锁、排他锁互斥,与表级的共享锁、排他锁兼容关系如下:
表锁:加锁语句lock tables t1 read, t2 write; 可以用 unlock tables 主动释放锁。
元数据锁(metadata lock):MDL不需要显示使用,在访问表时自动加上,最主要的目的是防止DDL与DML并发冲突。
需要注意的是DML读锁保证的是表结构不能修改,与表数据无关,可以对数据进行操作,与读锁有区别。MDL写锁保证的是表结构不能修改,并且数据也不能读取。
页锁:页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。目前只有BDB引擎支持页级锁。
行锁:行锁是粒度最小、发生锁冲突概率最低、并发度最高的锁,但是加锁慢、开销大,容易发生死锁现象。目前只有innodb引擎支持行锁,行锁通过锁索引来实现,当无法通过索引来加锁时,行锁会转成表锁。
记录锁、间隙锁和临键锁都属于行锁。
乐观锁:乐观锁假设数据不会发生冲突,只有在数据提交更新时才会检测是否冲突,如果冲突了才会返回错误信息。适用于读多写少的场景。
悲观锁:悲观锁具有强烈的独占和排他特性,适用于并发量不大、写入比较频繁、数据一致性比较高的场景。Mysql中的共享锁和排他锁都属于悲观锁。
记录锁:精准命中唯一索引或主键索引时加记录锁,避免不可重复读和脏读问题。
间隙锁:普通索引、联合索引命中,或者唯一索引命中多行数据时加锁,避免幻读问题。
临键锁:记录锁和间隙锁的组合,索引命中多行数据时加锁,避免脏读、幻读和不可重复读三个问题。
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题。现在主流用的就是InnoDB引擎。
事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外)
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏 感,可能更关心数据并发访问的能力。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。下表很好地概括了这4个隔离级别的特性。
mysql设置已提交读解决赃读,MVCC解决幻读和快照度问题,不需要额外加锁处理并发问题,执行SQL时自动增加间隙锁、行锁;java通过redis和zk加锁解决并发事务问题。
但是,也并不是所有的一连串操作都弄成同一个事务,所以出现了脏读、幻读可以考虑其他问题,比如数据库主从同步的会话一致性,设置合理的同步机制可以解决
在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:
如有不对,请指正