数据库并发策略

并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳。

乐观锁

乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;时间戳就是不加锁,通过时间戳来控制并发出现的问题。

悲观锁

悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问那部分数据。

时间戳

时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁机制,但是这种方法可以大大提高数据库处理的并发量,
以上悲观锁所说的加“锁”,其实分为几种锁,分别是:排它锁(写锁)和共享锁(读锁)。

数据库锁

行级锁
行级锁是一种排他锁,防止其他事务修改此行;在使用以下语句时,Oracle 会自动应用行级锁:

  1. INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];
  2. SELECT … FOR UPDATE 语句允许用户一次锁定多条记录进行更新
  3. 使用 COMMIT 或 ROLLBACK 语句释放锁。

表级锁

表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分 MySQL 引擎支持。最常使 用的 MYISAM 与 INNODB 都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁 (排他锁)。

页级锁

页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB 支持页级锁

基于 Redis 分布式锁

  1. 获取锁的时候,使用 setnx(SETNX key val:当且仅当 key 不存在时,set 一个 key
    为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0)加锁,锁的 value
    值为一个随机生成的 UUID,在释放锁的时候进行判断。并使用 expire 命令为锁添
    加一个超时时间,超过该时间则自动释放锁。
  2. 获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取
    锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。

MyISAM与InnoDB的锁的区别

背景知识:
MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM所改良。虽然性能极佳,但却有一个缺点:不支持事务处理(transaction)。
InnoDB,是MySQL的数据库引擎之一,现为MySQL的默认存储引擎,为MySQL AB发布binary的标准之一。

MyISAM默认用的是表级锁,不支持行级锁
InnoDB默认用的是行级锁,也支持表级锁

一般来说,MyISAM 的读锁是共享锁,写锁是排它锁。对表A而言,进程1给表A加了共享锁,进程2只能对表A加共享锁;若进程1加了排它锁,那进程2只能等待进程1解锁后才能查询或加锁。
InnoDB采用的是二段锁,即加锁和解锁(commit,数据库默认打开自动提交);但是在非SQL加共享锁时,若没改变某行数据,那另一进程亦可修改该行记录。InnoDB的SQL没用到索引时,用的是表级锁;用到时则是行级锁。

加行锁方式:
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE 解锁 COMMIT

MyISAM适合的场景

  1. 频繁执行全表count语句。
  2. 对数据进行增删改的频率不高,查询非常频繁。
  3. 没有事务

InnoDB适用场景

  1. 数据库增删改查都相当频繁。
  2. 可靠性要求比较高,要求支持事务。

悲观锁:在操作数据的时候,认为此操作会发生数据冲突,所以在进行每次操作时都要通过获取锁才能进行相同数据的操作。与Java的synchronized很相似,所以悲观锁需要耗费较多的时间。
乐观锁:操作数据库时,认为此操作不会导致数据冲突,在操作数据时,不进行加锁处理,在进行更新后,再去判断是否有冲突。例如hibernate中的乐观锁两种实现,就是分别基于version和timestamp来实现的。例如:update user set name = ‘John’ ,version=version+1 where id =13 and version = #{version}; 注:update语句完成后需要commit操作的,相当于一个update语句就是一个原子操作,所以不会出现两条update能同时更新的情况
参考:

锁的分类

锁的粒度:表级锁,行级锁,页级锁;
锁级别:共享锁,排它锁;
加锁方式:自动锁(InnoDB自动加锁)、显示锁(人为在SQL后加的锁);
操作划分:DML锁(数据修改涉及的锁)、DDL锁(表结构修改涉及的锁);
使用方式:乐观锁、悲观锁。