锁是计算机协调多个进程或线程并发访问某一资源的控制机制。在数据库中,除了计算资源(CPU、I/O)的争用以外,数据也是一种多用户共享的资源。如何保证数据并发访问的一致性和有效性是全部数据库必须解决的一个问题。相对于其余数据库而言,mysql的锁机制比较简单,最显著的特色是不一样的存储引擎支持不一样的锁机制。好比MyISAM和MEMORY采用表级锁;BDB存储引擎采用页面锁,但也支持表级锁;InnoDB存储引擎采用行级锁,也支持表级锁,默认状况使用行级锁。三种锁的特色以下:mysql
Mysql表锁有两种模式:表共享读锁(table read lock)和表独占写锁(table write lock)。对表的读操做不会阻塞其余用户对同一表的读操做,但会阻塞对同一表的写请求;对表的写操做,会阻塞其余用户对同一表的读和写操做。
准备数据:sql
CREATE TABLE `test` (
`id` bigint(20) NOT NULL auto_increment,
`name` varchar(100) NOT NULL,
`age` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='测试表';
复制代码
一、用户1:lock table test write; 给test表加独占写锁 二、当前用户1对test表的read,update和insert均可以执行 数据库
一、用户1给test表加共享读锁bash
用户1更新报错: 并发
用户1查询其余表报错: oracle
能够经过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:若是table_lock_waited的值比较高,则说明存在较严重的表级锁争用状况。测试
上面提到MyISAM表的读和写是串行的,但这是就整体而言。在必定条件下,MyISAM也支持查询和插入的并发执行。MyISAM存储引擎有一个系统变量concurrent_insert,专门用来控制并发插入的行为,能够取值0,1,2;优化
举例:
一、用户1对test表加共享读锁(注意这里要用read local)ui
可是即便用户2插入成功,可是用户1仍是只能查到加锁以前的记录,查询不到用户2新插入的记录spa
InnoDb行锁是经过给索引上的索引项加锁来实现的,这一点mysql与oracle不一样,oracle是经过在数据块中对相应数据行加锁来实现的。InnoDb的这种行锁实现特色意味着:只有经过索引条件检索数据,InnoDb才会使用行级锁,不然InnoDb将使用表锁。 举个例子:
create table test_no_index
(
id bigint(20) not null,
name varchar(50) not null
) ENGINE = INNODB COMMENT ='测试innoDb的行锁实现方式';
insert into test_no_index(id,name) values(1,'1');
insert into test_no_index(id,name) values(2,'2');
复制代码
一、当用户1查询id=1的记录并对这行记录加上排他锁(要把自动提交关掉)
alter table test_no_index add index id(id);
复制代码
用户1成功锁定id=1的记录
当咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDb会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫作间隙(GAP),InnoDb也会对这个“间隙”加锁,这种机制就是所谓的“间隙锁”。
例如:假如emp表中有101条数据,id的值分别是1,2,...,100,101,下面的sql:
select * from emp where id>100 for update;
复制代码
是一个范围条件的检索,InnnoDb不只会对符合条件的id=101的记录加锁,也会对id>101的间隙加锁。
InnoDb使用间隙锁的目的,一方面是为了防止幻读,以知足隔离级别的要求。对于上面的例子,要是不使用间隙锁,若是其余事务插入了id>100的任何记录,那么本事务若是再次执行上述语句,就会产生幻读。另外一方面,是为了知足其恢复和复制的须要。
显然,在使用范围条件检索并锁定记录时,InnoDb这种锁机制会阻塞符合条件范围内键值的并发插入,这每每会形成严重的锁等待。所以,在实际应用开发中,尤为是并发插入比较多的应用,要尽可能优化业务逻辑,尽可能使用相等来访问和更新数据,尽可能避免使用范围条件。
首先,悲观锁和乐观锁都是一种思想,并非真实存在于数据中的一种机制。
当认为数据被并发修改的概率比较大,须要在修改以前借助于数据库的锁机制对数据进行加锁的思想称为悲观锁,又称PCC(Pessimisic Concurrency Control)。在效率方面,处理锁的操做会产生额外的开销,并且增长了死锁的机会。当一个线程在处理某行数据的时候,其它线程只能等待。 ** 实现方式 ** 悲观锁的实现依赖数据库的锁机制,流程以下:
set autocommit=0;
//事务开始
begin;
//查询商品在某个仓库的库存
select * from inventory_summary where sku_id=1000 and warehouse_id=123456 for update;
//修改商品库存为2
update inventory_summary set quantity=2 where sku_id=1000 and warehouse_id=123456;
//提交事务
commit;
复制代码
乐观锁的实现不须要借助数据库的锁机制,只要两个步骤:冲突检测(比较)和数据更新,其中一种典型的实现方法就是CAS(比较交换,Compare And Swap)。CAS这里不作详细解释,就是先比较后更新,在对一个数据进行更新前,先持有这个数据原有值得备份,若是当前更新的值和原来的备份相等才进行更新,不然判断为数据被其余线程改过了。当前线程再次进行重试。 *** ABA问题 ***
//查询出商品的库存是3,而后用库存为3做为条件进行更新
select quantity from inventory_summary where sku_id=1000 and warehouse_id=123456;
//修改库存为2
update inventory_summary set quantity=2 where sku_id=1000 and warehouse_id=123456 and quantity=3;
复制代码
在更新以前,先查询原有的库存数,在更新库存时,用原有的库存数做为修改条件。相等则更新,不然认为是被改过的数据。可是会存在这样的状况下,好比线程A取出库存数3,线程B先将库存数更新为2,又将库存数更新为3,而后线程A更新的时候发现库存仍然是3,而后更新成功。可是这个过程可能存在问题。** 解决ABA问题一个方法是经过一个顺序递增的version字段或者时间戳**
//查询商品库存表,version=1
select version from inventory_summary where sku_id=1000 and warehouse_id=123456;
//修改商品库存为2
update inventory_summary set quantity=2,version=version+1 where sku_id=1000 and warehouse_id=123456 and version=1;
复制代码
在每次更新的时候都带上一个版本号,一旦版本号和数据版本号一致就能够执行修改并对版本号执行+1操做,不然执行失败。由于每次操做版本号递增,因此不会出现ABA问题。还可使用时间戳,由于时间戳具备自然的顺序递增性。
乐观锁并非真正的加锁,优势是效率高,缺点是更新时间的几率比较高(尤为是并发度比较高的环境中);悲观锁依赖于数据库锁机制,更新失败的几率低,可是效率也低。