3、数据库知识点总结

371 阅读31分钟

事务隔离级别

事务隔离分为不同级别,包括
读未提交(Read uncommitted)
读提交(read committed)
可重复读(repeatable read)
串行化(Serializable)

从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是MySQL的默认级别。

事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了4种隔离级别对这三个问题的解决程度。

image.png

读未提交

其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题。更别提可重复读和幻读了,想都不要想。

读已提交

既然读未提交没办法解决脏数据问题,那么就有了读提交。读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了。
在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,这就是不可重复读,也就是读提交隔离级别。读提交解决了脏读的问题,但是无法做到可重复读,也没办法解决幻读。

可重复读

可重复是对比不可重复而言的,上面说不可重复读是指同一事物不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

串行化

串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

总结:
首先说读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。

再来说串行化。读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

最后说读提交和可重复读。这两种隔离级别是比较复杂的,既要允许一定的并发,又想要兼顾的解决问题。

脏读、幻读、可重复读、不可重复读

脏读

脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。

幻读

幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

不可重复读

对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

什么是MySQL InnoDB下的当前读和快照读?

当前读
读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。

如下操作都是当前读:

select lock in share mode (共享锁)
select for update (排他锁)
update (排他锁)
insert (排他锁)
delete (排他锁)
串行化事务隔离级别

快照读
快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。

MVCC机制

全称Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发性能。
原理:主要是通过版本链,undo日志,Read View来实现。
数据库中每行记录,有几个隐藏的字段,db_trx_id、db_roll_pointer、db_row_id。

db_trx_id:最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID。

db_roll_pointer:回滚指针,指向这条记录的上一个版本(存储于rollback segment里)。

image.png

每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表。

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。

Read View(读视图) 事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。

记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表。

Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View几个属性

1、trx_ids: 当前系统活跃(未提交)事务版本号集合。

2、low_limit_id: 创建当前read view时“当前系统最大事务版本号+1”。

3、up_limit_id: 创建当前read view时“系统正处于活跃事务最小版本号”

4、creator_trx_id: 创建当前read view的事务版本号;

Read View可见性判断条件
db_trx_id < up_limit_id || db_trx_id == creator_trx_id(显示)

如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。

或者数据的事务ID等于creator_trx_id,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。

db_trx_id >= low_limit_id(不显示)

如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。如果小于则进入下一个判断

db_trx_id是否在活跃事务(trx_ids)中

不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示。

已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。

RR、RC生成时机

RC隔离级别下,每个快照读都会生成并获取最新的Read View;

RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的。

MVCC的优点:支持无锁的并发读,提高并发量

MVCC的缺点:对于同一行记录需要维护多个版本,耗费更多空间

事务ACID

原子性(Atomicity): 事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏

隔离性(Isolation):多个事务并发执行时相互隔离,事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

持久性(Durability):事务处理结束后,对数据的修改是永久的

索引

主键索引和非主键索引有什么区别?

如果查询语句是 select * from table where ID = 100,即主键查询的方式,则只需要搜索 ID 这棵 B+树。

如果查询语句是 select * from table where k = 1,即非主键的查询方式,则先搜索k索引树,得到ID=100,再到ID索引树搜索一次,这个过程也被称为回表。

binlog与主从复制、读写分离

主从复制用来实现读写分离,写主库,读从库,由binlog实现。

binlog格式

MySQL中的binlog是一个二进制文件,它记录了所有的增删改操作。节点之间的复制就是依靠binlog来完成的 binlog具有三种模式:

1、Row模式
日志中会记录成每一行数据被修改的日志,然后在slave端再对相同的数据进行修改
2、statement模式
记录每一条会修改数据的sql。slave在复制的时候sql Thread再次执行master执行过的sql
3、mixed模式
Mixed即混合模式,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。

主从复制流程

主库将变更写入binlog日志,然后从库连接到主库之后,从库有一个IO线程,将主库的binlog日志拷贝到自己本地,写入一个relay中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。

原理

(1)master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;

(2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O Thread请求master二进制事件;

(3)同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQL Thread将进入睡眠状态,等待下一次被唤醒。

1、从库会生成两个线程,一个I/O线程,一个SQL线程;
2、I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
3、主库会生成一个log dump线程,用来给从库I/O线程传binlog;
4、SQL线程会读取relay log文件中的日志,并解析成sql语句逐一执行;

image.png

主从复制的延迟与数据丢失问题

1、延时:从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
2、数据丢失:如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了

解决方案:
半同步复制,用来解决主库数据丢失问题;
并行复制,用来解决主从同步延时问题

半同步复制,主库写入binlog日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的relay log之后,接着会返回一个ack给主库,主库接收到至少一个从库的ack之后才会认为写操作完成了。
并行复制,指的是从库开启多个线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。

分库分表

一般的,单表到几百万条数据、单库的并发达到2000/s,就要考虑分库分表了,分库与分表是两回事,可能是光分库不分表,也可能是光分表不分库,也可能同时分库分表。只不过,在平时的设计中,分库分表是同时进行的。

垂直切分与水平切分

垂直切分:按照列拆分表,拆分后每个表都包含部分字段,一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,访问频率高的行字段越少,就可以在缓存里缓存更多的行。

水平切分:按照行拆分表,一个表的数据拆分到多张表中,比如单表3000W条数据,切分为3张表,每张表1000W条。

分库分表的方式

范围拆分:每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了

hash拆分:按照某个字段hash一下均匀分散,这个较为常用

优缺点: 范围拆分扩容更方便,比如按照时间范围,一个月一个库,只要预备好新库,后续的数据都会保存在新库中 hash拆分可以平均分配每个库的压力,坏处是扩容比较麻烦,数据迁移时需要rehash。

分库分表如何平滑过渡

双写:同时写老库和新库,除了对老库增删改,都加上对新库的增删改
部署系统以后,新库数据落后太多,使用导数据的SQL脚本,读老库写新库。写的时候要比较数据的时间戳,不允许用老数据覆盖新数据。
导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

left join和inner join的区别,嵌套子查询如何优化

left join(左联接) 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行。
right join(右联接) 关键字会右表 (table_name2) 那里返回所有的行,即使在左表 (table_name1) 中没有匹配的行。
inner join(等值连接) 只返回两个表中联结字段相等的行
嵌套子查询的效率低:执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,多了一个创建和销毁临时表的过程
嵌套子查询的优化方案:
1、使用join,join比嵌套子查询更高效,数据量较大时,无需真正带入不同参数循环迭代
2、拆分为多个查询语句

InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。
InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

InnoDB使用间隙锁的目的:

防止幻读,以满足相关隔离级别的要求;
满足恢复和复制的需要:
MySQL 通过 BINLOG 录入执行成功的 INSERT、UPDATE、DELETE 等更新数据的SQL语句,并由此实现MySQL数据库的恢复和主从复制。MySQL的恢复机制(复制其实就是在Slave Mysql不断做基于BINLOG的恢复)有以下特点:

一是MySQL的恢复是SQL语句级的,也就是重新执行BINLOG中的SQL语句。

二是MySQL的Binlog是按照事务提交的先后顺序记录的, 恢复也是按这个顺序进行的。

由此可见,MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读。

不同粒度锁的比较:

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。这些存储引擎通过总是一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁。
表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web应用

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
最大程度的支持并发,同时也带来了最大的锁开销。
在 InnoDB 中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。行级锁只在存储引擎层实现,而Mysql服务器层没有实现。
行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。

MySQL数据类型

分类类型名称说明
整数类型tinyInt很小的整数(8位二进制)
整数类型smallint小的整数(16位二进制)
整数类型mediumint中等大小的整数(24位二进制)
整数类型int(integer)普通大小的整数(32位二进制)
小数类型float单精度浮点数
小数类型double双精度浮点数
小数类型decimal(m,d)压缩严格的定点数
日期类型yearYYYY 1901~2155
日期类型timeHH:MM:SS -838:59:59~838:59:59
日期类型dateYYYY-MM-DD 1000-01-01~9999-12-3
日期类型datetimeYYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00~ 9999-12-31 23:59:59
日期类型timestampYYYY-MM-DD HH:MM:SS 19700101 00:00:01 UTC~2038-01-19 03:14:07UTC
文本、二进制类型CHAR(M)M为0~255之间的整数
文本、二进制类型VARCHAR(M)M为0~65535之间的整数
文本、二进制类型TINYBLOB允许长度0~255字节
文本、二进制类型BLOB允许长度0~65535字节
文本、二进制类型MEDIUMBLOB允许长度0~167772150字节
文本、二进制类型LONGBLOB允许长度0~4294967295字节
文本、二进制类型TINYTEXT允许长度0~255字节
文本、二进制类型TEXT允许长度0~65535字节
文本、二进制类型MEDIUMTEXT允许长度0~167772150字节
文本、二进制类型LONGTEXT允许长度0~4294967295字节
文本、二进制类型VARBINARY(M)允许长度0~M个字节的变长字节字符串
文本、二进制类型BINARY(M)允许长度0~M个字节的定长字节字符串
  • 1、整数类型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。
    长度:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。
    例子:假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。

  • 2、实数类型,包括FLOAT、DOUBLE、DECIMAL。
    DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。
    而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。
    计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。

  • 3、字符串类型,包括VARCHAR、CHAR、TEXT、BLOB
    VARCHAR用于存储可变长字符串,它比定长类型更节省空间。
    VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。
    VARCHAR存储的内容超出设置的长度时,内容会被截断。
    CHAR是定长的,根据定义的字符串长度分配足够的空间。
    CHAR会根据需要使用空格进行填充方便比较。
    CHAR适合存储很短的字符串,或者所有值都接近同一个长度。
    CHAR存储的内容超出设置的长度时,内容同样会被截断。

    使用策略:
    对于经常变更的数据来说,CHAR比VARCHAR更好,因为CHAR不容易产生碎片。
    对于非常短的列,CHAR比VARCHAR在存储空间上更有效率。
    使用时要注意只分配需要的空间,更长的列排序时会消耗更多内存。
    尽量避免使用TEXT/BLOB类型,查询时会使用临时表,导致严重的性能开销。

  • 4、枚举类型(ENUM),把不重复的数据存储为一个预定义的集合。
    有时可以使用ENUM代替常用的字符串类型。
    ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。
    ENUM在内部存储时,其实存的是整数。
    尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。
    排序是按照内部存储的整数

  • 5、日期和时间类型,尽量使用timestamp,空间效率高于datetime,
    用整数保存时间戳通常不方便处理。
    如果需要存储微妙,可以使用bigint存储。

B树和B+树的区别

  • 在B树中,键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
  • B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。

使用B树的好处

  • B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。

使用B+树的好处

  • 由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间。

数据库为什么使用B+树而不是B树

  • B树只适合随机检索,而B+树同时支持随机检索和顺序检索;

  • B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;

  • B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。

  • B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。

  • 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。

聚簇索引和非聚簇索引

  • 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据。

  • 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。

MySQL的锁

共享锁和排他锁

  • 共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁就是让多个线程同时获取一个锁。

  • 排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排它锁也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。

隔离级别与锁的关系

  • 在Read Uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突

  • 在Read Committed级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;

  • 在Repeatable Read级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。

  • SERIALIZABLE是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。

乐观锁和悲观锁

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

select status from t_goods where id=1 for update;

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

SQL语句主要分为哪几类

  • 数据定义语言DDL(Data Ddefinition Language)CREATE,DROP,ALTER

    主要为以上操作 即对逻辑结构等有操作的,其中包括表结构,视图和索引。

  • 数据查询语言DQL(Data Query Language)SELECT

    这个较为好理解 即查询操作,以select关键字。各种简单查询,连接查询等 都属于DQL。

  • 数据操纵语言DML(Data Manipulation Language)INSERT,UPDATE,DELETE

    主要为以上操作 即对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。

  • 数据控制功能DCL(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK

    主要为以上操作 即对数据库安全性完整性等有操作的,可以简单的理解为权限控制等。

SQL语句的语法顺序

1、SELECT
2、FROM
3、JOIN
4、ON
5、WHERE
6、GROUP BY
7、HAVING
8、UNION
9、ORDER BY
10、LIMIT

MySQL Explain

expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra。

1、id

SELECT识别符。这是SELECT的查询序列号。

2、select_type

查询中每个select子句的类型。

(1) SIMPLE(简单SELECT,不使用UNION或子查询等)

(2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)

(3) UNION(UNION中的第二个或后面的SELECT语句)

(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)

(5) UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)

(6) SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)

(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)

(8) DERIVED(派生表的SELECT, FROM子句的子查询)

(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

3、table

显示这一步所访问数据库中表名称。

4、type

对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。

常用的类型有: ALL、index、range、 ref、eq_ref、const、system、 NULL(从左到右,性能从差到好)

ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行。

index: Full Index Scan,index与ALL区别为index类型只遍历索引树。

range:只检索给定范围的行,使用一个索引来选择行。

ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。

eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者unique key作为关联条件。

const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system。

NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。

5、possible_keys
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示null)。

6、key

key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中。

7、key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)。

8、ref

列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。

9、rows

估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。

10、extra

Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤。

Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by; order by。

Using filesort:当Query中包含order by操作,而且无法利用索引完成的排序操作称为“文件排序”。

Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。

Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。

Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行。

No tables used:Query语句中使用from dual或不含任何from子句。