Mysql_一致的非锁定读取

一致的非锁定读取

一致的读取 该装置InnoDB在一个时间点使用多版本存在于查询数据库的快照。该查询将看到在该时间点之前提交的事务所做的更改,而看不到以后或未提交的事务所做的更改。该规则的例外是查询可以看到同一事务中较早的语句所做的更改。此异常导致以下异常:如果更新表中的某些行,则 SELECT查看已更新行的最新版本,但也可能会看到任何行的旧版本。如果其他会话同时更新同一张表,则异常意味着您可能会以数据库中不存在的状态查看该表。

如果事务 隔离级别REPEATABLE READ(默认级别),则同一事务中的所有一致读取将读取由该事务中的第一个此类读取建立的快照。您可以通过提交当前事务并在此之后发出新查询来获取查询的更新快照。

使用READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。

一致的读取是在默认模式 InnoDB进程 SELECT中的语句 READ COMMITTEDREPEATABLE READ隔离级别。一致读取不会在它访问的表上设置任何锁,因此其他会话可以自由地在对表执行一致读取的同时修改这些表。

假设您以默认REPEATABLE READ隔离级别运行 。当您发出一致的读取(即一条普通 SELECT语句)时, InnoDB为您的事务提供一个时间点,根据该时间点您的查询将看到数据库。如果另一个事务删除了一行并在分配了时间点后提交,则看不到该行已被删除。插入和更新的处理方式相似。

注意

数据库状态的快照适用 SELECT于事务中的语句,不一定适用于 DML语句。如果您插入或修改一些行,然后提交该事务,则另一个并发 事务发出的 DELETEor UPDATE语句REPEATABLE READ可能会影响那些刚刚提交的行,即使会话无法查询它们。如果某个事务确实更新或删除了另一个事务提交的行,则这些更改对于当前事务而言确实可见。例如,您可能会遇到以下情况:

SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match.
DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.

SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match.
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values.
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.

您可以通过提交事务,然后再执行另一个SELECT或 来提前时间START TRANSACTION WITH CONSISTENT SNAPSHOT

这称为多版本并发控制。

在以下示例中,会话A仅在B提交了插入并且A也提交了时,才看到B插入的行,因此时间点比B的提交提前了。

             Session A              Session B

           SET autocommit=0;      SET autocommit=0;
time
|          SELECT * FROM t;
|          empty set
|                                 INSERT INTO t VALUES (1, 2);
|
v          SELECT * FROM t;
           empty set
                                  COMMIT;

           SELECT * FROM t;
           empty set

           COMMIT;

           SELECT * FROM t;
           ---------------------
           |    1    |    2    |
           ---------------------

如果要查看数据库的“ 最新 ”状态,请使用READ COMMITTED隔离级别或 锁定读取

SELECT * FROM t FOR SHARE;

使用READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。使用时LOCK IN SHARE MODE,将发生锁定读取:A SELECT阻塞,直到包含最新行的事务结束(请参见 “锁定读取”)。

一致读取不适用于某些DDL语句:

  • 一致读取无法解决问题DROP TABLE,因为MySQL无法使用已删除InnoDB的表并破坏该表。
  • 一致读取不能解决在 ALTER TABLE创建临时表时创建原始表的临时副本并删除原始表的操作。当您在事务内重新发出一致的读取时,新表中的行不可见,因为在获取事务快照时这些行不存在。在这种情况下,事务返回一个错误: ER_TABLE_DEF_CHANGED, “ 表的定义发生了变化,请重试交易 ”。

读取的类型因选择子句(如INSERT INTO ... SELECT,)中的选择而有所不同 UPDATE ... (SELECT),并且 CREATE TABLE ... SELECT未指定FOR UPDATELOCK IN SHARE MODE

更多内容欢迎关注我的个人公众号“韩哥有话说”,100G人工智能学习资料,大量后端学习资料等你来拿。

韩哥有话说

