基本概念
ACID 原则
ACID,即 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)四种特性的缩写。
ACID 也是一种比较出名的描述一致性的原则,通常出现在分布式数据库等基于事务过程的系统中。
具体来说,ACID 原则描述了分布式数据库需要满足的一致性需求,同时允许付出可用性的代价。
- Atomicity:每次事务是原子的,事务包含的所有操作要么全部成功,要么全部不执行。一旦有操作失败,则需要回退状态到执行事务之前;
- Consistency:数据库的状态在事务执行前后的状态是一致的和完整的,无中间状态。即只能处于成功事务提交后的状态;
- Isolation:各种事务可以并发执行,但彼此之间互相不影响。按照标准 SQL 规范,从弱到强可以分为未授权读取、授权读取、可重复读取和串行化四种隔离等级;
- Durability:状态的改变是持久的,不会失效。一旦某个事务提交,则它造成的状态变更就是永久性的。
延伸-BASE与CAP
BASE原则
与 ACID 相对的一个原则是 eBay 技术专家 Dan Pritchett 提出的 BASE(Basic Availability,Soft-state,Eventual Consistency)原则。BASE 原则面向大型高可用分布式系统,主张牺牲掉对强一致性的追求,而实现最终一致性,来换取一定的可用性。
- 基本可用(Basically Available):系统保证在出现故障或者异常情况时,仍然能够保证基本的可用性,即系统能够正常响应用户请求,但是可能会出现一些数据的不一致或者错误。
- 软状态(Soft State):系统中的数据状态不需要是强一致性的,即允许数据在一段时间内处于中间状态,而不是必须保证数据在任何时间点都是完全一致的。这样可以提高系统的可扩展性和容错性。
- 最终一致性(Eventual Consistency):系统保证最终会达到一致性状态,但是在数据更新后,不同节点之间可能存在一段时间的数据不一致。这种不一致是暂时的,随着时间的推移会逐渐被修复,直到所有节点都达到一致性状态。
BASE的设计理念是为了满足大规模分布式系统的需求,它与ACID的设计理念不同,不要求在所有情况下都严格保证数据的一致性,而是允许在某些情况下牺牲一部分的一致性,以换取更高的可用性和可扩展性。但是,在实际的应用中,需要根据具体的业务需求和系统特点,选择适合的数据管理系统设计理念,综合考虑数据的一致性、可用性和可扩展性等方面的因素。
CAP
CAP是指分布式系统中三个基本特性的缩写:
- 一致性(Consistency):所有节点在同一时间看到的数据是一致的,即所有节点都具有相同的数据副本。在分布式系统中,这意味着在进行更新操作之后,所有节点必须最终达到一致的状态。
- 可用性(Availability):系统能够在任何时间点正常响应用户请求,即系统的服务不会因为某些异常情况而停止响应。
- 分区容错性(Partition tolerance):分布式系统能够在网络分区或者节点故障的情况下继续运行。这意味着系统在遇到网络分区或者节点故障时,仍然能够保证数据的一致性和可用性。
CAP理论指出,在一个分布式系统中,最多只能同时满足其中的两个特性,而不能同时满足所有三个特性。因此,当系统遇到网络分区或者节点故障时,分布式系统需要做出权衡,选择满足一致性和可用性中的哪一个特性,从而保证系统的正确性和可靠性。如果选择了分区容错性,那么系统可能会出现数据不一致的情况;如果选择了一致性和可用性,那么系统可能会出现停机和延迟响应等问题。
在实际的分布式系统中,需要根据具体的业务需求和系统特点,选择适合的CAP特性,综合考虑数据的一致性、可用性和容错性等方面的因素。
数据库并发事务存在问题
脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)都是数据库中的并发控制问题,主要影响事务的隔离性。
- 脏读(Dirty read):指一个事务读取了另一个事务还未提交的未经提交的数据。如果另一个事务回滚,那么第一个事务读取的数据就是无效的,这可能会导致数据的不一致性。
- 不可重复读(Non-repeatable read):指一个事务在读取同一行记录时,不同时间内读取到的数据不一致。这是因为在两次读取之间,另一个事务修改或删除了该行记录。
- 幻读(Phantom read):指一个事务在读取一组记录时,第二次读取时发现多了或少了一些记录。这是因为在两次读取之间,另一个事务插入了一些记录或者删除了一些记录。
脏读、不可重复读、幻读都是由于并发事务之间的竞争而导致的问题,可以通过加锁或者使用更高级别的隔离级别来解决。
数据库隔离级别
SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
- 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
隔离级别解决并发问题
数据库默认隔离级别
MYSQL
默认隔离级别是可重复读
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.01 sec)
mysql> show global variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)
延伸:SESSION隔离级别是指在一个 MySQL 会话中,可以通过设置隔离级别来控制该会话中的事务隔离级别。在同一个 MySQL 实例中,不同会话可以设置不同的隔离级别。可以使用以下命令来设置 SESSION 隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL ;
其中,isolation_level 表示要设置的隔离级别,可以是 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE。
ORACLE
Oracle默认的隔离级别是读已提交(Read Committed)
一条查询SQL执行过程
SQL语句执行整体流程如下,在前面的整体架构图中也介绍过
客户端
客户端主要是通过MYSQL驱动来和数据库进行通信,MySQL 驱动是一种软件组件,用于在应用程序和 MySQL 数据库之间建立连接,以便应用程序可以访问和操作 MySQL 数据库。MySQL 驱动通常有以下几种类型:
JDBC驱动(Java Database Connectivity)、ODBC驱动(Open Database Connectivity)、Connector/C 驱动等
客户端需要频繁使用数据库连接,采用池化技术-数据库连接池来进行管理:维护一定的连接数,方便系统获取连接,使用就去池子中获取,用完放回去就可以了,我们不需要关心连接的创建与销毁,也不需要关心线程池是怎么去维护这些连接的。
常见的数据库连接池有 Druid、C3P0、DBCP,后面有一节专门讲述
连接器
连接器负责跟客户端建立连接、获取权限、维持和管理连接。
命令如下:
/home/mysql/mysql3306/bin/mysql -uroot -p -S /home/mysql/mysql3306/mysqld.sock
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。
- 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
- 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
划重点:一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
show processlist 可以查看当前连接
mysql> show processlist;
+----+------+---------------------------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+---------------------------+------+---------+------+----------+------------------+
| 2 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 8 | root | TOBY-HYW.mshome.net:24708 | NULL | Sleep | 27 | | NULL |
| 9 | root | TOBY-HYW.mshome.net:24709 | NULL | Sleep | 27 | | NULL |
+----+------+---------------------------+------+---------+------+----------+------------------+
3 rows in set (0.00 sec)
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。
mysql> show variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout | 28800 |
+---------------+-------+
1 row in set (0.01 sec)
建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。
MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。解决此问题:
查询缓存
命中则直接结束,一般不使用。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。query_cache_type(OFF|ON|DEMAND 按需,使用select SQL_CACHE * from T where ID=10显示指定)控制 默认是关闭的
mysql> show variables like 'query_cache_type';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| query_cache_type | OFF |
+------------------+-------+
MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
分析器
词法分析、语法分析
如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒
优化器
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。
执行器
MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误,
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
引擎层
提供接口给执行层使用,分为多个执行引擎,后面单独讲述
问题解答
问题1
如果表 T 中没有字段 k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?
答案:分析器
一条更新SQL执行过程
更新SQL和查询SQL在server层是一样的(在查询缓存时将缓存失效调),在引擎层不一样,更新流程还涉及两个重要的日志模块
参考
1.一条SQL语句是如何执行的
2.MySQL - 一条 SQL 的执行过程详解