原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
如果事务执行之前数据库是一个完整性的状态,那么事务结束后,无论事务是否执行成功,数据库仍然是一个完整性状态(数据库的完整性状态:当一个数据库中的所有的数据都符合数据库中所定义的所有的约束,此时可以称数据库是一个完整性状态)
隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离
持久性(durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
CAP定理和BASE理论: 参考:CAP和BASE理论
dubbo+zookeeper
主要实现CP
springcloud eureka [hystrix]
主要实现AP
一次业务场景思考
事务已提交,数据却丢了
Read Uncommitted
读取未提交内容, 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)
Read Committed
读取提交内容, 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 这种隔离级别可能会导致所谓的 不可重复读(Nonrepeatable Read)
,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果
Repeatable Read
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)
Serializable
序列化, 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
Read Uncommitted | ✔️ | ✔️ | ✔️ |
Read Committed | ✖️ | ✔️ | ✔️ |
Repeatable Read | ✖️ | ✖️ | ✔️ |
Serializable | ✖️ | ✖️ | ✖️ |
某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的
在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几行(Row)数据,而另一个事务却在此时插入了新的几行数据,先前的事务在接下来的查询中,就会发现有几行数据是它先前所没有的, InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题
参考链接: MySQL锁总结
联想记忆: Java中的Happens Before
语义保证(volatile
关键字)
共享锁(读锁)
: 其他事务可以读,但不能写排他锁(写锁)
: 其他事务不能读取,也不能写表级锁
行级锁
页面锁
:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。但是在有的情况下, 用户需要明确地进行锁表或者进行事务的控制, 以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成
MyISAM 和 MEMORY
存储引擎采用的是表级锁
(table-level locking)BDB
存储引擎采用的是页面锁
(page-level locking),但也支持表级锁InnoDB
存储引擎既支持行级锁
(row-level locking),也支持表级锁,但默认情况下是采用行级锁
在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,这就决定了在 InnoDB 中发生死锁是可能的。
表共享读锁
(Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
表独占写锁
(Table Write Lock):会阻塞其他用户对同一表的读和写操作
MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止
默认情况下,写锁比读锁具有更高的优先级,当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁队列中等候的获取锁请求
这也正是 MyISAM 表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞
在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,这也正是 MyISAM 表不会出现死锁(Deadlock Free)的原因
MyISAM存储引擎支持并发插入,以减少给定表的读和写操作之间的争用:
如果MyISAM表在数据文件中间没有空闲块,则行始终插入数据文件的末尾。 在这种情况下,你可以自由混合并发使用MyISAM表的INSERT和SELECT语句而不需要加锁——你可以在其他线程进行读操作的时候,同时将行插入到MyISAM表中。 文件中间的空闲块可能是从表格中间删除或更新的行而产生的。 如果文件中间有空闲快,则并发插入会被禁用,但是当所有空闲块都填充有新数据时,它又会自动重新启用。 要控制此行为,可以使用MySQL的concurrent_insert系统变量:
- 当concurrent_insert设置为0时,不允许并发插入。
- 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个线程读表的同时,另一个线程从表尾插入记录。这也是MySQL的默认设置。
- 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录
可以通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁的争夺,如果 Table_locks_waited 的值比较高,则说明存在着较严重的表级锁争用情况:
mysql> SHOW STATUS LIKE 'Table%';
+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+
InnoDB 实现了以下两种类型的行锁:
共享锁(S)
:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。排他锁(X)
:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
意向共享锁(IS)
:事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁意向排他锁(IX)
:事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁(1) 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
(2) 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。
InnoDB在事务执行过程中,使用两阶段锁协议(联想记忆到 2pc 协议):
select ... lock in share mode //共享锁
select ... for update //排他锁
select for update
select lock in share mode
in share mode 子句的作用就是将查找到的数据加上一个 share 锁,这个就是表示其他的事务只能对这些数据进行简单的select 操作,并不能够进行 DML 操作
为了确保自己查到的数据没有被其他的事务正在修改,也就是说确保查到的数据是最新的数据,并且不允许其他人来修改数据。但是自己不一定能够修改数据,因为有可能其他的事务也对这些数据 使用了 in share mode 的方式上了 S 锁
其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁
通过给索引上的索引项加锁
来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!虽然多个session是访问不同行的记录, 但是如果是使用相同的索引键, 是会出现锁冲突的
(后使用这些索引的session需要等待先使用索引的session释放锁后,才能获取锁)Record Lock
:单个行记录上的锁。Gap Lock
:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。Next-Key Lock:Record + Gap
,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题MVCC
机制MVCC
机制是什么?其实就是在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号,而每一个事务在启动的时候,都有一个唯一的递增的版本号。 在InnoDB
中,给每行增加两个隐藏字段来实现MVCC,两个列都用来存储事务的版本号,每开启一个新事务,事务的版本号就会递增
consistent read (一致性读)
,InnoDB用多版本来提供查询数据库在某个时间点的快照。如果隔离级别是REPEATABLE READ,那么在同一个事务中的所有一致性读都读的是事务中第一个这样的读读到的快照; 如果是READ COMMITTED,那么一个事务中的每一个一致性读都会读到它自己刷新的快照版本。Consistent read(一致性读)是READ COMMITTED和REPEATABLE READ隔离级别下普通SELECT语句默认的模式。 一致性读不会给它所访问的表加任何形式的锁,因此其它事务可以同时并发的修改它们。
当一个 MVCC 数据库需要更一个一条数据记录的时候,它不会直接用新数据覆盖旧数据,而是将旧数据标记为过时(obsolete)并在别处增加新版本的数据。这样就会有存储多个版本的数据,但是只有一个是最新的。这种方式允许读者读取在他读之前已经存在的数据,即使这些在读的过程中半路被别人修改、删除了,也对先前正在读的用户没有影响。保证了在同一个事务中多次读取相同的数据返回的结果是一样的,解决了不可重复读的问题。
缺点在于:
这种多版本的方式避免了填充删除操作在内存和磁盘存储结构造成的空洞的开销,但是需要系统周期性整理(sweep through)以真实删除老的、过时的数据。
总结就是: MVCC是同一份数据临时保留多版本的一种方式,进而实现并发控制
etcd
学习MVCC
机制: ectdInnoDB
插入缓存(insert buffer)、两次写(double write)、自适应哈希(Adaptive Hash index)、异步IO(Async IO)、刷新邻接页(Flush Neighbor Page)
MyISAM
B树:有序数组+平衡多叉树
它的特点是:
(1)不再是二叉搜索,而是m叉搜索;
(2)叶子节点,非叶子节点,都存储数据;
(3)中序遍历,可以获得所有节点;
B+树:有序数组链表+平衡多叉树, 在B树的基础上,做了一些改进
(1)非叶子节点不再存储数据,数据只存储在同一层的叶子节点上
(2)叶子之间,增加了链表,获取所有节点,不再需要中序遍历
B+树改进后更优的特性
(1)范围查找,定位min与max之后,中间叶子节点,就是结果集,不用中序回溯
(2)叶子节点存储实际记录行,记录行相对比较紧密的存储,适合大数据量磁盘存储;非叶子节点存储记录的PK,用于查询加速,适合内存存储;
(3)非叶子节点,不存储实际记录,而只存储记录的KEY的话,那么在相同内存的情况下,B+树能够存储更多索引
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|
id
: select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
select_type
: 查询的类型,分为:
type
: 访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,好的sql查询至少达到range
级别,最好能达到ref
system
:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计const
:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个consteq_ref
:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。ref
:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体range
:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询index
:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)ALL
:Full Table Scan,遍历全表以找到匹配的行possible_keys
: 查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用
key
: 实际使用的索引,如果为NULL,则没有使用索引。查询中如果使用了覆盖索引,则该索引仅出现在key列表中
key_len
: 表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的
ref
: 显示索引的那一列被使用了,如果可能,是一个常量const
rows
: 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
Extra
: 不适合在其他字段中显示,但是十分重要的额外信息
Using filesort
:mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”Using temporary
:使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group byUsing index
:表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高;如果同时出现Using where,表明索引被用来执行索引键值的查找;如果没同时出现Using where,表明索引用来读取数据而非执行查找动作Using Where
: 使用了where过滤Using join buffer
: 使用了链接缓存Impossible WHERE
: where子句的值总是false,不能用来获取任何元祖select tables optimized away
: 在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化distinct
: 优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。
如果没定义主键,会选择一个唯一的非空索引代替,如果没有这样的索引,则会隐式定义一个主键作为聚簇索引
定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引