<think>我们正在解决一个在设置 `MYSQL_ROOT_PASSWORD` 环境变量后密码无效的问题。根据引用内容,这个问题在多个场景中出现过(Kubernetes、Docker、docker-compose)。核心原因是:**第一次启动MySQL容器时设置的密码会被持久化,后续重新创建容器时如果使用了新的密码,但数据卷未重置,则新密码不会覆盖旧密码**。 ### 问题分析 1. **数据持久化机制**:MySQL容器在首次运行时初始化数据库,并将root密码等设置写入数据文件(通常存储在宿主机的卷或容器内部)。后续容器重启时,如果挂载了相同的数据卷,则不会重新初始化,因此环境变量中的新密码不会生效[^2][^3][^4]。 2. **错误表现**:用户尝试使用新设置的密码登录时收到"Access denied"错误,而旧密码(或空密码)却能登录。 ### 解决方案 #### 方法一:清除旧数据卷(推荐测试环境使用) 1. 停止并删除容器: ```bash docker stop my-mysql docker rm my-mysql ``` 2. 删除关联的持久化卷: ```bash docker volume rm mysql_data # 替换为实际卷名 ``` 3. 重新创建容器: ```bash docker run --name my-mysql -e MYSQL_ROOT_PASSWORD=newpassword -v mysql_data:/var/lib/mysql -d mysql:tag ``` > ⚠️ **注意**:此操作会**永久删除所有数据库数据**,仅适用于可重置的环境。 #### 方法二:运行时强制重置密码(保留数据) 1. 启动临时容器挂载原数据卷,并启用`--skip-grant-tables`: ```bash docker run -it --rm -v mysql_data:/var/lib/mysql mysql:tag mysqld --skip-grant-tables --skip-networking & ``` 2. 进入容器修改密码: ```bash docker exec -it container_id mysql -uroot ``` ```sql FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY 'newpassword'; -- 更新为你的新密码[^2] EXIT; ``` 3. 重启正常容器: ```bash docker run --name my-mysql -v mysql_data:/var/lib/mysql -d mysql:tag ``` #### 方法三:通过配置文件跳过权限验证(生产环境慎用) 1. 创建自定义MySQL配置文件`my.cnf`: ```ini [mysqld] skip-grant-tables ``` 2. 启动容器时挂载配置文件: ```bash docker run -v ./my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=newpassword -d mysql:tag ``` 3. 按方法二步骤修改密码后移除配置文件。 ### 预防措施 1. **单次密码原则**:一个数据卷只初始化一次密码,后续避免通过环境变量修改。 2. **数据卷命名管理**:明确区分新老数据卷,例如添加版本后缀`mysql_data_v1`。 3. **初始化脚本**:通过`/docker-entrypoint-initdb.d`挂载SQL脚本,在首次启动时修改密码[^4]。 > 📌 **核心原理**: > MySQL容器启动流程: > ```mermaid > graph LR > A[启动容器] --> B{数据目录是否为空?} > B ----> C[初始化数据库<br>应用MYSQL_ROOT_PASSWORD] > B ----> D[跳过初始化<br>使用原有密码] > ``` §§相关问题§§ 1. Docker中MySQL数据卷如何实现持久化存储? 2. 如何在Kubernetes中安全地重置MySQL root密码? 3. MySQL容器启动时如何自动执行初始化脚本? </think>### 问题分析与解决方案 设置 `-e MYSQL_ROOT_PASSWORD` 后密码无效的核心原因是:**MySQL 容器首次启动时会将密码持久化到数据卷中,后续重启容器时新密码不会覆盖原密码**。以下是完整解决方案: --- ### 一、根本原因解析 1. **首次启动固化** 首次运行容器时,`MYSQL_ROOT_PASSWORD` 生成的密码会写入 `/var/lib/mysql` 目录并固化[^2][^3]。 $$ \text{初始密码} \xrightarrow{\text{持久化存储}} \text{数据卷} $$ 2. **后续修改无效** 重新创建容器时: - 若挂载**相同数据卷**,MySQL 直接读取旧密码而非新密码[^4] - 环境变量仅在**初始化空数据库**时生效[^1] --- ### 二、分步解决方案 #### ▶ 场景1:需要保留原有数据 ```bash # 1. 启动临时容器(跳过权限验证) docker run -d --name temp-mysql \ -v your_volume:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=anything \ # 此处密码无效 mysql:tag \ mysqld --skip-grant-tables # 2. 进入容器修改密码 docker exec -it temp-mysql mysql ``` ```sql -- SQL 执行 UPDATE mysql.user SET authentication_string=PASSWORD('你的新密码') -- MySQL 5.7 SET authentication_string='' WHERE user='root'; -- MySQL 8.0+ ALTER USER 'root'@'localhost' IDENTIFIED BY '你的新密码'; FLUSH PRIVILEGES; ``` ```bash # 3. 重启正式容器 docker stop temp-mysql docker run ... # 正常启动命令(无需特殊参数) ``` #### ▶ 场景2:可清除旧数据(推荐测试环境) ```bash # 1. 删除旧数据卷 docker volume rm your_mysql_volume # 2. 重新创建容器(自动应用新密码) docker run --name mysql-new \ -e MYSQL_ROOT_PASSWORD=your_strong_password \ # ✅ 此时生效 -v mysql_data:/var/lib/mysql \ # 新建空卷 -d mysql:tag ``` #### ▶ Kubernetes 特殊处理(StatefulSet) ```yaml # mysql-statefulset.yaml 关键配置 spec: template: spec: containers: - name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: password volumeMounts: - mountPath: /var/lib/mysql name: data volumes: - name: data emptyDir: {} # ⚠️ 每次Pod重启清空数据 ``` > 注意:生产环境需改用 `persistentVolumeClaim` 并配合密码重置流程[^2] --- ### 三、预防措施 1. **初始化后锁定密码** 首次启动后不再通过环境变量修改密码,使用 `ALTER USER` 命令修改 ```sql ALTER USER 'root'@'%' IDENTIFIED BY 'new_password'; ``` 2. **数据卷版本控制** ```bash # 数据卷名称添加版本号 -v mysql_data_v1:/var/lib/mysql ``` 3. **使用初始化脚本** 在 `/docker-entrypoint-initdb.d` 目录放置SQL脚本: ```sql /* init.sql */ ALTER USER 'root'@'localhost' IDENTIFIED BY '动态密码'; ``` --- > 💡 **核心原理总结**: > MySQL 镜像通过 `/entrypoint.sh` 检测 `/var/lib/mysql` 目录状态: > ```mermaid > graph LR > A[启动容器] --> B{/var/lib/mysql 是否为空?} > B ----> C[应用 MYSQL_ROOT_PASSWORD 初始化] > B ----> D[跳过初始化<br>使用现有数据] > ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值