数据库开发规范
基础规范
(
1
)必须使用
InnoDB
存储引擎解读:支持事务、行级锁、并发性能更好、
CPU
及内存缓存页优化使得资源利用率
更高
(
2
)必须使用
UTF8
字符集解读:万国码,无需转码,无乱码风险,节省空间
(
3
)
数据表、数据字段必须加入中文注释解读:
N
年后谁知道这个
r1,r2,r3
字段是干嘛的
(
4
)禁止使用存储过程、视图、触发器、
Event
解读:高并发大数据的互联网业务,架构设计思路是
“
解放数据库
CPU
,将计算转移到服务层
”
,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“
增机器就加性能
”
。
数据库擅长存储与索引,
CPU
计算还是上移吧
(
5
)禁止存储大文件或者大照片解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数库里存URI
多好
命名规范
(
6
)只允许使用内网域名,而不是
ip
连接数据库
(
7
)线上环境、开发环境、测试环境数据库内网域名遵循命名规范业务名称:
xxx
线上环境:
my10000m.mysql.jddb.com
开发环境:
yf10000m.mysql.jddb.com
测试环境:
test10000m.mysql.jddb.com
从
库在名称后加
-s
标识,备库在名称后加
-ss
标识线上从库:
my10000sa.mysql.jddb.com
(
8
)库名、表名、字段名:小写,
下划线风格
,不超过
32
个字符,必须见名知意,禁止拼音英文混用
(
9
)表名
t_xxx
,非唯一索引名
idx_xxx
,唯一索引名
uniq_xxx
(
10
)单实例表数目必须小于
500
(
11
)单表列数目必须小于
30
(
12
)表必须有主键,例如自增主键解读:
a
)主键递增,数据行写入可以提高插入性能,可以避免
page
分裂,减少表碎片提升空间和内存的使用
b
)
主键要选择较短的数据类型
,
Innodb
引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
c
) 无主键的表删除,在
row
模式的主从架构,会导致备库夯住
(
13
)
禁止使用外键,如果有外键完整性约束,需要应用程序控制解读:外键会导致表与表之间耦合,
update
与
delete
操作都会涉及相关联的表,十分影响
sql
的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大
数据高并发业务场景数据库使用以性能优先
字段设计规范
(
14
)必须把字段定义为
NOT NULL
并且提供默认值解读:
a
)
null
的列使索引
/
索引统计
/
值比较都更加复杂,对
MySQL
来说更
难优化
b
)
null
这种类型
MySQL
内部需要进行特殊处理,
增加数据库处理记录的复杂性
;同等条件下,表中有较多空字段
的时候,数据库的处理性能会降低很多
c
)
null
值需要更多的存储空,无论是表还是索引中每行中的
null
的列都需要额外的空间来标识
d
)对
null
的处理时候,只能采用
is null
或
is not null
,而不能采用
=
、
in
、
<
、
<>
、
!=
、
not in
这些操作符号。如:
where name!=’shenjian’
,如果存在
name
为
null
值的记录,查询结果就不会包含
name
为
null
值的记录
(
15
)禁止使用
TEXT
、
BLOB
类型解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能
(
16
)禁止使用小数存储货币解读,小数容易导致钱对不上
(
17
)必须使用
varchar(20)
存储手机号解读:
a
)涉及到区号或者国家代号,可能出现
+-()
b
)手机号会去做数学运算么?
c
)
varchar
可以支持模糊查询,例如:
like“138%”
(
18
)禁止使用
ENUM
,可使用
TINYINT
代替解读:
a
)增加新的
ENUM
值要做
DDL
操作
b
)
ENUM
的内部实际存储 就是整数,你以为自己定义的是字符串?
(
19
)单表索引建议控制在
5
个以内
(
20
)单索引字段数不允许超过
5
个解读:字段超过
5
个时,实际已经起不到有效过滤数据的作用了
(
21
)禁止在更新十分频繁、区分度不高的属性上建立索引解读:
a
)更新会变更
B+
树,更新频繁的字段建立索引会大大降低数据库性能b
)
“
性别
”
这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似
(
22
)建立组合索引,必须把区分度高的字段放在前面解读:能够更加有效的过滤数据
(
23
)
禁止使用
SELECT *
,只获取必要的字段,需要显示说明列属性解读:
** a
)读取不需要的列会增加
CPU
、
IO
、
NET
消耗
**
** b
)不能有效的利用覆盖索引
**
** c
)使用
SELECT *
容易在增加或者删除字段后出现程序
BUG**
(
24
)禁止使用
INSERT INTO t_xxx VALUES(xxx)
,必须显示指定插入的列属性解读:容易在增加或者删除字段后出现程序BUG
(
25
)禁止使用属性隐式转换解读:
SELECT uid FROM t_user WHERE phone=13800000000
会导致全表扫描,而不能命中phone
索引,猜猜为什么?(这个线上问题不止出现过一次)
(
26
)禁止在
WHERE
条件的属性上使用函数或者表达式解读:
SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-01-15' 会导致全表扫描正确的写法是:
SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-01-15 00:00:00')
(
27
)禁止负向查询,以及
%
开头的模糊查询解读:
a
)负向查询条件:
NOT
、
!=
、
<>
、
!<
、
!>
、
NOT IN
、
NOT LIKE
等,会导致全表扫描
b
)
%
开头的模糊查询,会导致全表扫描
(
28
)
禁止使用
JOIN
查询,禁止大表使用子查询解读:会产生临时表,消耗较多内存与
CPU
,极大影响数据库性
能
(
29
)
禁止使用
OR
条件,必须改为
IN
查询解读:旧版本
Mysql
的
OR
查询是不能命中索引的,即使能命中索引,为
何要让数据库耗费更多的
CPU
帮助实施查询优化呢?
(
30
)应用程序必须捕获
SQL
异常,并有相应处理
(
31
)同表的增删字段、索引合并一条
DDL
语句执行,提高执行效率,减少与数据库的交互。
总结
大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。
三、数据库索引
Hash
索引
B+
索引
索引的作用是
“
排列好次序,使得查询时可以快速找到
”
。
唯一索引
唯一索引是在表上一个或者多个字段组合建立的索引,这个或者这些字段的值组合起来在表中不可以重复。如学生 表中的’
学号
‘
非唯一索引
非唯一索引是在表上一个或者多个字段组合建立的索引,这个或者这些字段的值组合起来在表中可以重复,不要求唯一。如学生表中的‘
成绩
’
主键索引
主键索引(主索引)是唯一索引的特定类型。表中创建主键时自动创建的索引 。一个表只能建立一个主索引。
聚集索引(聚簇索引)
聚集索引(聚簇索引),表中记录的物理顺序与键值的索引顺序相同。一个表只能有一个聚集索引。
扩展:聚集索引和非聚集索引的区别?分别在什么情况下使用?
聚集索引和非聚集索引的根本区别是表中记录的物理顺序和索引的排列顺序是否一致。
聚集索引的表中记录的物理顺序与索引的排列顺序一致。
优点是查询速度快,因为一旦具有第一个索引值的记录被找到,具有连续索引值的记录也一定物理的紧跟其后。
缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致,而把记录插入到数据页的 相应位置,必须在数据页中进行数据重排,降低了执行速度。在插入新记录时数据文件为了维持 B+Tree
的特性而
频繁的分裂调整,十分低效。
建议使用聚集索引的场合为:
某列包含了小数目的不同值。
排序和范围查找。
非聚集索引的记录的物理顺序和索引的顺序不一致。
其他方面的区别:
1.
聚集索引和非聚集索引都采用了
B+
树的结构,但非聚集索引的叶子层并不与实际的数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针的方式。聚集索引的叶节点就是数据节点,而非聚集索引的叶节点仍然是索引节点。
2.
非聚集索引添加记录时,不会引起数据顺序的重组。看上去聚簇索引的效率明显要低于非聚簇索引, 因为每次使 用辅助索引检索都要经过两次 B+
树查找, 这不是多此一举吗? 聚簇索引的优势在哪? 由于行数据和叶子节点存储在一起, 这样主键和行数据是一起被载入内存的, 找到叶子节点就可以立刻将行数据返回了, 如果按照主键 Id
来组织数据, 获得数据更快。
辅助索引使用主键作为
"
指针
",
而不是使用地址值作为指针的好处是, 减少了当出现行移动或者数据页分裂时
, 辅助索引的维护工作, InnoDB
在移动行时无须更新辅助索引中的这个
"
指针
"
。 也就是说行的位置会随着数据库里数据的修改而发生变化, 使用聚簇索引就可以保证不管这个主键 B+
树的节点如何变化, 辅助索引树都不受影响。
建议使用非聚集索引的场合为:
此列包含了大数目的不同值;
频繁更新的列
索引实现机制
MyISAM
引擎使用
B+Tree
作为索引结构,叶节点的
data
域存放的是数据记录的地址。下图是
MyISAM
索引的原理 图:
![](https://img-blog.csdnimg.cn/a752181375634bcbba8a1be417723bfc.png)
这里设表一共有三列,假设我们以
Col1
为主键,则上图是一个
MyISAM
表的主索引(
Primary key
)示意。可以看
出
MyISAM
的索引文件仅仅保存数据记录的地址。在
MyISAM
中,主索引和辅助索引(
Secondary key
)在结构上没
有任何区别,只是主索引要求
key
是唯一的,而辅助索引的
key
可以重复。如果我们在
Col2
上建立一个辅助索引,则
此索引的结构如下图所示:
![](https://img-blog.csdnimg.cn/d47164924c7b47d0b982d3dde253855d.png)
同样也是一颗
B+Tree
,
data
域保存数据记录的地址。因此,
MyISAM
中索引检索的算法为首先按照
B+Tree
搜索算法搜索索引,如果指定的Key
存在,则取出其
data
域的值,然后以
data
域的值为地址,读取相应数据记录。
MyISAM
的索引方式也叫做
“
非聚集
”
的,之所以这么称呼是为了与
InnoDB
的聚集索引区分。
InnoDB
索引实现
虽然
InnoDB
也使用
B+Tree
作为索引结构,但具体实现方式却与
MyISAM
截然不同。
第一个重大区别是
InnoDB
的数据文件本身就是索引文件。从上文知道,
MyISAM
索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB
中,表数据文件本身就是按
B+Tree
组织的一个索引结构,这棵树的叶节点data
域保存了完整的数据记录。这个索引的
key
是数据表的主键,因此
InnoDB
表数据文件本身就是主索引。
![](https://img-blog.csdnimg.cn/8f0e1de32179471a8d65bd1cefe00099.png)
上图是
InnoDB
主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚
集索引。因为
InnoDB
的数据文件本身要按主键聚集,所以
InnoDB
要求表必须有主键(
MyISAM
可以没有),如果
没有显式指定,则
MySQL
系统
会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则
MySQL
自动为
InnoDB
表生成一个隐含字段作为主键,这个字段长度为
6
个字节,类型为长整形。
第二个与
MyISAM
索引的不同是
InnoDB
的辅助索引
data
域存储相应记录主键的值而不是地址。换句话说,
InnoDB
的所有辅助索引都引用主键作为
data
域。例如,下图为定义在
Col3
上的一个辅助索引:
![](https://img-blog.csdnimg.cn/d2fb6550d0e5488d8a7cf479d7308323.png)
这里以英文字符的
ASCII
码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了
InnoDB
的索引实现后,就 很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引 变得过大。再例如,用非单调的字段作为主键在InnoDB
中不是个好主意,因为
InnoDB
数据文件本身是一颗 B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持
B+Tree
的特性而频繁的分裂调整,十分低效,而
使用自增字段作为主键则是一个很好的选择。
索引建立原则
(
id
,
name) where id=1 and name='xxx'
1. .
最左前缀匹配原则
,
mysql
会一直向右匹配直到遇到范围查询
(>
、
<
、
between
、
like)
就停止匹配,范围查询会导致组合索引半生效。比如 a = 1 and b = 2 and c > 3 and d = 4
如果建立
(a,b,c,d)
顺序的索引,
c
可以用到索引,d
是用不到索引的,如果建立
(a,b,d,c)
的索引则都可以用到,
a,b,d
的顺序可以任意调整。
where 范围查询要放在最后 (这不绝对,但可以利用一部分索引)。
欢迎
2.
特别注意:
and
之间的部分可以乱序,比如
a = 1 and b = 2 and c = 3
建立
(a,b,c)
索引可以任意顺序,
mysql 的查询优化器会帮你优化成索引可以识别的形式。where
字句有
or
出现还是会遍历全表。
3.
尽量
选择区分度高的字段作为索引
,
某字段的区分度的公式是
count(distinctcol)/count(*)
,表示字段不重复的比例,比例越大,我们扫描的记录数越少,查找匹配的时候可以过滤更多的行, 唯一索引的区分度是 1
,而一些状态、性别字段可能在大数据面前区分度就是 0
。
4.
不在索引列做运算或者使用函数。
5.
尽量扩展索引,不要新建索引。比如表中已经有
a
的索引,现在要加
(a,b)
的索引,那么只需要修改原来的索引即可。
6. Where
子句中经常使用的字段应该创建索引,分组字段或者排序字段应该创建索引,两个表的连接字段应该创建索引。
7.
like
模糊查询中,右模糊查询(
321%
)会使用索引,而
%321
和
%321%
会放弃索引而使用全局扫描。
四、
MyISAM vs InnoDB
Mysql
数据库
中,最常用的两种引擎是
innordb
和
myisam
。
InnoDB
是
Mysql
的默认存储引擎。
1.
事务处理上方面:
MyISAM
强调的是性能,查询的速度比
InnoDB
类型更快,但是不提供事务支持。
InnoDB
提供事务支持事务。
2.
外键:
MyISAM
不支持外键,
InnoDB
支持外键。
3.
锁方面的介绍:
MyISAM
只支持表级锁,
InnoDB
支持行级锁和表级锁,默认是行级锁,行锁大幅度提高了多用户并发操作的性能。innodb
比较适合于插入和更新操作比较多的情况,而
myisam
则适合用于频繁查询的情况。另外,
InnoDB 表的行锁也不是绝对的,如果在执行一个 SQL
语句时,
MySQL
不能确定要扫描的范围,
InnoDB
表同样会锁全表,例如 update table set num=1 where name like “%aaa%”
。
4.
全文索引:
MyISAM
支持全文索引,
InnoDB
不支持全文索引。
innodb
从
mysql
5.6
版本开始提供对全文索引的支持。
5.
表主键的区别:
MyISAM
:允许没有主键的表存在。
InnoDB
:如果没有设定主键,就会自动生成一个
6
字节的主键
(
用户不可见
)
。
6.
表的具体行数问题:
MyISAM
:
select count() from table,MyISAM
只要简单的读出保存好的行数。因为
MyISAM
内置了一个计数器, count()时它直接从计数器中读。
InnoDB
:不保存表的具体行数,也就是说,执行
select count(*) from table
时,
InnoDB
要扫描一遍整个表来计算有多少行。
一张表
,
里面有
ID
自增主键
,
当
insert
了
17
条记录之后
,
删除了第
15,16,17
条记录
,
再把
Mysql
重启
,
再
insert
一
条记录
,
这条记录的
ID
是
18
还是
15
?
如果表的类型是
MyISAM
, 那么是
18
。因为
MyISAM
表会把自增主键的最大
ID
记录到数据文件里, 重启
MySQL自增主键的最大 ID
也不会丢失。
如果表的类型是
InnoDB
, 那么是
15
。
InnoDB
表只是把自增主键的最大
ID
记录到内存中, 所以重启数据库会导致最大 ID
丢失。
五、并发事务带来的问题
丢失更新
如果两个事务都要更新数据库一个字段
X
,
x=100 两个不同事物同时获得相同数据,然后在各自事务中同时修改了该数据,那么先提交的事务更新会被后提交事务的更新给覆盖掉,这种情况事务A
的更新就被覆盖掉了、丢失了。
脏读(未提交读)
防止一个事务读到另一个事务还没有提交的记录。 如: 事务读取了未提交的数据,事务B
的回滚,导致了事务
A
的数据不一致,导致了事务
A
的
脏读
!
不可重复读
一个事务在自己没有更新数据库数据的情况,同一个查询操作执行两次或多次的结果应该是一致的;如果不一致,就说明为不可重复读。 还是用上面的例子
幻读(
Phantom Read
)
事务
A
读的时候读出了
15
条记录,事务
B
在事务
A
执行的过程中 增加 了
1
条,事务
A
再读的时候就变成了
16
条,这种情况就叫做幻影读。 不可重复读说明了做数据库读操作的时候可能会出现的问题。
六、事务隔离级别及锁的实现机制
排他锁 写锁
被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象
共享锁
**
读锁
**
被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。
特别的,对共享锁: 如果两个事务对同一个资源上了共享锁,事务
A
想更新该数据,那么它必须等待 事务
B
释放其共享锁。
在运用 排他锁 和 共享锁 对数据对象加锁时,还需要约定一些规则,例如何时申请 排他锁 或 共享锁、持锁时间、 何时释放等。称这些规则为封锁协议(Locking Protocol
)。对封锁方式规定不同的规则,就形成了各种不同的封
锁协议。
一级封锁协议 (对应
read uncommited
)
一级封锁协议是:事务 在对
需要修改的数据上面(就是在发生修改的瞬间) 对其加共享锁
(其他事务不能更改, 但是可以读取-
导致
“
脏读
”
),直到事务结束才释放。事务结束包括正常结束(
COMMIT
)和非正常结束 ROLLBACK)。
一级封锁协议不能避免
丢失更新,脏读,不可重复读,幻读!
二级封锁协议 (对应
read commited)
二级封锁协议是:
1
)事务 在对需要更新的数据 上(就是发生更新的瞬间) 加 排他锁 (直到事务结束),防止其他事务读取未提交
的数据,这样,也就避免了
“
脏读
”
的情况。
2
)事务 对当前被读取的数据 上面加共享锁(当读到时加上共享锁),一旦读完该行,立即 释放该该行的共享锁
- 上面只能防止不读脏数据
2
)事务 对当前被读取的数据 上面加共享锁(当读到时加上共享锁),直到事务结束才释放可以防止不可重复读从数据库的底层实现更深入的来理解,既是,数据库会对游标当前的数据上加共享锁
, 但是当游标离开当前行的时候,立即释放该行的共享锁。 二级封锁协议除防止了“
脏读
”
数据,但是不能避免
丢失更新,不可重复读,幻读
。 但在二级封锁协议中,由于读完数据后立即
释放共享锁,所以它不能避免
可重复读
,同时它也不能避免
丢失更新 ,如果事务A
、
B
同时获取资源
X
,然后事务
A
先发起更新记录
X
,那么 事务
B
将等待事务
A
执行完成,然后获得记录X
的排他锁,进行更改。这样事务
A
的更新将会被丢失。
如果要避免
丢失更新,我们需要额外的操作,
对凡是读到的数据加
共享锁
和
排他锁
,这个往往需要程序员自己编程实现,比如在Oracle
中,需要加
SELECT FOR UPDATE
语句,表明,凡是该事务读到的数据,额外的加上排他锁,防止其他数据同一时间获取相同数据,这样就防止了 丢失更新
!
三级封锁协议 (对应
reapetable read
)
三级封锁协议是:二级封锁协议加上事务 在读取数据的瞬间 必须先对其加
共享锁
,
但是
直到
事务结束才释放
,
这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。
三级封锁协议除防止了
“
脏
”
数据
和
不可重复读
。但是这种情况不能避免
幻读 和 丢失更新
的情况,在事务
A
没有完成之前,事务 B
可以新增数据,那么 当事务
A
再次读取的时候,事务
B
新增的数据会被读取到,这样,在该封锁协议下,幻读
就产生了。 如果事务
A
和 事务
B
同时读取了资源
X=100
,同样,如果事务
A
先对
X
进行 更新 X=X+100,等待事务
A
执行完成
X=200
,那么事务
B
获得
X
的排他锁,进行更新
X=X+200
,然后提交
X=300
,同样
A的更新被B
所覆盖!
(
如果要避免
丢失更新,我们需要额外的操作,
对凡是读到的数据加
共享锁
和
排他锁
,这个往往需要程序员自己编程实现,比如在Oracle
中,需要加
SELECT FOR UPDATE
语句,表明,凡是读到的数据,我 会加 排他锁,防止其他数据同一时间获取相同数据)
!
进阶:
repeatable read
导致死锁的情况
(即便是 不同的资源在相同的顺序下获取)。
比如 事务
1
读取
A
,同时 事务2
也读取
A
,那么事务
1
和事务
2
同时对
A
上了共享锁,然后事务
1
要
UPDATE A
,而此时 事务
2
也要
UPDATE A,这个时候 事务
1
等待 事务
2
释放其在
A
上的共享锁,然后 事务
2
要等待 事务
1
释放其在
A
上的共享锁,这 样,事务1
和 事务
2
相互等待,产生死锁!(
SQL Server/DB2
里面有
UPDATE LOCK
可以解决这种情况,具体的思路是,在 repeatable read
的情况下,将读取的数据 上的
UPDATE
锁,介于 共享锁 和 排他锁之间的一种锁,该
锁的作用是 当出现上面这种情况后,事务
1
和 事务
2
对
A
上的是
UPDATE
锁,那么谁先 要修改
A
,那么该事务就会将 UPDATE
锁可以顺利升级为 排他锁对该数据进行修改!)
最强封锁协议(对应
Serialization
)
四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读
,都得以避免!
七、
MVCC
(多版本并发控制)
mysql
的
innodb
采用的是行锁,而且采用了多版本并发控制来提高读操作的性能
MVCC
只在
REPEATABLE READ
和
READ COMMITED
两个隔离级别下工作,其它两个隔离级别下不存在
MVCC
什么是多版本并发控制呢 ?其实就是在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号,
而每一个事务在启动的时候,都有一个唯一的递增的版本号。
1
、在插入操作时 : 记录的创建版本号就是事务版本号。
比如我插入一条记录
,
事务
id
假设是
1
,那么记录如下:也就是说,创建版本号就是事务版本号。
2
、在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。 比如,针对上面那行记录,事务Id
为
2
要把
name
字段更新 update table set name= 'new_value' where id=1;
3
、删除操作的时候,就把事务版本号作为删除版本号。比如 delete from table where id=1;
4
、查询操作:
从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:
1
)
InnoDB
只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,只么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2
)行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。 这样就保证了各个事务互不影响。从这里也可以体会到一种提高系统性能的思路,就是:
通过版本号来减少锁的争用。
另外,只有
read-committed
和
repeatable-read
两种事务隔离级别才能使用
mVcc
read-uncommited
由于是读到未提交的,所以不存在版本的问题
而
serializable
则会对所有读取的行加锁。
八、间隙锁与幻读
间隙锁(
Next-Key
锁)
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,
InnoDB
会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“
间隙(
GAP)”
,
InnoDB
也会对这个
“
间隙
”
加锁,这种锁机制就是所谓的间隙锁(Next-Key
锁)。
举例来说,假如
emp
表中只有
101
条记录,其
empid
的值分别是
1,2,...,100,101
,下面的
SQL
:
Select
*
from
emp
where
empid >
100
for
update
;
是一个范围条件的检索,
InnoDB
不仅会对符合条件的
empid
值为
101
的记录加锁,也会对
empid
大于
101
(这些记录并不存在)的“
间隙
”
加锁。
InnoDB
使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid
大于
100
的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下InnoDB
使用间隙锁的情况,在后续的章节中会做进一步介绍。
很显然,在使用范围条件检索并锁定记录时,
InnoDB
这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
RR
级别下防止幻读
快照读:使用
MVCC
防止幻读
当前读:使用间隙所防止幻读