每当有多个连接同时修改数据的时候,就会涉及到并发控制的问题。MySQL实现了两个层次的并发控制:服务层和引擎层。锁分类按使用场景对共享锁进行分类:共享锁也称为读锁。共享锁是共享的,或者说是相互非阻塞的。多个连接可以同时读取同一个资源,互不干扰。独占锁:独占锁(exclusivelock)也叫写锁(writelock)。写锁是排他的,即一个写锁阻塞其他的写锁和读锁。悲观锁按加锁思想分类:对数据被外界修改保持悲观态度。在整个数据处理过程中,数据处于锁定状态。乐观锁:认为数据在正常情况下不会产生冲突,只会在数据更新时检查数据是否冲突。根据锁粒度对全局锁进行分类:锁定整个数据库实例,将整个数据库实例置于只读状态。表级锁:一种锁定整个表的方法。MySQL表级锁分为表锁和元数据锁。行级锁:行锁可以最大程度的支持并发处理。行锁由存储引擎层实现,而MySQL服务层没有实现行锁。InnoDB存储引擎行级锁类型:RecordLock、GapLock、Next-keyLock。读写锁读锁共享锁:共享锁(sharedlock)也叫读锁(readlock)。共享锁是共享的,或者说是相互非阻塞的。多个连接可以同时读取同一个资源,互不干扰。锁定命令选择...lockinsharemode;测试的时候,设置事务手动提交:设置autocommit=0,如果以后没有明确提示,autocommit就会为0。测试的时候,我们打开两个窗口,建立两个连接。窗口1和窗口2分别对应事务A和事务B。窗口1:查询id=6的行数据并加读锁,数据正确返回。窗口2:仍然查询id=6的行数据并加读锁,正确返回数据。阅读不冲突。窗口1:对id=6的行执行写操作(更新语句)。在窗口2中的事务提交之前,写操作被阻塞并可能超时退出。如果写锁等待时间过长,会超时退出。窗口1mysql>updateusersetage=20whereid=6;ERROR1205(HY000):Lockwaittimeoutexceeded;tryrestartingtransaction如果窗口1的事务在等待写操作,窗口2的事务也执行同一行数据的写操作,这将导致死锁错误。窗口2mysql>updateusersetage=30whereid=6;ERROR1213(40001):尝试获取锁时发现死锁;tryrestartingtransactionwritelock独占锁(exclusivelock)也叫写锁(writelock)。写锁是排他的,即一个写锁阻塞其他的写锁和读锁。锁定命令select...forupdate;测试窗口1:查询id=6的行数据并加写锁,正确返回数据。窗口2:还是查询id=6的行数据,加写锁,阻塞。写-写冲突。窗口1:对id=6的行进行写操作(update语句),写操作没有被阻塞。窗口1的事务提交后,窗口2的查询语句返回结果。悲观锁和乐观锁乐观锁和悲观锁都是人们定义的概念,不是锁的实现,而是一种思想。乐观锁更适合读多写少的场景,悲观锁适合写多读少的场景。悲观锁当我们修改数据库中的某条数据时,为了防止其他人同时修改同一行数据,我们对数据进行加锁,以防止并发问题。这种借助于数据库的锁定机制,在修改数据之前对其进行锁定和修改的方法称为悲观锁定(PessimisticLock)。悲观锁具有很强的排他性和独占性,在整个数据写入过程中都对数据进行锁定。悲观锁的实现往往需要数据库提供的锁机制。悲观锁的实现:行锁、表锁、读锁、写锁等数据库锁机制都是操作前加锁,都是悲观锁。Java里学的synchronized关键字也是悲观锁。乐观锁乐观锁是相对于悲观锁的一个概念。乐观锁假设数据一般不存在并发冲突,只有在更新数据时才会校验数据是否冲突。如果存在冲突,则会告知用户结果,并由用户决定下一步做什么。乐观锁是一种松散的锁方式,不需要使用数据库本身的锁机制。乐观锁的实现:MVCC,数据库多版本控制使用版本号来控制数据更新的并发,就是乐观锁的实现。CAS、CompareandSwap是Java中乐观锁的一种实现。全局锁全局锁:锁定整个数据库实例,使整个数据库实例处于只读状态。锁定命令(FTWRL)Flushtableswithreadlock;应用场景全局锁常用于对整个数据库实例进行逻辑备份。在全局锁的加锁期间,业务的数据更新操作(DML)和表结构的修改操作(DDL)都会被加锁。至此,你是否有一个疑问:开发时直接使用Mysqldump做备份,什么时候使用FTWRL?官方的逻辑备份工具mysqldump在使用参数-single-transaction时,会在导出数据时启动一个Transactions来保证视图的一致性。在学习事务隔离的时候了解到,基于MVCC的一致性视图,这个过程中的数据是可以正常更新的。但是我们要知道,事务是引擎层的实现,并不是每个存储引擎都支持事务。我们在开发中的备份大多使用mysqldump,主要是因为我们的存储引擎在大多数情况下使用的是默认引擎InnoDB。表级锁表级锁:就是锁住整个表。表的定义由数据和结构两部分组成,所以表级锁也分为两类:表锁和元空间锁。表锁表锁是MySQL中最基本的加锁策略,也是开销最小的策略。表锁将锁定整个表。在对表进行写操作(插入、删除、更新等)之前,需要先获取写锁,这会阻塞其他用户对该表的所有读写操作。只有在没有写锁的情况下,其他读用户才能获得读锁。读锁之前不会互相阻塞。写锁的优先级高于读锁,所以写锁请求可以插入到读锁队列的前面,但是读锁不能插入到写锁的前面。添加解锁命令--对表locktables添加读锁...read;--对表locktables添加写锁...write;--释放锁unlocktables;testtablelockreadlock测试窗口1:给user表加读锁。窗口2:读取整个用户表,正常返回全表数据。窗口2:修改user表和block中id=6的数据。读写冲突。窗口1:修改user表中id=6的数据,报错。窗口1:读锁被释放,窗口2的更新数据执行成功。表锁写锁测试窗口1:给用户表加写锁。窗口2:跨表查询user表,阻塞。窗口1:更新user表中id=6的数据,更新成功。窗口1:释放锁,窗口2全表查询返回最新数据。元空间锁MySQL5.5引入了元空间锁(matadatalock)。在对表进行增删改查时,会加一个MDL读锁。当更改表结构时,将添加一个MDL写锁。MDL的作用是防止DDL和DML并发冲突。MDL锁是系统默认添加的,不需要显式添加。测试窗口1:查询表中的一条数据。这时候在执行查询语句的时候,会加一个MDL读锁。窗口2:添加字段。这时候执行alter语句,会加上MDL写锁。此时窗口1的读锁没有释放,所以alter语句会阻塞。窗口3:查询表中的一条数据。由于窗口2造成的阻塞,在窗口3申请MDL读锁时也会造成阻塞。窗口1:提交事务,窗口3获取MDL读锁,返回查询结果。窗口3:提交事务释放读锁,窗口2获取写锁,字段添加成功。行级锁MySQL的行锁在引擎层由各个存储引擎实现。并非所有存储引擎都支持行锁。例如,MyISAM引擎不支持行锁。不支持行锁意味着只能使用表锁来进行并发控制,也意味着同一个表在同一时间只能执行一次update,严重影响并发性。InnoDB支持行锁,这也是InnoDB能够替代MyISM的重要原因之一。在两阶段锁协议中,行锁在InnoDB事务中需要的时候加,不需要的时候不立即释放。相反,他们需要等到事务结束才释放锁。这就是两阶段锁定协议。InnoDB是采用的两阶段锁定协议。在交易执行过程中,可以随时锁定。只有在执行commit或rollback时才会释放锁,所有的锁都会同时释放。事务A执行完两条更新语句后,事务B也执行了更新语句,但是事务B阻塞,直到事务A提交事务。行锁我们创建一个简单的表t,其中id为主键,a为索引,插入6条数据。创建表`t1`(`id`int(11)NOTNULL,`a`int(11)DEFAULTNULL,`b`int(11)DEFAULTNULL,PRIMARYKEY(`id`),KEY`idx`(`a`))ENGINE=InnoDBDEFAULTCHARSET=utf8;insertintot1values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);间隙锁(GapLock)间隙锁,锁是两个值之间的间隙。对于表t1,6条数据产生了7个间隙,如下图所示:再看前面学习的写锁的例子:begin;选择*fromt1whereb=5forupdate;提交;加了写锁的select查询是当前读取的,读取的是最近的数据值。b字段不是表的索引字段。它会扫描全表并对所有满足条件的行加写锁,同时也会对满足条件的行两边的间隙加锁。间隙锁之间不存在冲突,与间隙锁发生冲突的是向这个间隙插入数据的操作。next-keylockrowlock和gaplock的组合称为next-keylock。每个下一键锁都是一个区间,之前打开,之后关闭。这里不要混淆,gaplock是一个open的区间,gaplock加rowlock产生的next-keylock是一个先开后闭的区间。gaplock和next-keylock的引入帮助我们解决了幻读问题。间隙锁只在可重复隔离级别下生效。如果我们设置隔离级别为readcommitted,就不会有间隙锁。测试之前,我们在研究其他锁的时候,为了省事,把autocommit设置为0。现在我们需要将自动提交设置为1;Window1:使用显式事务,通过forupdate加写锁,加Lock。窗口2:更新id为0的行。该行未锁定并更新成功。窗口3:插入数据,满足(0,5)的next-key锁,阻塞等待。摘要锁按锁粒度分为:全局锁、表锁和行锁。行锁是引擎层的实现,我们在本文中描述的所有行锁都是基于InnoDB存储引擎实现的。InnoDB行锁采用两阶段锁协议。需要时加锁,事务提交或回滚时一次性释放所有锁。在可重复性隔离级别下有一个间隙锁。如果设置为其他隔离级别,则不会有间隙锁。间隙锁就是锁住行数据两边的间隙。间隙锁加上行锁,叫做next-keylock,也就是之前开启,之后关闭的区间。间隙锁解决了幻读问题。大家在学习锁的时候需要多练习。
