mysql的基础架构
Mysql是由SQL接口,解析器,优化器,缓存,存储引擎组成的。
connectors(连接管理器):与其他编程语言中的sql 语句进行交互
Management Serveices & Utilities:系统管理和控制工具
Connection Pool (连接池):管理缓冲用户连接,线程处理等需要缓存的需求
SQL Interface(SQL接口):接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
Parser (解析器):QL命令传递到解析器的时候会被解析器验证和解析。
主要功能:
a . 将SQL语句分解成数据结构,并将这个结构传递到后续步骤,后面SQL语句的传递和处理就是基于这个结构的
b. 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的,语句将不会继续执行下去
Optimizer (查询优化器):SQL语句在查询之前会使用查询优化器对查询进行优化(产生多种执行计划,最终数据库会选择最优化的方案去执行,尽快返会结果) 他使用的是“选取-投影-联接”策略进行查询。
Cache和Buffer (查询缓存):如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。
这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
Engine (存储引擎):存储引擎是MySql中具体的与文件打交道的子系统。也是Mysql最具有特色的一个地方。
Mysql的存储引擎是插件式的。它根据MySql AB公司提供的文件访问层的一个抽象接口来定制一种文件访问机制(这种访问机制就叫存储引擎)
mysql访问流程:
面向用户的一端叫做:连接管理器
连接管理器负责接收用户连接,并建立用户连接
然后,到达查询缓存和分析器(如果缓存中有结果,那么直接从告诉缓存中返回给用户(只和读操作的查询操作有关系);如果缓存没有命中,那么交给分析器,做四大分析,如果分析的结果,发现缓存中有结果,再交给缓存,否则向下给优化器)
优化器完成优化,交给存储引擎(真正的执行者),然后交给存储端
连接管理器需要做的事:
接受请求
创建线程
认证用户
建立安全连接
mysql并发控制
并发控制:两个(或更多)用户,试图同时读写同一个数据,都会带来并发控制问题
myslq依赖的并发技术:
锁:读锁,写锁
时间戳:记录每个事务的启动执行时间
MVCC多版本快照隔离
多版本并发控制(MVCC):每一个用户在操作数据的时候,都不是操作原数据,而是数据的一个副本(快照),以时间戳为标志谁先谁后。
mysql的读写锁
锁:是最简单的并发控制机制(并不是多版本并发控制的机制)
读锁:共享锁,允许同时读,不允许写
写锁:独占锁,既不允许其他用户读,也不允许写
mysql对表手动加锁方式:
mysql> HELP LOCK Name: 'LOCK' Description: Syntax: LOCK TABLES tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... // tbl_name:表名 lock_type:锁类型 lock_type: READ [LOCAL] | [LOW_PRIORITY] WRITE UNLOCK TABLES //解锁
加锁示例:
MariaDB [fsx]> SHOW TABLES; +---------------+ | Tables_in_fsx | +---------------+ | fsx | +---------------+ MariaDB [fsx]> DESC fsx; +-------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+---------------------+------+-----+---------+----------------+ | cid | tinyint(3) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(30) | NO | | NULL | | | sex | enum('boy','sex') | YES | | NULL | | | age | tinyint(4) | NO | | NULL | | +-------+---------------------+------+-----+---------+----------------+ MariaDB [fsx]> LOCK TABLES fsx READ; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> INSERT INTO fsx (name,sex,age) VALUES("pqy","girl",21); ERROR 1099 (HY000): Table 'fsx' was locked with a READ lock and can't be updated MariaDB [fsx]> UNLOCK TABLES; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> INSERT INTO fsx (name,sex,age) VALUES("pqy","girl",21); Query OK, 1 row affected, 1 warning (0.07 sec)
mysql服务器支持表级锁,行锁需要由存储引擎完成
表锁
页锁
行锁
mysql事务
当需要对数据表执行一系列多个操作的情况下,为了防止这些操作中的部分操作执行成功而另一些操作执行失败,从而导致数据不正确,我们就需要使用事务了。
事务:多项操作指向一个处理单元,要么同时执行,要么都不执行。一个事务需要大量的cpu和io操作。 一般能够启动事务的操作:一堆sql语句,ODBC中启动事务的指令。
一个RDBMS支持事务,指的是能够满足四种ACID测试:
原子性:操作不可被打断
一致性:事务完成前后结果一致
隔离性:两个事务同时执行,第一个事务在完成之前不能被第二个事务所知道,即事务之间互不影响
持久性:即使服务器down机,数据不能丢失
mysql是插件式存储引擎,事务处理能力是由引擎提供的,MYISAM不支持事务,INnoDB支持事务。
一致性实现
一致性:在隔离状态下,数据库一定要从一个状态平稳转换到另一个状态,两个状态的状态基不变,服务器状态不变。如:
A账户原本有1000rmb,给B账户转500rmb,B账户原本也是1000rmb,转账结束后,AB账户金钱总和应该是200rmb不变
事务日志:可以完成对突然down机而未为完成的事务进行还原,从而提高ACID兼容性
重作日志(redo log):每一个操作在真正写入数据库之前写入日志中,即可以根据日志无限重作
撤销日志(undo log):在操作之前把原有的状态保留,万一需要还原,可以进行撤销所有操作
所有的mysql操作,先在内存中完成,事务结束后,写入到日志中,过一段时间,写入磁盘空间(数据文件),即由重作日志再完成一次
所以,在事务中,所有操作都是执行了两次,一次日志,一次磁盘数据文件。在日志中的操作是特别快的,因为日志仅仅记录操作,而不是操作数据本身。
如果一个事物结束,写入到了日志,但是从日志往数据文件中写入的时候,突然down机:
在每次启动时,mysql都会先读取日志,然后进行同步,也就是修复过程。
一般情况下,日志文件有两个,叫日志组,写满一个时,另一个接管,写满的日志文件去同步。日志文件不是越大越好,根据事务进行选择。切事务日志和数据文件不再同一磁盘上,保证一个磁盘坏了,可以还原。(牵扯到备份技术,和raid技术)
隔离性实现
如何启动事务,看下一节介绍
尽量让不同事务之间的干扰降低,所以让事务进行串行执行,但是这又失去了并发能里。为了折中解决该问题,sql引入了隔离级的技术,共有四种隔离级:
READ-UNCOMMITTED 读未提交:别的事务操作,立刻可以发现,这是最低的隔离级别
READ-COMMITTED 读提交:别的事务提交后,才能看到
REPEATABLE-READ 可重读:无论提交与否,至始至终,看到的数据不变
SERIABLIZABLE串行:
由上往下,并发能力越来越低,串行,隔离能力越来越高,mysql默认是REPEATABLE READ
MariaDB [fsx]> SHOW GLOBAL VARIABLES LIKE '%iso%'; //全局变量 +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+ //要想永久生效,修改配置文件 MariaDB [fsx]> SET GLOBAL tx_isolation="REPEATABLE-READ"; //临时修改全局变量 Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED"; //临时修改局部变量 Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT @@tx_isolation; +------------------+ | @@tx_isolation | +------------------+ | READ-UNCOMMITTED | +------------------+
事务隔离级别对事务的影响
读未提交
打开两个mysql会话,都设置隔离级别未READ-UNCOMMITTED,两个会话都打开事务
会话1:
MariaDB [fsx]> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set (0.00 sec) MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT @@tx_isolation; +------------------+ | @@tx_isolation | +------------------+ | READ-UNCOMMITTED | +------------------+ 1 row in set (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 21 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+
会话2:
MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec)
会话修改fsx表中一个数据:
MariaDB [fsx]> UPDATE fsx SET age=22 WHERE name="qpy"; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
因为隔离级别是读未提交,所以在会话2中查看:
MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 22 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
此时,会话1中撤回修改操作:
MariaDB [fsx]> ROLLBACK; Query OK, 0 rows affected (0.34 sec)
再在会话2中查看fsx表:
MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 21 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
这就出现了幻读
读提交
打开两个mysql会话,都设置隔离级别未READ-COMMITTED,两个会话都打开事务
会话1:
MariaDB [fsx]> SET tx_isolation="READ-COMMITTED"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 21 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
会话2:
MariaDB [fsx]> SET tx_isolation="READ-COMMITTED"; Query OK, 0 rows affected (0.00 sec)
会话1修改fsx表中数据,不提交情况下,会话2看不到fsx表的数据变化:
会话1修改数据:
MariaDB [fsx]> UPDATE fsx SET age=22 WHERE name="qpy"; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
会话2查看:
MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 21 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
当会话1提交后,会话2可以看到改变:
会话1提交:
MariaDB [fsx]> COMMIT; Query OK, 0 rows affected (0.07 sec)
会话2查看:
MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 22 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
所谓读提交,就是别人提交后才可读,不提交就看不到变化 这样在一次会话中,仍会出现幻读
可重读
打开两个mysql会话,都设置隔离级别未REPEATABLE-READ,两个会话都打开事务
会话1:
MariaDB [fsx]> SET tx_isolation="REPEATABLE-READ"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 28 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
会话2:
MariaDB [fsx]> SET tx_isolation="REPEATABLE-READ"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 28 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec)
会话1修改fsx表数据,并且提交:
MariaDB [fsx]> UPDATE fsx SET age=43 WHERE name="qpy"; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 43 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec) MariaDB [fsx]> COMMIT; Query OK, 0 rows affected (0.07 sec)
会话2查看fsx表:
MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 28 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec) //注意:尽管会话1提交了,但此时会话2还是不能看到变化,也就是REPEATABLE-READ比READ-COMMIT隔离级别更高
当会话2也进行提交,再次打开一个会话,查看fsx表:
MariaDB [fsx]> COMMIT; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 43 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec) //此时,数据变化
可重读,隔离的是同时打开的事务之间,即使提交完成,任何操作也都不可见,所以事务提交前后会有幻读问题
串行
打开两个mysql会话,都设置隔离级别未REPEATABLE-READ,两个会话都打开事务
会话1:
MariaDB [fsx]> SET tx_isolation="SERIALIZABLE"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+------+------+-----+ | cid | name | sex | age | +-----+------+------+-----+ | 4 | qpy | girl | 21 | | 5 | cooc | girl | 31 | | 6 | fsx | boy | 24 | +-----+------+------+-----+ 3 rows in set (0.00 sec) MariaDB [fsx]> UPDATE fsx SET age=44 WHERE name="qpy"; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
会话2如果此时也对fsx表中同样数据进行修改,则会进入阻塞状态:
ariaDB [fsx]> SET tx_isolation="SERIALIZABLE"; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
串行的隔离级别,禁止了并发,所以必须一个事务提交之后,另一个事务才进行对同一个数据的操作
原子性
原子性:一个事务可能有多个执行单元,这些语句要么同时都完成,要么都不完成,当作一个整体看待。即事务锁引起的所有数据库操作在数据库中完全执行,否则完全不执行
持久性
持久性:一旦事务成功执行完成,无论任何情况,任何意外,都必须保证事务结果可得到,不可再次发生变化,表现出一致性。
提供持久性的方法:
事务提交之前,将数据写入磁盘(随机IO),持久性存储。效率低,且撤销时还需要从磁盘上撤销
结合事务日志完成,先写入事务日志中(顺序IO),然后写入数据文件
事务一旦提交,就无法撤销,需要通过补偿事务来完成撤销。
事务的状态:
活动事务
部分提交事务:在提交过程中,一部分写入到磁盘,另一部分在写入。最后一条语句执行后,提交前
失败
终止
状态事务转换,从活动事务开始,到部分提交或者失败,如果部分提交完成,则提交;如果提交失败,则到终止执行状态。
为了进肯能提高资源利用率(IO吞吐量,减少等待时间,事务调度等),事务提交的过程允许并发,
事务调度策略:
可恢复调度
无级连调度
启动事务
START TRANSACTION:启动一个事务
SQL
COMMIT:提交一个事务
ROLLBACK:回滚,不提交
事务回滚示例:
MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 1 | pqy | | 21 | | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 2 rows in set (0.00 sec) MariaDB [fsx]> DELETE FROM fsx WHERE cid LIKE 1; Query OK, 1 row affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 1 row in set (0.00 sec) MariaDB [fsx]> ROLLBACK; Query OK, 0 rows affected (0.34 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 1 | pqy | | 21 | | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 2 rows in set (0.00 sec)
事务提交示例:
MariaDB [fsx]> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> DELETE FROM fsx WHERE cid LIKE 1; Query OK, 1 row affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 1 row in set (0.00 sec) MariaDB [fsx]> COMMIT; Query OK, 0 rows affected (0.11 sec) //此时提交后就无法回滚 MariaDB [fsx]> ROLLBACK; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 1 row in set (0.00 sec)
事务未提交之前,所有操作都可以撤销
mysql中有一个自动提交的全局变量,autocommit
MariaDB [fsx]> SELECT @@autocommit; +--------------+ | @@autocommit | +--------------+ | 1 | +--------------+ 1 row in set (0.00 sec) 是否会自动提交每一个sql语句,如果设置为1,当没有明确启动事务,则会自动提交;如果设置为0,当没有明确启动事务,则不会自动提交,也就是可以ROLLBACK
关闭自动提交,则不明确启动事务,也可以ROLLBACK
MariaDB [fsx]> SET autocommit=0; Query OK, 0 rows affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 1 row in set (0.00 sec) MariaDB [fsx]> DELETE FROM fsx WHERE cid=2; Query OK, 1 row affected (0.00 sec) MariaDB [fsx]> SELECT * FROM fsx; Empty set (0.00 sec) MariaDB [fsx]> ROLLBACK; Query OK, 0 rows affected (0.06 sec) MariaDB [fsx]> SELECT * FROM fsx; +-----+-------+------+-----+ | cid | name | sex | age | +-----+-------+------+-----+ | 2 | jerry | boy | 22 | +-----+-------+------+-----+ 1 row in set (0.00 sec)