在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题, 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。锁是影响数据库并发访问性能的一个重要因素,因此锁对数据库而言非常重要。
一、锁的分类
1、从对数据操作的颗粒度来分:
行锁:
优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强
劣势:开销大,加锁慢,会出现死锁
表锁:
优势:开销小,加锁快,无死锁
劣势:锁粒度大,发生锁冲突的概率高,并发处理能力低
页锁:
开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般,了解一下即可;
2、行锁的加锁方法
读锁(共享锁):当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上写锁,但允许上读锁。
写锁(排它锁):当一个事务对某几行上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。
二、死锁
锁冲突和死锁不是一个概念,锁冲突会按顺序执行,死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,需借助外力才能解锁,业务系统数据库出现死锁,可能导致整个系统无法使用,亲身惨痛经历!
死锁的快速解决办法:
1.查看下在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2.杀死进程id(就是上面命令的trx_mysql_thread_id列)
kill 线程ID
三、mysql常用存储引擎
1、MyISAM存储引擎:开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发最低,使用表锁;
2、InnoDB存储引擎:开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高,支持事务;使用行锁,但如果不能确定要 扫描的范围,InnoDB表同样会锁全表, InnoDB 是mysql的默认引擎;
*InnoDB与MyISAM的最大不同有两点:一是支持事务,二是采用了行级锁
三、事务
1、事务的ACID属性
原子性:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行,在操作失败后,也不能对数据库中的数据有任何影响;
一致性:事务执行前与执行后数据内在的逻辑始终是成立的,比如转账前与转账后两人存款的总和始终不变;
隔离性:两个事务独立执行互不干扰
持久性:事务完成后,它对数据的修改是永久的,即使数据库系统出现故障,也能保持数据的正确性
2、多线程并发事务处理带来的问题
更新丢失:
多个事务处理同一行数据,事务之间并不知道有其他事务已经在处理这行数据,最后的更新覆盖了其他事务之前的更新,发生更新丢失。
举个例子:
俩人处理同一个java文件,修改完之后直接提交,直接覆盖掉了上一个人所做的修改;
脏读:
一个事务A在处理过程中读取了另一个事务B中未提交的数据。
举个例子:
用户A向用户B转账100元,对应SQL命令如下:
update account set money=money+100 where name=’B’; (此时A通知B)
update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
不可重复读:
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读:
一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。
注:
脏读:读取到修改但未提交的数据
不可重复读:读取到修改且已提交的数据
幻读:读取到新增且已提交的数据
3、 事务隔离机制
并发处理带来的问题中,更新丢失可以完全避免,由应用对数据加锁即可。脏读、不可重读度、幻读,其实都是数据库的一致性问题,必须由一定的事务隔离机制来解决。性能高和事务完全隔离性无法同时满足,DB提供了4项事务隔离级别来权衡,需要用户根据自身业务来选择合适的隔离级别。隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。我们的数据库锁,就是为了构建这些隔离级别存在的。mysql默认的隔离级别为可重复读。
四、锁的优化技巧
1、尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁;
2、尽可能较少检索条件,避免间隙锁;
间隙锁:
间隙锁是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制,幻读的问题存在是因为新增操作,会出现不一致的问题,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。间隙锁之间不是互斥的,如果一个事务A获取到了(5,10]之间的间隙锁,另一个事务B也可以获取到(5,10]之间的间隙锁。这时就可能会发生死锁问题
3、尽量控制事务大小,减少锁定资源量和时间长度;
4、尽可能低级别事务隔离;