Mysql 在5.5以前默认使用 MyISAM 存储引擎,以后使用 InnoDB 。查看当前存储引擎:程序员
show variables like '%storage_engine%';
复制代码
MyISAM 操做数据都是使用的表锁,你更新一条记录就要锁整个表,致使性能较低,并发不高。固然同时它也不会存在死锁问题。sql
而 InnoDB 与 MyISAM 的最大不一样有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。也就是你须要修改哪行,就能够只锁定哪行。数据库
在 Mysql 中,行级锁并非直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,若是一条sql 语句操做了主键索引,Mysql 就会锁定这条主键索引;若是一条语句操做了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。并发
InnoDB 行锁是经过给索引项加锁实现的,若是没有索引,InnoDB 会经过隐藏的聚簇索引来对记录加锁。也就是说:若是不经过索引条件检索数据,那么InnoDB将对表中全部数据加锁,实际效果跟表锁同样。由于没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。性能
首先说明:数据库的增删改操做默认都会加排他锁,而查询不会加任何锁。spa
对某一资源加共享锁,自身能够读该资源,其余人也能够读该资源(也能够再继续加共享锁,即 共享锁可多个共存),但没法修改。要想修改就必须等全部共享锁都释放完以后。语法为:.net
select * from table lock in share mode
复制代码
对某一资源加排他锁,自身能够进行增删改查,其余人没法进行任何操做。语法为:线程
select * from table for update --增删改自动加了排他锁
复制代码
下面援引例子来进行理解(援自:blog.csdn.net/samjustin1/… )。设计
这里用T1表明一个数据库执行请求,T2表明另外一个请求,也能够理解为T1为一个线程,T2 为另外一个线程。code
例1:
T1:select * from table lock in share mode(假设查询会花很长时间,下面的例子也都这么假设)
T2:update table set column1='hello'
复制代码
分析:
T1运行(并加共享锁)
T2运行
If T1还没执行完
T2等......
else锁被释放
T2执行
endif
复制代码
T2 之因此要等,是由于 T2 在执行 update 前,试图对 table 表加一个排他锁,而数据库规定同一资源上不能同时共存共享锁和排他锁。因此 T2 必须等 T1 执行完,释放了共享锁,才能加上排他锁,而后才能开始执行 update 语句。
例2:
T1:select * from table lock in share mode
T2:select * from table lock in share mode
复制代码
分析:
这里T2不用等待T1执行完,而是能够立刻执行。T1运行,则 table 被加锁,好比叫lockAT2运行,再对 table 加一个共享锁,好比叫lockB两个锁是能够同时存在于同一资源上的(好比同一个表上)。这被称为共享锁与共享锁兼容。这意味着共享锁不阻止其它人同时读资源,但阻止其它人修改资源。
例3:
T1:select * from table lock in share mode
T2:select * from table lock in share mode
T3:update table set column1='hello'
复制代码
分析:
T2 不用等 T1 运行完就能运行,T3 却要等 T1 和 T2 都运行完才能运行。由于 T3 必须等 T1 和 T2 的共享锁所有释放才能进行加排他锁而后执行 update 操做。
例4:(死锁的发生)
T1:begin tran select * from table lock in share modeupdate table set column1='hello'
T2:begin tran select * from table lock in share modeupdate table set column1='world'
复制代码
假设 T1 和 T2 同时达到 select,T1 对 table 加共享锁,T2 也对 table 加共享锁,当 T1 的 select 执行完,准备执行 update 时,根据锁机制,T1 的共享锁须要升级到排他锁才能执行接下来的 update.在升级排他锁前,必须等 table 上的其它共享锁(T2)释放,同理,T2也在等 T1 的共享锁释放。因而死锁产生了。
例5:
T1:begin tran update table set column1='hello' where id=10
T2:begin tran update table set column1='world' where id=20
复制代码
这种语句虽然最为常见,不少人以为它有机会产生死锁,但实际上要看状况:
那死锁怎么解决呢?一种办法是,以下:
例6:
T1:begin tran select * from table for updateupdate table set column1='hello'
T2:begin tran select * from table for updateupdate table set column1='world'
复制代码
这样,当 T1 的 select 执行时,直接对表加上了排他锁,T2 在执行 select 时,就须要等 T1 事务彻底执行完才能执行。排除了死锁发生。但当第三个 user 过来想执行一个查询语句时,也由于排他锁的存在而不得不等待,第四个、第五个 user 也会所以而等待。在大并发状况下,让你们等待显得性能就太友好了。 因此,有些数据库这里引入了更新锁(如Mssql,注意:Mysql不存在更新锁),以下:
例7:
T1:begin transelect * from table (加更新锁)update table set column1='hello'
T2:begin transelect * from table (加更新锁)update table set column1='world'
复制代码
更新锁其实就能够当作排他锁的一种变形,只是它也容许其余人读(而且还容许加共享锁)。但不容许其余操做,除非我释放了更新锁。T1 执行 select,加更新锁。T2 运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。当后来有 user三、user4...须要查询 table 表中的数据时,并不会由于 T1的 select 在执行就被阻塞,照样能查询,相比起例6,这提升了效率。
某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为n,以后进行了一些操做,最后将均执行update table set 库存数=n-1,那么,很显然这是错误的。
其实说白了也就是排他锁。
靠表设计和代码来实现。
update table set num=num-1 where id=10 and version=23
复制代码
这样,保证了修改的数据是和它查询出来的数据是一致的,而其余执行程序未进行修改。固然,若是更新失败,表示在更新操做以前,有其余执行程序已经更新了该库存数,那么就能够尝试重试来保证更新成功。为了尽量避免更新失败,能够合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。
对于以上,能够看得出来乐观锁和悲观锁的区别:
悲观锁使用了排他锁,当程序独占锁时,其余程序就连查询都是不容许的,致使吞吐较低。若是在查询较多的状况下,可以使用乐观锁。
乐观锁更新有可能会失败,甚至是更新几回都失败,这是有风险的。因此若是写入较频繁,对吞吐要求不高,可以使用悲观锁。
也就是一句话:多读用乐观锁,多写用悲观锁。