问题1: mysql是磁盘数据库,那么他储存的数据都马上落盘了吗?
不是的,mysql 新增的数据都是先储存到缓冲区,再找合适的时机将缓冲区的数据一起落盘
问题 2: 什么时候数据落盘,为什么要这样做,有什么好处?
什么时候落盘不清楚,与缓冲区的大小与时间有关
好处: mysql 的写的性能会高非常多;就是磁盘顺序写入的性能是 60 多万的 qps,mysql 还不是顺序写,性能会更低, 但是内存写的性能在上千万qps,性能相差几十倍.
问题3: 那如果 mysql 突然宕机,或者突然断电怎么办.
那就是这里的主要内容
redo log
刚刚的问题 3:因为内存断电数据会丢失,那么mysql 是如何解决数据丢失的问题的呢?
mysql 采用的是WAL (Write-Ahead Logging)技术。
简单来讲就是采用记日志的方式避免数据的丢失,这里就是记录在 redo log 中;因为记录数据的开销比记录日志的开销更大
问题4: 为什么记录数据的开销更大,大多少?
数据的指定位置写是一件非常麻烦开销非常大的事情
比如同样 10 万条数据
顺序写
// 压力测试文件顺序写入
func TestFile(t *testing.T) {
// 打开文件
path := fmt.Sprintf("bench_file_%s.txt", time.Now().String())
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
// defer f.Close()
start := time.Now()
// 写入数据
for i := 0; i < 10_0000; i++ {
_, err := f.WriteString(fmt.Sprintf("data case %d", i))
if err != nil {
t.Fatal(err)
}
}
// 时间 164.484541ms
fmt.Println(time.Since(start))
}
指定位置(行)写
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"time"
)
// WriteToNthLine writes the string s to the nth line of the given file
func WriteToNthLine(file *os.File, n int, s string) error {
if n < 1 {
return fmt.Errorf("line number must be greater than 0")
}
// Read the file content into memory
file.Seek(0, 0)
scanner := bufio.NewScanner(file)
var buffer bytes.Buffer
currentLine := 1
lineWritten := false
for scanner.Scan() {
if currentLine == n {
// Write the new content to the buffer
buffer.WriteString(s + "\n")
lineWritten = true
} else {
// Write the original content to the buffer
buffer.WriteString(scanner.Text() + "\n")
}
currentLine++
}
// If the target line is beyond the end of the file, append new lines
for currentLine <= n {
if currentLine == n {
buffer.WriteString(s + "\n")
lineWritten = true
} else {
buffer.WriteString("\n")
}
currentLine++
}
if !lineWritten {
return fmt.Errorf("failed to write to the specified line")
}
// Write the buffer content back to the file
file.Truncate(0)
file.Seek(0, 0)
_, err := file.Write(buffer.Bytes())
if err != nil {
return fmt.Errorf("failed to write to file: %w", err)
}
return nil
}
// 指定行写入
func main() {
// 打开文件
path := fmt.Sprintf("bench_file_%s.txt", time.Now().String())
f, err := os.Create(path)
if err != nil {
log.Fatal(err)
}
// defer f.Close()
start := time.Now()
// 写入数据
for i := 1; i < 10_0001; i++ {
err := WriteToNthLine(f, i, fmt.Sprintf("data case %d", i))
if err != nil {
log.Fatal(err)
}
}
// 时间2m34.389153084s
fmt.Println(time.Since(start))
}
10 万条数据,顺序写需要 164.484541ms;而指定位置写需要2m34.389153084s;速度相差几百倍;
当然 mysql 不会使用这种暴力的写法;他每个数据的大小是固定的,这样就不会涉及到数据的重写问题,会快非常多,但是还是没有顺序写快
问题5 redo log会马上落盘吗?
不一定;首先不仅仅是写的数据会到 redo log;undo log 也会储存到 redo log;所以 redo log 也有一个缓冲区
redo log的自动落盘
-
每一秒
-
事务提交(innodb_flush_log_at_trx_commit=1)
a. innodb_flush_log_at_trx_commit=0;提交落 redo log 的缓存
b. innodb_flush_log_at_trx_commit=1提交落盘
c. innodb_flush_log_at_trx_commit=2 提交落内核缓存 -
大于缓冲区的一半
-
mysql 正常关闭
问题6 0 与 2 的区别?
0 : mysql故障(宕机),会丢数据
2 : mysql 故障,机器正常不会丢数据, 机器故障(突然断电)会丢数据
2 就是把数据交给操作系统了,操作系统正常数据就不会丢.
问题 7:事务进行到一半突然断电怎么办
mysql 的事务没有提交,就会回到事务开始前的状态,但是 redo log只能恢复到故障/断电之前,并不能恢复到事务之前的状态.
undo log
- 作用:事务回滚,让数据回到事务开始之前的状态
- 记录:undo log 记录更改前的数据版本 id 与状态
- 如何回滚: 根据数据版本的 id,使用 undo log 的数据去覆盖当前的数据
问题 8 undo log 如何持久化?
跟其他数据的持久化方式相同,先储存缓冲区,在储存 redo log 的缓冲区,1s 落一次盘
问题 9 : 查询一条记录会进行缓存吗?
不是的,而是缓存一页数据(16k)