锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所在有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决。
在mysql中有三种锁的级别:页级锁,表级锁,行级锁.其中:
mysql中的表锁包括读锁(表共享读锁)和写锁(表独占写锁),对myisam表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞写请求; 写操作,则会阻塞其他用户对同一表的读和写操作,myisam表的读操作和写操作之间,以及写与写操作之间都是串行的.
myisam在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作 (UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给myisam表显式加锁。
Lock tables orders read local, order_detail read local;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;
说明:上面的例子在LOCK TABLES时加了“local”选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾并发插入记录;
在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的 情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因。
并发插入(Concurrent Inserts)
上文提到过MyISAM表的读和写是串行的,但这是就总体而言的。在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
前面讲过,MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后 到,写锁也会插到读锁请求之前!这是因为MySQL认为写请求一般比读请求要重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原 因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM 的调度行为。
虽然上面3种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。
上面已经讨论了写优先调度机制带来的问题和解决办法。这 里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语 句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每 一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。
该引擎中的锁分为行锁和表锁.
行锁包括共享锁(S)和排它锁(X).共享锁允许一个事务去读一行,阻止其他事务获取相同数据集的排它锁;排它锁允许获得排它锁的事务更新数据,阻止其他事务取得相同数据集的共享度锁和排他写锁.
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,这两种意向锁都是表锁.
表锁分为三种:
注:我们在设计表结构时,通常会建议添加一列作为自增主键,这里就会涉及自增锁,它属于表锁,在insert结束后立即释放.
对表加表锁的前提是没有其他任何事务已经锁定该表的任一行.
InnoDB行锁是通过对索引数据页上的记录(record)加锁实现.主要实现算法有三种:
注:同一把间隙锁不冲突,可重复获取.gap锁住了一个区间,临键锁也锁住了下一区间,所以解决了幻读问题.
排查锁问题的两种方法:一.打开innodb_lock_monitor表,注意使用后关闭,会影响性能.二.在mysql5.5之后,可以通过查看information_schema库下面的innodb_locks,innodb_lock_waits,innodb_trx三个视图.
mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
一.主键
条件:update t1 set name=‘XX’ where id=10; id为主键索引,name不是一个key.
行为:仅在id=10的主键索引记录上加排他锁.
二.唯一键
条件:同上面的sql语句; id为唯一索引,name为主键索引.
行为:先在唯一索引id上加id=10的排它锁,再在id=10的主键索引记录上加排它锁.
三.非唯一键
条件:同上面的sql语句; name为主键索引,id非唯一索引(只是一个key).
行为:通过id=10定位到第一个满足的记录,对该记录加排它锁,要在这个记录之前,上个记录之后这个之间加上gap lock,防止幻读.然后在主键索引name上加对应记录的排它锁.以此类推找到所有满足的记录.最后还要在最后一个满足的记录后面再加上一个gap lock.
四.无索引
条件:同上面的sql语句; name为主键,id不是一个key.
行为:表里所有行和间隙均加排它锁.
**注意:InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁! **
在mysql中死锁不会发生在myisam中,但会在innodb中发生,因为innodb是逐行加锁.
产生死锁的四个条件如下:
在发生死锁时,innodb存储引擎会自动检测,并且会自动回滚代价较小的事务来解决死锁.但很多时候一旦死锁发生,innodb的处理效率是很低下的或者有时根本解决不了,需要人为手动解决.
避免死锁产生的一些建议:
加锁顺序一致; 尽量基于主键或者唯一键更新数据; 单次操作数据量不宜过多,涉及表尽量少; 减少表上索引,减少锁定资源; 相关工具:pt-deadlock-logger
避免线上业务因死锁造成的不必要影响的开发建议:
事务是由一组sql语句组成的逻辑处理单元.具有一下4个属性:
并发事务带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
事务隔离级别
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
select * from table where ?;
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏 感,可能更关心数据并发访问的能力。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。
四个隔离级别分别是: