从事软件领域研发,只有涉及到数据领域,或者跟数据库打交道,或者NoSQL或者大数据领域下,都离不开事务的应用场景,通过对应的锁设计思路来解决并发的问题。
首先关注数据库的事务级别
1:Read unommitted(也称之为脏读)
事务隔离的最低级别,比如A修改了一个数据,还没有提交,而另外一个用户B就读取都到了。
这种场景没有实际的应用价值,因为没有做到ACID原则的Consistent reads(一致性)
2:Read committed
这个级别是Oracle默认的支持的隔离级别,起始也是保证数据一致性的基本。
用于B读取到的数据,必须是其它用于已经提交的数据。
在这种隔离级别下,可能会出现这种场景:
A用户读取数据D, B用户准备修改数据D,准备修改为D1
在B修改提交之前,A读到了数据D
然后B提交了修改结果,修改为D1
然后A再一次读取D,就得到了结果D1
该种场景为:Non-Repeatable Reads(不可重复读)
另外一种可能的场景为Phantom Reads(幻想读)
这种隔离级别在Mysql的InnoDB的存储引擎中,由于支持的为行级别锁定,如果通过
select ... for update或者select ... Lock in Share Mode.
此时如果仅仅通过锁定索引记录而不锁定之前的间隙,会允许锁定记录后,仍然可以自由的插入新纪录。
此时如果通过索引不能够准确定位到需要锁定的记录,就必须依赖升级为表级别的锁定,或者设置next-key或gap-locks来阻塞其他用户的空隙插入。
幻想读就会产生。
3:REPAETABLE READ
而这个级别为InnoDB的默认事务隔离级别
类似SELECT ... FOR UPDATE,SELEC ... LOCK IN SHARE MODE,UPDATE ,DELETE
如果能够以唯一条件或者唯一索引,只锁定所需的记录时,而不锁定该索引之前的间隙。
否则的话,就通过next-key或者gap locks来锁定所需要的资源,来阻塞其他用户的新建插入。
该事务级别中:同一事务的所有的Consistent Reads均读取第一次读取时已确定的快照。
该事务级别:不会出现脏读、不可重复读,但还是会有幻想读。
4:SERIALIZABLE
最高级别事务隔离,事务中的任何时刻,所看到的数据都是事务启动时的状态。
在此级别,不会出现幻想读。InnoDB也支持到了。
接下来需要思考:
事务的目的是为了保证对共享数据的并发访问能顾保持一致性好完整性,理想情况下,事务级别越高越好;
但是针对系统的开销也越大,为了实现高级别的事务隔离,数据库需要通过一系列的锁机制来保证并发处理数据的操作不能够相互
覆盖;
这里自然需要关联到数据库的锁的类型,锁的类型跟数据库的模型有关;
比如MYSQL支持的三种类型的锁:
表级锁:
粗粒度,对整个表上锁,MyISAM存储引擎的支持规则(还包括Memory,CSV),支持并发度低。
行级锁:
细粒度,对行上锁,InnoDB的支持(NDB cluster),支持并发度高。
页级锁:
粒度适中,为BerkeleyDB存储引擎支持。
对于表级的锁定,可以分为:
读锁、写锁
内部通过
Curren readLock Queue和Pending ReadLock Queue
Curren writeLock Queue和Pending writeLock Queue
来维持。
针对列级的锁定,除了读写之外,还可以细分:
共享锁、排他锁、意向共享锁、意向排他锁
而InnerDB作为实现行锁,锁定的粒度在行级别,实现机制对比一下;
oracle:通过在需要锁定的某行记录所在的物理Block上的事务槽上面添加锁定信息
InnoDB:通过在指向数据记录的索引键之前和最后一个索引键之后的空域空间标记锁定信息来实现。
实现原理的差别:可能为导致InnoDB如果在通过索引无法定位记录或者无法锁定范围时,会升级为表级锁定。
同时还有考虑:这种间隙锁(Next-key locking)的弱点:
当锁定在一定范围时,某些不存在的键值也会被无辜锁定,即导致锁定时,无法插入锁定范围内的任何数据。
InnoDB的解释可以为:阻止幻想读。
前面引入了锁,如果出现死锁,数据库如何快速识别死锁,并且如何解决死锁的问题
当然这个没有一个很好的快速解决方式,需要综合考虑;
MyISAM引擎提供的表锁定:由于粒度过大,降低了并发处理能力,如何优化呢:
a:尽可能减少业务的锁定表的时间,来减少死锁或者锁等待的时间
b:分类页的读写,支持并行操作
MyISAM引擎支持一个特征为Concurrency Insert,当配置为1,2时,支持并发写入。
针对conurrency_insert=1的规则为:当存储引擎表数据文件中间不存在空闲空间时,可以从文件尾部进行Concurrency Insert
conurrency_insert=2的规则为:无论当存储引擎表数据文件中间是否不存在空闲空间时,可以从文件尾部进行Concurrency Insert
c:合理利用读写优先级,针对读和写锁还可以设置对应的优先级,可以根据系统特征,优先保障写锁定完成,或者优先保障读锁定完成。
可以同table_locks_immediate和table_locks_waited来查看
而针对InnoDB的行级别锁定,如何优化呢:
a:由于数据的锁定机制依赖索引,索引数据的查找尽可能通过索引来完成
b:注意索引的设计,避免由于无法通过索引定位数据,而上升为表级别锁定
c:当然也避免间隙锁带来的阻止幻想读
d:控制事务力度不要过大,减少所等待时间
e:合理设计业务应用,尽可能不要产生死锁
f:针对事务的锁定,一次性锁定所需的所有资源
可以通过innodb_row_lock_current_waits
innodb_row_lock_time
innodb_row_lock_time_avg
innodb_row_lock_time_max
innodb_row_lock_waits来查看。