行锁变表锁,是福仍是坑?若是你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给大家标记几个坑。遇到了可别乱踩。经过本章内容,带你学习MySQL的行锁,表锁,两种锁的优缺点,行锁变表锁的缘由,以及开发中须要注意的事项。还在等啥?经验等你来拿!html
MySQL的存储引擎是从MyISAM到InnoDB,锁从表锁到行锁。后者的出现从某种程度上是弥补前者的不足。好比:MyISAM不支持事务,InnoDB支持事务。表锁虽然开销小,锁表快,但高并发下性能低。行锁虽然开销大,锁表慢,但高并发下相比之下性能更高。事务和行锁都是在确保数据准确的基础上提升并发的处理能力。本章重点介绍InnoDB的行锁。mysql
目前,MySQL经常使用的存储引擎是InnoDB,相对于MyISAM而言。InnoDB更适合高并发场景,同时也支持事务处理。咱们经过下面这个案例(坑),来了解行锁和表锁。git
业务:由于订单重复导入,须要用脚本将订单状态为"待客服确认"且平台是"xxx"的数据批量修改成"已关闭"。
说明:避免直接修改订单表形成数据异常。这里用innodb_lock 表演示InnoDB的行锁。表中有三个字段:id,k(key值),v(value值)。表在github上:https://github.com/ITDragonBlog/daydayup/tree/master/MySQL/
步骤:
第一步:链接数据库,这里为了方便区分命名为Transaction-A,设置autocommit为零,表示需手动提交事务。
第二步:Transaction-A,执行update修改id为1的命令。
第三步:新增一个链接,命名为Transaction-B,能正常修改id为2的数据。再执行修改id为1的数据命令时,却发现该命令一直处理阻塞等待中。
第四步:Transaction-A,执行commit命令。Transaction-B,修改id为1的命令自动执行,等待37.51秒。github
总结:多个事务操做同一行数据时,后来的事务处于阻塞等待状态。这样能够避免了脏读等数据一致性的问题。后来的事务能够操做其余行数据,解决了表锁高并发性能低的问题。sql
# 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实际上并无使用索引,行锁升级为表锁也和上面的结论一致。学习
本章重点介绍InnoDB的行锁及其相关的事务知识。若是想了解MySQL的执行计划,请看上一章节。
行锁的劣势:开销大;加锁慢;会出现死锁
行锁的优点:锁的粒度小,发生锁冲突的几率低;处理并发的能力强
加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;固然咱们也能够显示的加锁:
共享锁:select * from tableName where ... + lock in share more
排他锁:select * from tableName where ... + for update
InnoDB和MyISAM的最大不一样点有两个:一,InnoDB支持事务(transaction);二,默认采用行级锁。加锁能够保证事务的一致性,可谓是有人(锁)的地方,就有江湖(事务);咱们先简单了解一下事务知识。
事务是由一组SQL语句组成的逻辑处理单元,事务具备ACID属性。
原子性(Atomicity):事务是一个原子操做单元。在当时原子是不可分割的最小元素,其对数据的修改,要么所有成功,要么所有都不成功。
一致性(Consistent):事务开始到结束的时间段内,数据都必须保持一致状态。
隔离性(Isolation):数据库系统提供必定的隔离机制,保证事务在不受外部并发操做影响的"独立"环境执行。
持久性(Durable):事务完成后,它对于数据的修改是永久性的,即便出现系统故障也可以保持。
更新丢失(Lost Update)
缘由:当多个事务选择同一行操做,而且都是基于最初选定的值,因为每一个事务都不知道其余事务的存在,就会发生更新覆盖的问题。类比github提交冲突。
脏读(Dirty Reads)
缘由:事务A读取了事务B已经修改但还没有提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题。
不可重复读(Non-Repeatable Reads)
缘由:事务A第一次读取最初数据,第二次读取事务B已经提交的修改或删除数据。致使两次读取数据不一致。不符合事务的隔离性。
幻读(Phantom Reads)
缘由:事务A根据相同条件第二次查询到事务B提交的新增数据,两次数据结果集不一致。不符合事务的隔离性。
幻读和脏读有点相似
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据。
数据库的事务隔离越严格,并发反作用越小,但付出的代价也就越大。这是由于事务隔离实质上是将事务在必定程度上"串行"进行,这显然与"并发"是矛盾的。根据本身的业务逻辑,权衡能接受的最大反作用。从而平衡了"隔离" 和 "并发"的问题。MySQL默认隔离级别是可重复读。
脏读,不可重复读,幻读,其实都是数据库读一致性问题,必须由数据库提供必定的事务隔离机制来解决。
+------------------------------+---------------------+--------------+--------------+--------------+ | 隔离级别 | 读数据一致性 | 脏读 | 不可重复 读 | 幻读 | +------------------------------+---------------------+--------------+--------------+--------------+ | 未提交读(Read uncommitted) | 最低级别 | 是 | 是 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 已提交读(Read committed) | 语句级 | 否 | 是 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 可重复读(Repeatable read) | 事务级 | 否 | 否 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 | +------------------------------+---------------------+--------------+--------------+--------------+
查看当前数据库的事务隔离级别:show variables like 'tx_isolation';
mysql> show variables like 'tx_isolation'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+
当咱们用范围条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫作"间隙(GAP)"。InnoDB也会对这个"间隙"加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
Transaction-A mysql> update innodb_lock set k=66 where id >=6; Query OK, 1 row affected (0.63 sec) mysql> commit; Transaction-B mysql> insert into innodb_lock (id,k,v) values(7,'7','7000'); Query OK, 1 row affected (18.99 sec)
危害(坑):若执行的条件是范围过大,则InnoDB会将整个范围内全部的索引键值所有锁定,很容易对性能形成影响。
排他锁,也称写锁,独占锁,当前写操做没有完成前,它会阻断其余写锁和读锁。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4000 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4001' where id=4; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.04 sec)
# Transaction_B mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (9.53 sec)
共享锁,也称读锁,多用于判断数据是否存在,多个读操做能够同时进行而不会互相影响。当若是事务对读锁进行修改操做,极可能会形成死锁。以下图所示。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4; Query OK, 1 row affected (31.29 sec) Rows matched: 1 Changed: 1 Warnings: 0
# Transaction_B mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) mysql> update innodb_lock set v='4002' where id=4; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
经过检查InnoDB_row_lock 状态变量分析系统上的行锁的争夺状况 show status like 'innodb_row_lock%'
mysql> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+
innodb_row_lock_current_waits: 当前正在等待锁定的数量
innodb_row_lock_time: 从系统启动到如今锁定总时间长度;很是重要的参数,
innodb_row_lock_time_avg: 每次等待所花平均时间;很是重要的参数,
innodb_row_lock_time_max: 从系统启动到如今等待最常的一次所花的时间;
innodb_row_lock_waits: 系统启动后到如今总共等待的次数;很是重要的参数。直接决定优化的方向和策略。
1 尽量让全部数据检索都经过索引来完成,避免无索引行或索引失效致使行锁升级为表锁。
2 尽量避免间隙锁带来的性能降低,减小或使用合理的检索范围。
3 尽量减小事务的粒度,好比控制事务大小,而从减小锁定资源量和时间长度,从而减小锁的竞争等,提供性能。
4 尽量低级别事务隔离,隔离级别越高,并发的处理能力越低。
表锁的优点:开销小;加锁快;无死锁
表锁的劣势:锁粒度大,发生锁冲突的几率高,并发处理能力低
加锁的方式:自动加锁。查询操做(SELECT),会自动给涉及的全部表加读锁,更新操做(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也能够显示加锁:
共享读锁:lock table tableName read;
独占写锁:lock table tableName write;
批量解锁:unlock tables;
对MyISAM表的读操做(加读锁),不会阻塞其余进程对同一表的读操做,但会阻塞对同一表的写操做。只有当读锁释放后,才能执行其余进程的写操做。在锁释放前不能取其余表。
Transaction-A mysql> lock table myisam_lock read; Query OK, 0 rows affected (0.00 sec) mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> select * from innodb_lock; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> update myisam_lock set v='1001' where k='1'; ERROR 1099 (HY000): Table 'myisam_lock' was locked with a READ lock and can't be updated mysql> unlock tables; Query OK, 0 rows affected (0.00 sec)
Transaction-B mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> select * from innodb_lock; 8 rows in set (0.01 sec) mysql> update myisam_lock set v='1001' where k='1'; Query OK, 1 row affected (18.67 sec)
对MyISAM表的写操做(加写锁),会阻塞其余进程对同一表的读和写操做,只有当写锁释放后,才会执行其余进程的读写操做。在锁释放前不能写其余表。
Transaction-A mysql> set autocommit=0; Query OK, 0 rows affected (0.05 sec) mysql> lock table myisam_lock write; Query OK, 0 rows affected (0.03 sec) mysql> update myisam_lock set v='2001' where k='2'; Query OK, 1 row affected (0.00 sec) mysql> select * from myisam_lock; 9 rows in set (0.00 sec) mysql> update innodb_lock set v='1001' where k='1'; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES mysql> unlock tables; Query OK, 0 rows affected (0.00 sec)
Transaction-B mysql> select * from myisam_lock; 9 rows in set (42.83 sec)
总结:表锁,读锁会阻塞写,不会阻塞读。而写锁则会把读写都阻塞。
show open tables; 1表示加锁,0表示未加锁。
mysql> show open tables where in_use > 0; +----------+-------------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+-------------+--------+-------------+ | lock | myisam_lock | 1 | 0 | +----------+-------------+--------+-------------+
能够经过检查table_locks_waited 和 table_locks_immediate 状态变量分析系统上的表锁定:show status like 'table_locks%'
mysql> show status like 'table_locks%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Table_locks_immediate | 104 | | Table_locks_waited | 0 | +----------------------------+-------+
table_locks_immediate: 表示当即释放表锁数。
table_locks_waited: 表示须要等待的表锁数。此值越高则说明存在着越严重的表级锁争用状况。
此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合作写为主表的存储引擎。由于写锁后,其余线程不能作任何操做,大量的更新会使查询很可贵到锁,从而形成永久阻塞。
InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁。MySQL这样设计并非给你挖坑。它有本身的设计目的。
即使你在条件中使用了索引字段,MySQL会根据自身的执行计划,考虑是否使用索引(因此explain命令中会有possible_key 和 key)。若是MySQL认为全表扫描效率更高,它就不会使用索引,这种状况下InnoDB将使用表锁,而不是行锁。所以,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
第一种状况:全表更新。事务须要更新大部分或所有数据,且表又比较大。若使用行锁,会致使事务执行效率低,从而可能形成其余事务长时间锁等待和更多的锁冲突。
第二种状况:多表级联。事务涉及多个表,比较复杂的关联查询,极可能引发死锁,形成大量事务回滚。这种状况若能一次性锁定事务涉及的表,从而能够避免死锁、减小数据库因事务回滚带来的开销。
开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发处理能力通常。只需了解一下。
1 InnoDB 支持表锁和行锁,使用索引做为检索条件修改数据时采用行锁,不然采用表锁。
2 InnoDB 自动给修改操做加锁,给查询操做不自动加锁
3 行锁可能由于未使用索引而升级为表锁,因此除了检查索引是否建立的同时,也须要经过explain执行计划查询索引是否被实际使用。
4 行锁相对于表锁来讲,优点在于高并发场景下表现更突出,毕竟锁的粒度小。
5 当表的大部分数据须要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁。
6 为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能。
到这里,Mysql的表锁和行锁机制就介绍完了,若你不清楚InnoDB的行锁会升级为表锁,那之后会吃大亏的。如有打什么不对的地方请指正。若以为文章不错,麻烦点个赞!来都来了,留下你的痕迹吧!