在并发操作中,锁是必不可少的一部分,所以数据库中的锁也是必不可少的。
MySQL锁定机制类型
在不同的存储引擎采取的锁机制不同,mysql中存在三种机制,从小到大依次是:行锁,页锁,表锁;
1.行级锁定
锁的颗粒度很小,每次锁定只锁定一行数据,其他事务可以对其他行进行操作,所以发生所冲突的几率也很小,大大提交了并发性,但是频繁的加锁和解锁对系统性能也有很大消耗,同级锁定也更容易发生死锁。一般采用行级锁定的引擎有innodb引擎和NDB Cluster引擎。
2.页级锁定
每次事务进行操作的时候锁定当前页,并发能力以及消耗系统资源处于行锁和表锁之间,使用页级锁定的引擎有BerkeleyDB储存引擎。
3.表级锁定
事务操作的时候锁定当前表,锁的粒度非常大,对并发很不友好,但是消耗系统资源较少,通常采用表级锁定的引擎:MyISAM,Memory,CSV.
锁的种类
MySQL中有不同的锁种类,他们对不同的操作有不同的锁定范围以及内容。MySQL默认自动提交,所以在平常使用的时候感觉不出来,如果需要测试锁机制需要把自动提交关闭即set autocommit =0;因为在不同的引擎中加锁的粒度不同,会加上不同的锁,比如innodb引擎中会加行锁,即锁定当前操作行,在myisam引擎中会加表锁,即锁定当前操作表。
但是根据执行的sql语句不同又细分为读锁(查询)以及写锁(增删改)。
MySIAM引擎中:
MySIAM中采用悲观锁机制,即会在读写操作的时候都会自动加锁,并且不支持事务。在读操作的时候会加读锁,写操作的时候加上写锁。
读锁(共享锁):加锁后其他事务能正常读取锁定表的数据,但是不能进行写操作,自身事务可以对锁定表进行读操作,但是不能进行写操作,不能对其他表进行任何操作。
写锁(排他锁):加锁后其他事务不能对锁定的表进行任何操作,自身事务可以对锁定的表进行读写操作,不能对其他表进行任何操作。
显式加锁:
#显示加读锁
lock table 表名 read;
#显示加写锁
lock table 表名 write;
#解锁
unlock tables;
InnoDB引擎中:
InnoDB采用乐观锁机制,在读操作的时候不会自动加锁,在写操作的时候会加上排他锁,支持事务,加锁的级别与事务隔离级别直接相关。
隔离级别相关,请移步我另一篇文章:https://blog.csdn.net/huaixiaohai_1/article/details/89923326
加锁方式:
#加共享锁
select * from 表名 where 限制条件 lock in share mode;
#加排他锁
select * from 表名 where 限制条件 for update
注意:在InnDB中排他锁和共享锁与MYISAM中有多不同,在innodb中,在一个事务对一行内容加上共享锁之后,其他事务可以访问锁定的内容,但是不可以对该行内容进行写操作,但是其他事务可以重复锁定(只能用共享锁)该行内容,这样也大大避免了锁冲突。
排他锁,在一个事务在一行内容加上排他锁之后,其他事务可以访问,同样不可以进行写操作,其他事务不能重复锁定该行内容。
innodb中的锁是针对索引加的锁,所以当没有使用索引或者索引失效的时候,会使用表锁。
间隙锁:
间隙锁是行锁的一种特殊情况,是在一个范围内没有的表中没有数值也会被锁定。
#查询id在1-10之间的数据
select * from table where id >1 and id <10;
#好比表中并没有id=4的数据,但是如果我们对id=4的数据进行删除操作,同样不会被删除,会被锁住。
意向锁:
考虑下面一个情况:
会话A对表进行数据操作,锁住了一行数据,会话B对表进行数据操作,由于没有采用索引或索引失效需要锁住整个表,这无疑是一种锁冲突的情况,那在会话B进行申请表锁的时候,数据库要怎么判断这个冲突呢?
1.判断此表是否被其他事务锁住了整张表。
2.判断每行数据是否被其他事务锁住了该行。
因为2中需要去遍历整张表,这无疑是一个非常耗资源的操作,为了更好的解决这个问题就引入了意向锁。
在意向锁存在的情况下,事务需要先去申请意向共享锁,成功后再申请该行的行锁。
上面步骤可改为:
1.不变
2.查看是否存在意向共享锁,如果存在则说明表中有些数据被锁定了,则会阻塞加锁操作。
申请意向锁是数据库自动执行的,无需程序员用代码去实现。
查询加锁情况:
show open tables;
行锁分析:
show status like '%innodb_row_lock%';
参数1:当前正在等待的锁的数量;
参数2:等待总时长,从系统启动到现在;
参数3:平均等待时长;
参数4:最大的一次等待时间;
参数5:等待的次数;