ACID
原子性(Atomicity)
事务是一个原子操作单元,事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistent)
在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构也都必须是正确的。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
例如转账,假设用户 A 和用户 B 两者的钱加起来一共是 5000,那么不管 A 和 B 之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是 5000,这就是事务的一致性。
隔离性(Isolation)
数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的独立环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务 T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始之前就已经结束,要么在 T1 结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
持久性(Durable)
事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。Mysql 中会保存有相应的操作日志,即使遭遇故障依然能够通过日志恢复最后一次更新。
例如在使用 JDBC 操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
隔离级别
事务的并发问题
- 脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。
- 幻读:A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是 B 就在这个时候插入了一条具体分数的记录,当 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
注:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
读取未提交(Read uncommitted)
还没来得及提交就被读了,原因是只是写事务阻止其他写事务,只避免了更新遗失,但是没有阻止其他读事务。
存在的问题:脏读,即读取到不正确的数据,因为另一个事务可能还没提交最终数据,这个读事务就读取了中途的数据,这个数据可能是不正确的。
读取已提交(Read committed)
读事务不会阻止其他任何事务,但是写事务会
存在的问题:不可重复读。即在一次事务之间,进行了两次读取,但是结果不一样,可能第一次 id 为 1 的人叫“李三”,第二次读 id 为 1 的人就叫了“李四”。因为读取操作不会阻止其他事务。
可重复读(Repeatable read)
MySQL 的默认隔离级别,读事务会阻止其他写事务,但是不会阻止其他读事务。
存在的问题:幻读。可重复读阻止的写事务包括 update 和 delete(只给存在的表加上了锁),但是不包括 insert(新行不存在,所以没有办法加锁),所以一个事务第一次读取可能读取到了 10 条记录,但是第二次可能读取到 11 条,这就是幻读。
序列化/串行化(Serializable)
可避免幻读。读加共享锁,写加排他锁。这样读取事务可以并发,但是读写,写写事务之间都是互斥的,基本上就是一个个执行事务,所以叫序列化。
MySql 锁
全局锁
全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是Flush tables with read lock。当需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都 select 出来存成文本
表级锁
表锁的语法是 lock tables … read/write。可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放。如果在某个线程 A 中执行lock tables t1 read,t2 wirte;这个语句,则其他线程写 t1、读写 t2的语句都会被阻塞。同时,线程A在执行unlock tables之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许
另一类表级的锁是 MDL。MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做了变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定不行。事务中的MDL锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放
行锁
MySQL 的行锁是在引擎层由各个引擎自己实现的。但不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁
行锁就是针对数据表中行记录的锁。比如事务A更新了一行,而这时候事务B也要更新同一行,则必须等事务 A 的操作完成后才能进行更新
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议
如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
假设要实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。业务需要涉及到以下操作:
- 从顾客 A 账户余额中扣除电影票价
- 给影院 B 的账户余额增加这张
MySql 引擎
Innodb
提供了对数据库 ACID 事务的支持。并且还提供了行级锁和外键的约束。
该引擎是不支持全文搜索的。同时,启动也比较的慢,它是不会保存表的行数的。当进行Select count(*) from table指令的时候,需要进行扫描全表。所以当需要使用数据库的事务时,该引擎就是首选。由于锁的粒度小,写操作是不会锁定全表的。所以在并发度较高的场景下使用会提升效率的。
MyISAM
它是 MySql 的默认引擎,但不提供事务的支持,也不支持行级锁和外键。因此当执行 Insert 插入和 Update 更新语句时,即执行写操作的时候需要锁定这个表。所以会导致效率会降低。不过和 Innodb 不同的是,MyISAM 引擎是保存了表的行数,于是当进行Select count(*) from table语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的读操作远远多于写操作时,并且不需要事务的支持的。可以将 MyISAM 作为数据库引擎的首先。
索引类型
唯一索引
索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
主键索引
一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引
组合索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
全文索引
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。它更像是一个搜索引擎,而不是简单的 where 语句的参数匹配。只有 char、varchar,text 列上可以创建全文索引。
索引缺点
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行 insert、update 和 delete。因为更新表时,不仅要保存数据,还要保存一下索引文件。
- 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果在一个大表上创建了多种组合索引,索引文件的会增长很快。索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
索引失效
- 前导模糊查询不能利用索引(like '%XX'或者like '%XX%')
- 条件中有 or
- 索引无法存储 null 值,所以 where 的判断条件如果对字段进行了 null 值判断,将导致数据库放弃索引而进行全表查询
- in 和 not in、!= 或 <> 操作符、函数操作会导致索引失效
慢查询日志
开启慢查询日志,可以让 MySQL 记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能
慢查询开启状态:SET GLOBAL slow_query_log = ON;
设置慢查询日志的阈值(以秒为单位):SET GLOBAL long_query_time = 2;
慢查询日志存放的位置:SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';