可以按照锁的粒度把数据库锁分为表级锁和行级锁。
Mysql中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快,不会出现死锁 。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
Mysql中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 InnoDB支持的行级锁,包括如下几种。
虽然使用行级索具有粒度小、并发度高等特点,但是表级锁有时候也是非常必要的:
表级锁和行级锁可以进一步划分为共享锁(s)和排他锁(X)。
共享锁(Share Locks,简记为S)又被称为读锁,其他用户可以并发读取数据,但任何事务都不能获取数据上的排他锁,直到已释放所有共享锁。
共享锁(S锁)又称为读锁,若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排它锁((Exclusive lock,简记为X锁))又称为写锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
两者之间的区别:
共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不 能加排他锁。获取共享锁的事务只能读数据,不能修改数据。
排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获取排他锁的事务既能读数据,又能修改数据。
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。 而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁 。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。
InnoDB另外的两个表级锁:
意向共享锁(IS): 表示事务准备给数据行记入共享锁,事务在一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX): 表示事务准备给数据行加入排他锁,事务在一个数据行加排他锁前必须先取得该表的IX锁。
注意:
InnoDB的锁机制兼容情况如下:
当一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之如果请求不兼容,则该事物就等待锁释放。
MVCC(Multi-Version Concurrency Control)多版本并发控制,可以简单地认为:MVCC就是行级锁的一个变种(升级版)。
数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的, 锁的应用最终导致不同事务的隔离级别 。 事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节。 在表锁中我们读写是阻塞的,基于提升并发性能的考虑, MVCC一般读写是不阻塞的 (所以说MVCC很多情况下避免了加锁的操作)。
MVCC实现的读写不阻塞正如其名:
多版本并发控制--->通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。
快照有两个级别:
Read uncommitted会出现的现象--->脏读:一个事务读取到另外一个事务未提交的数据
例子:A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。
出现脏读的本质就是因为操作(修改)完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据。
Read committed避免脏读的做法其实很简单:
就是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作
但Read committed出现的现象--->不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改
注:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】
Read committed是语句级别的快照!每次读取的都是当前最新的版本!
Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。
我们使用悲观锁的话其实很简单(手动加行锁就行了):
select * from xxxx for update 复制代码
在select 语句后边加了 for update相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改。
也就是说,如果张三使用 select ... for update
,李四就无法对该条记录修改了。
乐观锁不是数据库层面上的锁,是需要自己手动去加的锁。一般我们添加一个版本字段来实现, 具体过程是这样的:
select * from table
--->会查询出记录出来,同时会有一个version字段 id | name | version |
---|---|---|
1 | zangsan | 1 |
select * from table
--->会查询出记录出来,同时会有一个version字段 id | name | version |
---|---|---|
1 | zangsan | 1 |
update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}
,判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段 此时数据库记录如下: id | name | version |
---|---|---|
1 | lisi | 2 |
update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}
,但失败了!因为当前数据库中的版本跟查询出来的版本不一致! 并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题。
但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但 死锁是无法完全避免的 ,可以通过以下的经验参考,来尽可能少遇到死锁:
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。
一些常见的优化措施如下:
经典的数据库拆分方案,主库负责写,从库负责读;
根据数据库里面数据表的相关性进行拆分。
例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。 简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 。
水平拆分能够 支持非常大的数据量存储,应用端改造也少,但分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
中间件 | 出品方 | 架构模型 | 支持数据库 | 分库 | 分表 | 读写分离 | 外部依赖 | 是否开源 | 实现语言 | 支持语言 |
---|---|---|---|---|---|---|---|---|---|---|
MySQL | Fabric | MySQL官方 | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | python |
Cobar | 阿里巴巴 | 代理架构 | MySQL | 有 | 无 | 无 | 无 | 是 | Java | 无限制 |
Cobar | Client | 阿里巴巴 | 客户端架构 | MySQL | 有 | 无 | 无 | 无 | 是 | Java |
TDDL | 淘宝 | 客户端架构 | 无限制 | 有 | 有 | 有 | Diamond | 只开源部分 | Java | Java |
Atlas | 奇虎360 | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | C | 无限制 |
Heisenberg | 百度熊照 | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | Java | 无限制 |
ShardingJDBC | 当当 | 客户端架构 | MySQL | 有 | 有 | 有 | 无 | 是 | Java | Java |
Shark | 个人 | 客户端架构 | MySQL | 有 | 有 | 无 | 无 | 是 | Java | Java |
KingShard | 个人 | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | Golang | 无限制 |
MyCat | 社区 | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | Java | 无限制 |
Vitess | Youtube | 代理架构 | MySQL | 有 | 有 | 有 | 无 | 是 | Golang | 无限制 |
Mixer | 个人 | 代理架构 | MySQL | 有 | 有 | 无 | 无 | 是 | Golang | 无限制 |
HibernateShard | Hibernate | 客户端架构 | 无限制 | 有 | 有 | 无 | 无 | 是 | Java | Java |
MybatisShard | MakerSoft | 客户端架构 | 无限制 | 有 | 有 | 无 | 无 | 是 | Java | Java |
Gizzard | 代理架构 | 无限制 | 有 | 有 | 无 | 无 | 是 | Java | 无限制 |
如此多的方案,如何进行选择?可以按以下思路来考虑:
按照上述思路,推荐以下选择: