事务(ACID)
- 原子性:事务中的所有操作要么全部成功要么全部失败回滚到最初状态。通过undolog回滚日志保证
- 一致性:事务操作之前和操作之后数据保持完整约束,数据库保证一致性状态,比如A有100,B有100,A给B转100块钱,最后的状态就是A为0,B账户200,他们的总金额始终是200不能改变,或者这个A给B转了150,进行一个透支这是不允许的,同时在这个改变过程,A余额字段被更新成字符串了,这也是不允许的。通过原子性,隔离性,持久性一起保证。
- 隔离性:高并发场景下,多个事务之间的操作互不干扰,防止多个事务交叉执行导致数据不一致,通过MVCC保证
- 持久性:事务处理结束后,操作是永久生效的,即便系统故障也不会丢失,通过redolog重做日志保证
mysql的并发问题(通过锁机制和事务管理机制解决)
- 脏读:读到别人未提交的数据
- 不可重复读:针对同一行数据,两次读取数据操作之间有其他的事务对数据进行修改,导致两次读取到的数据值不一样
- 幻读:在一个事务内,多次进行同一个范围查询,由于有其他的事务进行了插入/删除操作,导致两次查询的行数不一样,然后后续基于这个范围查询进行一些额外的操作会影响到一些意外的行,不如查询age>20的行数,能够查到10条,这个时候插入一条age>20的数据,这个时候再查询,发现查出来11条,如果我要对这个范围的数据进行删除,那么本来应该删除10行,结果删除了11行数据,对额外的这一行造成了影响
不适合脏读的场景
- 银行系统;对某个用户余额进行调整,读取到这个临时的余额,导致用户看到了不正确的信息
- 库存管理:商品库存信息正在被更改但是还没有提交就被读取,导致库存管理出错
事务隔离级别
- mysql默认是可重复读级别,前提是在这个InnoDB引擎下
- 读未提交:三种读问题都有
- 读已提交:解决脏读,在每一语句执行之前都生成一个ReadView
- 可重复读:解决不可重复读,在事务启动的时候就生成一个ReadView,然后整个事务内都通过这一个ReadView在undolog里面找到事务最开始的一个数据,所以每次数据查询都是一样的,但是可能会有一点数据的不一致性
- 串行化:解决幻读,通过加锁的方式避免并行访问
MVCC实现原理
- 多版本并发控制,允许多个事务读取同一行数据而不会发生阻塞,主要是在事务开始的时候就创建一个ReadView,每个数据看到的就是这个事务开始时候的版本。像可重复读和读已提交就是创建这个ReadView的时机不同
update操作是不是原子性的
- update更新数值的时候会对这一行进行加锁操作,防止其他事务进行干扰
- 同时会在undolog日志里面进行记录,如果更新失败根据undolog日志进行回滚
- 如果两个update语句同时处理一条数据,是会产生阻塞的
mysql有哪些锁?
-
全局锁:FTWRL通过flash tables with read lock让整个数据库编程只读状态,主要用于全局备份,避免备份的时候有数据和结构被修改
-
表级锁:表锁,元数据锁,意向锁,AUTO-INC锁
-
行级锁:Record lock,Gap lock,Next key lock
-
表锁:通过lock tables给表加锁,像MyISAM引擎没有MVCC和行级锁,只能依赖表级锁来保证数据的一致性
-
元数据锁:当用户堆数据库表进行CRUD的时候就加MDL锁,为了防止在用户CRUD的时候有其他的用户对这个表结构进行修改
-
意向锁:当多个事务想要访问一个共享资源的时候,如果每个事务都直接请求获得锁,有可能造成互相阻塞,所以在有请求的时候会自动获得该表的意向锁,这样其他的事务请求时候会判断这个表是否加了锁,他是否能去请求获取锁,从而不阻塞其他事务
-
记录锁:共享锁互斥锁,锁住的是一条记录。共享锁数据只能读数据不能修改数据,排它锁加了之后其他事务不能再对这个数据加任何锁,加锁的事务可以对这个数据进行读写操作
日志种类
- redo log日志,实现事务中的持久性,在服务器崩溃掉之后能恢复。在事务提交之前把事务的操作写入到redo log日志里面,然后再写入磁盘,这样即便宕机也可以通过redo log记录来恢复(WAL技术)
- undo log日志,实现了事务的原子性,主要是为了事务的回滚和MVCC
- bin log日志:二进制日志,主要用于日志备份以及主从复制。记录了数据库表结构或者数据更新的操作,不会记录查询操作,而且是追加文件,写满之后创建新的文件继续写,不会覆盖以前的数据。
- relay log中继日志:用于主从复制场景,拷贝master的bin log后本地生成的日志
- 慢查询日志:用于记录执行时间过长的sql语句
update语句更新的具体过程
- 比如update user set name='小林' where id=1;
- 执行器负责执行,通过主键索引树获得id=1这一行,如果这一行所在数据页本来就在buffer pool中,就直接返回给执行器,如果不在就在磁盘里面读取
- 执行器等到索引记录之后会判断更新前个更新后的数据是否一样
- 开启事务,记录相应的undo log日志
- 开始更新数据,先更新内存,然后写到redo log日志中
- 更新语句执行完之后,记录该语句对应的binlog日志
- 事务提交
mysql两次写
- linux系统页大小默认是4kB,而mysql的页默认是16kB,所以一页数据刷到磁盘里面,需要写4个系统文件页,这过程不是原子性的,如果这个时候linu突然断电,就会出现数据页损坏,这个是redo log日志没办法恢复的。
- 所以在把页数据写到这个数据文件之前,先写到一个Doublewrite buffer一个共享空间中,然后再往数据文件中写入,如果linux断电了,启动之后发现数据有损坏,就去doublewrite buffer中用于恢复,本质就是写了一个备份
性能调优
- explain:查看sql的执行计划,主要用来分析sql语句的执行过程,比如有没有走索引,有没有索引覆盖。主要参数有:possible_key表示可能遇到的索引,key表示实际使用的索引,如果为NULL就表示没有使用索引,rows表示扫描的行数,type表示数据扫描类型(有全表扫描,全索引扫描,索引范围扫描,非唯一索引扫描,唯一索引扫描)
- 查看表的索引:show index
- 查询很慢,解决方法:使用explain分析查询语句,创建或者优化索引,查询优化(避免使用select*),优化数据库表,使用缓存技术如redis