就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
需要使用数据库的锁机制,如行锁中的共享锁(S锁)、互斥锁(X锁),还有意向锁(IS锁、IX锁)
select * from user where name=”zc” for update
这条 sql 语句锁定了user 表中所有符合检索条件(name=”zc”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
使用自增长的整数表示数据版本号。更新时检查版本号是否一致,比如数据库中数据版本为1,更新提交时version=1+1,使用该version值(=2)与数据库version+1(=2)作比较,如果相等,则可以更新,如果不等则有可能其他程序已更新该记录,所以返回错误。
使用时间戳来实现.
不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
乐观锁相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整。
参考:
并发控制中的乐观锁与悲观锁
除了悲观、乐观并发控制,数据库系统引入了另一种并发控制机制 - 多版本并发控制(Multiversion Concurrency Control),每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回;在这时,读写操作之间的冲突就不再需要被关注,而管理和快速挑选数据的版本就成了 MVCC 需要解决的主要问题。
MVCC 并不是一个与乐观和悲观并发控制对立的东西,它能够与两者很好的结合以增加事务的并发量。
参考:
浅谈数据库并发控制 - 锁和 MVCC
MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM存储引擎的读锁和写锁是互斥的,MyISAM表的读操作与写操作之间,以及写操作之间是串行的
图片来自:
MySQL InnoDB锁机制全面解析分享
InnoDB通过索引添加共享锁、排他锁
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁(InnoDB 的意向锁有什么作用?)。
举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:
Select * from emp where empid > 100 for update;
是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使 用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需 要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况,在后续的章节中会做进一步介绍。
很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!
参考:
MySQL中的锁(表锁、行锁,共享锁,排它锁,间隙锁)
五分钟了解Mysql的行级锁——《深究Mysql锁》
MySQL行锁与表锁
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
1、快照读:简单的select操作,属于快照读,不加锁。(已经加了排他锁也可以这样查询记录)
select * from table where ?;
2、当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
下面语句都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
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 ?;
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系
统版本号(System version number),每开始一个新的事物,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
下面来看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。
InnoDB会根据一下两个条件检查每行记录:
1、InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样就可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或修改过的。
2、行的删除版本号要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述这两个条件的记录,才能返回查询结果。
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
InnoDB为插入一行新纪录保存当前系统版本号作为行版本号。同时保存当前系统版本号到原来的行作为行删除标识。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容。因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行;而SERIALIZABLE则会对所有读取的行加锁。
参考:
MySQL InnoDB锁机制全面解析分享
MySQL InnoDB引擎,写数据前,先把当前数据写到Undo日志,便于多版本控制或者回滚保证事务的原子性,之后把更新后的数据写入Redo日志,产生脏页,后面由主线程写入磁盘。写的时候为了避免redo日志写一半出现故障,进行了二次写的设计,先写入二次写缓存,然后在写入磁盘。如果写磁盘出现故障,也可以从二次写缓存中恢复过来。
MySql每行记录有三个隐藏字段,通过指向回滚版本的字段或删除版本标识,实现多版本的读取。
MVCC多版本快照由innodb的rollback segment构照的,一个sql进行查找数据当查找到某一个数据需要到回滚段中查找数据时,就会根据当前页上行数据的一个指针到回滚段中查找对应数据,在innodb的表主键中都会存在三个隐藏的字段:
在一个sql进行查询时,读取到一行数据的DB_TRX_ID值和自己事物ID的对比,假如隔离级别为MySQL的默认级别,就只读取该ID值小于本身事物ID的数据,其余数据就需要通过DB_ROLL_PTR的信息到回滚段中读取。MVCC是否起到相应的作用需取决于数据库隔离级别的配置。
参考:
InnoDB的多版本并发控制机制—— MVCC底层实现
mysql 之mvcc多版本控制
innodb的多版本控制
参考:
mysql Innodb在RR级别如何避免幻读
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程.
阻止死锁的途径就是避免满足死锁条件的情况发生,为此我们在开发的过程中需要遵循如下原则:
解除死锁的两种方法:
(1)终止(或撤销)进程。终止(或撤销)系统中的一个或多个死锁进程,直至打破循环环路,使系统从死锁状态中解除出来。
(2)抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以打破死锁状态。
摘自:
MySQL产生死锁的根本原因及解决方法
解除正在死锁的状态有两种方法:
第一种:
1.查询是否锁表
show OPEN TABLES where In_use > 0;
2.查询进程(如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程)
show processlist
3.杀死进程id(就是上面命令的id列)
kill id
第二种:
1.查看下在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2.杀死进程id(就是上面命令的trx_mysql_thread_id列)
kill 线程ID
其它关于查看死锁的命令:
1:查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
摘自:
mysql查看死锁和解除锁
参考:
死锁产生原因和解决办法
参考:
MySQL锁机制及优化