锁机制
三大锁
表锁、行锁、页锁
锁类型。
- MyISAM 和 Memory 存储引擎使用的是表级锁,BDB 引擎使用的是页级锁,也支持表级锁。由于 BDB 引擎基本已经成为历史,因此就不再介绍了。
- InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁。
- 表级锁,它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。
- 行级锁,它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。
- 页级锁,它是锁住的一个页面,在 InnoDB 中一个页面为16KB,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁定粒度也介于表级锁和行级 锁中间,并发度也介于表级锁和行级锁中间。
- 仅仅从锁的角度来说,表级锁更加适合于以查询为主的应用,只有少量按照索引条件更新数据的应用
- 行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用
InnoDB有两种类型的行级锁,两种内部使用的意向锁;
共享锁(S):允许一个事务读一行数据时,阻止其他的事务读取相同数据的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据的共享锁和排他锁。 意向共享锁(IS):事务打算给数据行加行共享锁。事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁。事务在给一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是InnoDB存储引擎自动加的,对于普通select语句,InnoDB不会加任何锁,对于insert,update,delete语句,InnoDB会自动改涉及的数据加排他锁,
InnoDB以通过以下语句显示添加的共享锁和排他锁。
共享锁语句select * from table_name lock in share mode;
排他锁语句select * from table_name for update;
加上共享锁的时候,排他锁就会等待
排它锁:锁住的这一条数据不能被别的锁而操作(读和写)
共享锁:锁住的这条数据可以被 共享锁操作(读)
都不会影响普通的查询
insert、update、delete 默认都会加上排它锁,普通查询不加锁
死锁
死锁是由于两个或以上的线程互相持有对方需要的资源,导致这些线程处于等待状态,无法执行。
共享锁 只可读,不可写 与任和锁一起使用都不会冲突(除了排它锁)
排它锁 不可读也不可写 与任何锁一起使用时都会冲突
产生死锁的四个必要条件
- 互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。
- 请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。
- 不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。
- 循环等待:发生死锁时,线程进入死循环,永久阻塞。
对于锁的建议
收集死锁信息:
利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
调试阶段开启 innodb_print_all_deadlocks,收集所有死锁日志。
减少死锁:
使用事务,不使用 lock tables 。
保证没有长事务。
操作完之后立即提交事务,特别是在交互式命令行中。
如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE),尝试降低隔离级别。 修改多个表或者多个行的时候,将修改的顺序保持一致。
创建索引,可以使创建的锁更少。
最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
InnoDB 行锁、表锁、innodb间隙锁
InnoDB 行级锁是通过给索引上的索引项加锁来实现的,InnoDB行级锁只有通过索引条件检索数据,才使用行级锁;否则,InnoDB使用表锁 在不通过索引(主键)条件查询的时候,InnoDB是表锁而不是行锁。
在没有使用索引的情况下InnoDB就会使用表级锁(共享锁不会有这个情况)
如果查询的条件没有带索引,那么行锁则会转为表锁即时表中字段有主键;所以在查询的时候建议使用索引字段查询
innodb间隙锁
可以理解为是对于一定范围内的数据进行锁定,如果说这个区间没有这条数据的话也是会锁住的;主要是解决幻读的问题,如果没有添加间隙锁,如果其他事物中
添加id在1到100之间的某条记录,此时会发生幻读;另一方面,视为了满足其恢复和赋值的需求。
默认情况下,innodb_locks_unsafe_for_binlog是0(禁用),这意味着启用了间隙锁定:InnoDB使用下一个键锁进行搜索和索引扫描。若要启用该变量,请将其设 置为1。
事务
回滚日志
想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的
修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。
重做日志
与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。
回滚日志(undo log)和重做日志(redo log);在数据库系统中,事务的原子性和持久性是由事务日志 (transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保 证两点:
- 发生错误或者需要回滚的事务能够成功回滚(原子性);
- 在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性);
事务的隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
1 | show variables like '%ios%' |
脏读(一个事务读取到另一个事务未提交的数据)
不可重复读(一个事务读取到另一个事务已经提交的数据)
幻读(一个事务多次查询整表数据,由于其他事务新增(删除)记录造成多次查询的记录条数不同(一个事务读取到另一个事务已经提交的数据))
事务不会自动回滚。
对于事务的建议 innodb存储引擎由于实现了行几所,颗粒更小,实现更复杂。但是innodb行锁在并发性能上远远要高于表锁页锁。在使用方面可以尽量做到以下几点;
- 控制事务大小,减少锁定的资源量和锁定时间长度。
- 人所有的数据检索都通过索引来完成,从而避免因为无法通过索引加锁而升级为表锁。
- 减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
- 在业务条件允许下,尽量使用较低隔离级别的事务隔离。减少隔离级别带来的附加成本。
- 使用索引,让innodb在索引上面加锁的时候更加准确。
- 在应用中尽可能做到访问的顺序执行