当前,Mysql数据库使用的锁机制有三种类型:行级锁定,页级锁定和表级锁定。
表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许。
行级, 仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
页级,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
根据不同的存储引擎,MySQL中锁的特性可以大致归纳如下:
行锁 | 表锁 | 页锁 | |
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
MyISAM表锁
MySQL的表锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写请求;MyISAM表的读和写操作之间,以及写和写操作之间是串行的!(当一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。)
如何加表锁
InnoDB锁
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
- 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
- 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
2.并发事务处理带来的问题。在数据库中,当不止一个查询同时修改数据时,会产生并发控制问题--并发读和并发写。主要有以下几种:
1. 丢失更新。
2. 脏读。
3. 不可重复读。
4. 幻像读。
解决并发问题的手段就是锁机制,对正在操作的数据加锁,限制其他事务对该记录进行读或写。
分为读锁和写锁:
读锁是共享的,在同一时间,多个用户可以读取同一资源,而互不干扰。
写锁是排他的,一个写锁会阻塞其他的读锁和写锁,在给定时间里,只有一个用户能写入资源,以防止用户在写操作的同时其他用户读取同一资源。
各种并发问题加锁的策略是不同的,具体分析如下:
1:丢失更新。
e.g.事务A和事务B同时修改某行的值,
- 事务A将数值改为1并提交
- 事务B将数值改为2并提交。(这时数据的值为2,事务A所做的更新将会丢失。)
解决方法是加锁,有悲观锁和乐观锁两种:(都是行级锁)
1:悲观锁。
在包含修改操作的事务中,先使用select ……for update nowait进行查询, 通过添加for update nowait语句,将这条记录锁住,避免其他用户更新,从而保证后续的更新是在正确的状态下更新的。然后在保持这个链接的状态下,再做修改操作,最后提交事务。
2:乐观锁(版本法)
在表上加一个属性列作为版本列,其数值表示最新的版本号,这一列的数据类型可以是NUMBER或 DATE/TIMESTAMP,用来记录这条数据的版本。在应用程序中我们每次操作对版本列做修改即可。在更新时我们把上次版本作为条件进行更新。在对一行进行更新的时候 限制条件=主键+版本号,同时对记录的版本号进行更新。
e.g.
update table set version = old_verison + 1 ,new_value = old_value+1 where primary_key = ? and version = old_version
2:脏读。
当一个事务读取另一个事务尚未提交的修改时,产生脏读。
e.g.
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000
像这样,Mary记取的工资数8000是一个脏数据。
解决办法:如果在第一个事务提交前,对数据进行加锁,任何其他事务不可读取其修改过的值,则可以避免该问题。
3:不可重复读。
同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。e.g.
- 在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
- 在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
- 在事务1中,Mary 再次读取自己的工资时,工资变为了2000
解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。
4:幻象读。
同一查询在同一事务中多次进行,由于其他提交事务所做的插入、修改操作,每次返回不同的结果集,此时发生幻像读。当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中,因为该行已被其它事务删除。同样,由于其它事务的插入操作,事务的第二次或后续读显示有一行已不存在于原始读中。
【区分脏读:脏读是由于多次读取同一条数据,而该数据被其他事务修改而导致多次读取数据结果不同。
幻象读:则是由于根据某一条件进行多次查询时,其他事务插入或删除了符合条件的一些记录,导致多次查询返回的记录数不同。】
e.g.目前工资为1000的员工有10人。
- 事务1,读取所有工资为1000的员工。
- 这时事务2向employee表插入了一条员工记录,工资也为1000。
- 事务1再次读取所有工资为1000的员工 共读取到了11条记录。
解决办法:如果在操作事务完成数据处理之前(插入、删除),任何其他事务都不可以添加新数据,则可避免该问题。
- 第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
- 第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。