把那些可能会被多个线程同时操作的资源称为临界资源,加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。数据库作为用户共享的一个资源,如何保证数据并发访问一致性也是所有数据库必须解决的问题,如何加锁是数据库并发访问性能的一个重要因素。
关于数据库中的锁
从对数据库操作的类型可将锁分为读锁(共享锁)和写锁(排他锁):
读锁:多个读操作可以同时进行不会互相影响;
写锁:当前写操作没有完成前,会排斥其他的读锁和写锁。
从数据操作粒度上可以分为表锁和行锁。
表锁
顾名思义,表锁就是锁住整张表。这种操作开销小,加锁快,不会出现死锁,但是锁的粒度大,并发度低。MyISAM 引擎在操作数据时就会自动给数据加上表锁。
首先创建一个数据库和一张表:
create database test_mysql;
use test_mysql;
create table test_mysql_lock(
`id` INT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(40) DEFAULT NULL,
primary key(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into test_mysql_lock values(1,'javayz');
insert into test_mysql_lock values(2,'javayz2');
可以通过下面 sql 手动增加表锁,其中 read 和 write 是两种锁类型:
lock table 表名 read(write);
查看表上加过的锁:
show open tables;
删除锁:
unlock tables;
加了读锁后,将限制写入;加了写锁之后,将限制读和写;
行锁
行锁就是锁住一行数据,这种做法开销大,加锁慢,会出现死锁。但是锁的粒度小,并发度最高。InnoDB 支持行锁,同时 InnoDB 还支持事务。
通过一个简单的例子来介绍一下行锁,开启两个 session(在控制台中创建两个终端)去操作数据库,其中在第一个查询中修改数据,但是不提交:
begin;
update test_mysql_lock set name='javayz2' where id=1;
这个时候用另外一个查询窗口去修改同一行数据:
update test_mysql_lock set name='javayz2' where id=1;
这行更新语句会被一直阻塞,现在把第一个查询中修改的数据提交:
commit;
第二个查询中的修改也立刻生效了,查看结果,阻塞了 11 秒:
注意:InnoDB 的行锁是加在索引上的,如果索引失效或者修改的是非索引字段,行锁就会升级为表锁。