mysql锁
事务的4个基本特征 :
Atomic(原子性):
事务中包括的操作被看做一个逻辑单元。这个逻辑单元中的操作要 么所有成功。要么所有失败。
Consistency(一致性):
仅仅有合法的数据能够被写入数据库,否则事务应该将其回滚到最初 状态。
Isolation(隔离性):
事务同意多个用户对同一个数据进行并发訪问,而不破坏数据的正 确性和完整性。同一时候。并行事务的改动必须与其它并行事务的改动
相互独立。
Durability(持久性):
事务结束后。事务处理的结果必须可以得到固化。
-
一些名词
多个事务并发会产生一些问题:
脏读:可以读取到其他事务修改但未提交的脏数据。=》事务A读取了事务B已经修改但尚未提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题
不可重复读:在一个事务中重复读取相同数据。在其中两次读取数据之间有另一个事务修改并提交了该数据。使得事务两次读到的数据是不一样的。=》事务A第一次读取最初数据,第二次读取事务B已经提交的修改或删除数据。导致两次读取数据不一致。不符合事务的隔离性。
幻读: 第一个事务对一个表中的数据进行了修改,这种修改涉及 到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有 修改的数据行,就好象发生了幻觉一样。=》事务A根据相同条件第二次查询到事务B提交的新增数据,两次数据结果集不一致。不符合事务的隔离性
-
幻读和脏读有点类似
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据
-
丢失更新: 多个用户同时对一个数据资源进行更新,必定会产生被覆盖的数据,造成数据读写异常。
例子:假定有数据表
==student==
id | name
1 | 张三
a.脏读
事务A:select name from student where id=1;
事务B:update student set name='李四' where id=1;不提交
结果:可能是“李四”
读已提交:避免读取未提交数据。
b.不可重复读
事务A:select name from student where id=1;
select name from student where id=1;
事务B:update student set name='李四' where id=1;提交
结果:第一次读到“张三”,第二次可能读到“李四”
可重复读:避免事务B修改id为1的数据。但是事务B可以向表中新增数据李四
c.幻读
事务A:select name from student;
select name from student;
事务B:insert into student values(default,'李四');提交
结果:第一次读到“张三”,第二次可能读到"张三"和"李四"
串行化读:每次读都需要获得表级共享锁,读写相互都会阻塞。可避免幻读。
各种隔离级别与各种读的关系:
数据库隔离级别:MySQL默认的事务隔离级别(可以重复读:repeatable read)
查看当前数据库的事务隔离级别:show variables like ‘tx_isolation’;
读未提交:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
读提交:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
可重复读: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
序列化:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行
设置mysql的隔离级别:set session transaction isolation level 设置事务隔离级别
设置read uncommitted级别:set session transaction isolation level read uncommitted;
查看设置结果:SELECT @@tx_isolation;
设置read committed级别:set session transaction isolation level read committed
查看设置结果:SELECT @@tx_isolation;
设置repeatable read级别:set session transaction isolation level repeatable read;
查看设置结果:SELECT @@tx_isolation;
设置serializable级别:set session transaction isolation level serializable
查看设置结果:SELECT @@tx_isolation;
悲观锁(Pessimistic Lock), 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适
悲观锁
在悲观锁的情况下,为了保证事务的隔离性,就须要一致性锁定读。
读取数据时给加锁,其他事务无法改动这些数据。
改动删除数据时也要加锁,其他事务无法读取这些数据。
乐观锁
相对悲观锁而言,乐观锁机制採取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性
。但随之而来的就是数据库性能的大量开销。特别是对长事务而言,这种开销往往无法承受。
而乐观锁机制在一定程度上攻克了这个问题。乐观锁,大多是基于数据版本号( Version )记录机制实现。何谓数据版本号?即为数据添
加一个版本号标识,在基于数据库表的版本号解决方式中,通常是通过为数据库表添加一个 “version” 字段来实现。
读取出数据时,将此版本号号一同读出,之后更新时,对此版本号号加一。
此时。将提交数据的版本号数据与数据库表相应记录的当前版本号信息进行比对,假设提交的数据版本号号大于数据库表当前版本号号,
则予以更新。否则觉
更新语句设置版本号,在指定版本中更新数据
一方:update account set money=money-200,version=version+1 where id=1 and version=0;
另一方操作同一个版本号,则不能更新数据
另一方:update account set money=money+200,version=version+1 where id=1 and version=0;
如果更新多,查询少,用悲观锁;反之,乐观锁
表级锁,where用的是非主键
行级锁,where用主键一般是id
如果用表级锁,其他客户将不能进行查询操作,因此开发中记得用行级锁
以mysql为例,有索引并且使用了该索引当条件的时候就是行锁,没有索引的时候就是表锁。innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.
行级锁,一般是指排它锁,即被锁定行不可进行修改,删除,只可以被其他会话select。行级锁之前需要先加表结构共享锁。
表级锁,一般是指表结构共享锁锁,是不可对该表执行DDL操作,但对DML操作都不限制。
行级锁之前需要先加表结构共享锁。
在事务中通过select for update语句给sid = 1的数据行上了锁。右边的线程此时可以使用select语句读取数据,但是如果也使用select for update语句,就会阻塞,使用update,add,delete也会阻塞,当左边的线程将事务提交(或者回滚),右边的线程就会获取锁,线程不再阻塞
mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。(一个事务获取了共享锁之后,可以对锁定范围内的数据执行读操作。)
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。(一个事务获取了排它锁之后,可以对锁定范围内的数据执行写操作)
对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select ...for update语句,加共享锁可以使用select ... lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。
锁的分类
①从对数据操作的类型(读\写)分
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
②从对数据操作的粒度分
表锁
行锁
页面锁
适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;
而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
2.表级锁定(table-level)
特点:偏向MyISM存储引擎,开销小,加锁块;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
3.行级锁定(row-level)
特点:偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度最高。
InnoDB与MyISAM最大不不同有两点,一是支持事务,二是采用了行级锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。
但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。
2.InnoDB行锁实现方式
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
使用LOCK TABLES虽然可以给InnoDB加表级锁,在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。
(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。
(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,
比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
InnoDB 自动给修改操作加锁,给查询操作不自动加锁
行锁可能因为未使用索引而升级为表锁,所以除了检查索引是否创建的同时,也需要通过explain执行计划查询索引是否被实际使用。
表级锁:
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。特点是开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
行级锁:
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。特点是开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页级锁:
表级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。特点是开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
MySQL常用存储引擎的锁机制:
MyISAM和MEMORY采用表级锁(table-level locking);
BDB采用页面锁(page-level locking)或表级锁,默认为页面锁;
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁;
MyISAM与InnoDB引擎的锁机制区别:
MySQL在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB 存储引擎。
查看当前存储引擎的语句:
show variables like '%storage_engine%';
MyISAM 操作数据都是使用表级锁,MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。所以不会产生死锁,但是由于每操作一条记录就要锁定整个表,导致性能较低,并发不高。InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。在Mysql中,行级锁并不是直接锁记录,而是锁索引。InnoDB 行锁是通过给索引项加锁实现的,索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表级锁一样。
---------------------
作者:一骑走烟尘
来源:CSDN
原文:https://blog.csdn.net/zgcr654321/article/details/82345087
版权声明:本文为博主原创文章,转载请附上博文链接!