通过Gorm使用数据库的一些进阶知识

467 阅读6分钟

写在前面

越来越发现,后端技术的学习似乎都是围绕数据库来展开的。今天主要就以Gorm使用MySQL数据库为例,来学习一下数据库的索引优化外键的相关知识。

索引优化

什么是索引?

一句话,帮助MySQL数据库高效获取数据数据结构。网络上有一种非常形象的说法:数据库的索引好比是一本书的目录。相比翻阅几百页来查找自己想要的内容,通过两三页的目录来直接查找相关标题从而找到对应的内容,这样做可以节省许多时间,提高数据库的查询速度。

索引主要有哪些类型?

唯一索引: 唯一索引确保索引列中的值是唯一的,通常用于确保表中的某些元素数据不重复,例如用户Id。 值得注意,主键索引(primary key) 是一种特殊的唯一索引,它通常用来标识表中的每一行,且不允许有空值

单列索引: 最普通的索引,没有任何特殊限制。

多列索引: 有时候需要根据多个列进行查询,即要查询符合多种条件的记录,而多列索引就可以显著提高这种复合条件查询的性能。

在Gorm中如何使用索引?

简单来讲,在Gorm中主要使用结构体标签来定义索引,使用gorm标签来指定模型结构体的字段如何与数据库的索引关联。

创建单例索引 假设有一个名为 User 的模型,现在想要在 email 列上创建一个单列索引:

type User struct {
    gorm.Model
    Username string `gorm:"primary_key"` // 使用 "primary_key" 标签创建主键
    Email    string `gorm:"index"`       // 使用 "index" 标签创建普通索引
}

创建组合索引 假设有一个名为 Order 的模型,其中包含了 user_idorder_date 两个列,现在想要为这两个列创建一个组合索引以提高查询性能,我们可以使用 AddIndex 方法创建组合索引:

type Order struct {
    gorm.Model
    UserID     uint
    OrderDate  time.Time
    // 其他订单相关字段...
}

// 创建组合索引,覆盖了UserID和OrderDate列
db.AutoMigrate(&Order{})
db.Model(&Order{}).AddIndex("idx_user_order", "user_id", "order_date")

索引的优缺点

优点

  • 大大提高数据查询的速度
  • 通过索引对数据进行排序,降低了数据的排序成本,从而降低了CPU的消耗

缺点

  • 降低了更新表的效率。每次对表进行增删改操作时,数据库不仅要保存数据,还要更新对应的索引文件
  • 索引的存储需要消耗数据库的资源。如果设置的索引不当,可能会导致索引占据的存储空间超过表中数据的存储空间,使数据库性能下降

如何维护索引?

  1. 避免过度索引:过多的索引会增加数据库的存储和维护成本,同时也会影响数据库的性能。因此,在建立索引时,应该根据实际查询场景进行决定,尽量避免过度索引。
  2. 建立复合索引:对于经常需要同时查询多个列的语句,建立复合索引可以有效地提高查询效率。值得注意的是,复合索引的建立顺序需要根据实际查询场景进行决定。
  3. 使用覆盖索引:覆盖索引是指查询语句只需要使用索引就可以获取需要的数据,而不需要回表操作。覆盖索引可以减少MySQL的I/O操作,从而提高查询效率。
  4. 避免使用函数在索引列上进行计算:在查询语句中,应尽量避免在索引列上使用函数进行计算,因为这样会使MySQL无法使用索引,而需要进行全表扫描。
  5. 定期维护索引:定期维护索引可以清理无用的索引,以保证数据库的正常运行。

外键

何为外键?

一句话,用来建立表与表之间的关系,从而保证数据的完整性和一致性。 展开来说,外键是指一个表中的一个或多个字段,它们的值必须在另一个表中的某个字段存在。这个被参照的表中的字段通常是主键,这样就可以通过外键将两个表关联起来。

举个简单的外键例子

假设有两个表,一个是订单表,一个是客户表。订单表中有一个字段是客户ID,这个字段就可以作为外键,参照客户表中的客户ID字段。这样,当我们在订单表中插入一条数据时,就必须保证这个客户ID在客户表中已经存在,否则就会出现数据不一致的问题。

外键有何用?

保证数据的完整性和一致性。通过外键,我们可以将多个表关联起来,从而避免数据重复和冗余。同时,外键也可以限制数据的删除和修改,以避免对关联表中的数据造成影响。

如何使用外键?

假设有两个模型 UserProfile,其中 Profile 模型有一个外键关联到 User 模型的 ID 列。我们可以这样定义关联:

type User struct {
    gorm.Model
    Username string
    Profile  Profile // 单个外键关联
}

type Profile struct {
    gorm.Model
    UserID   uint // 外键字段
    Address  string
}

// 在User模型上定义外键关联
db.Model(&Profile{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

何为锁?

一句话,一种用于处理大量并发访问共享数据的机制,用来保证数据的完整性和一致性。主要有两种机制的锁:乐观锁和悲观锁。

乐观锁: 简单来说,在读取数据时不加锁,但在更新数据之前,会检查是否有其他线程或进程已经修改了数据。如果检测到其他修改,就会阻止当前操作,以避免数据不一致性。

具体来说,当一个线程想要读取共享数据时,它会获取数据的当前版本号,并将其存储在本地。然后当其想要修改共享数据,并尝试把修改后的数据写回时,线程会检查当前数据的版本号是否仍然与它在读取时存储的版本号相同。如果相同,说明在此线程读取数据后没有其他线程修改过它,那么写操作将成功,版本号会更新。但如果版本号不同,说明在读取数据后有其他线程修改了它,那么写操作将失败

悲观锁: 简单来说,在访问共享资源之前,先假定会发生竞争和冲突,因此会采取一些措施来阻止其他线程或进程同时访问该资源,以确保数据的完整性和一致性。

具体来说,当一个线程想要访问共享资源时,它会首先尝试获取锁。如果锁已经被其他线程占用,那么当前线程会被阻塞,直到锁被释放为止。一旦获取到锁,线程就可以安全地访问共享资源,并且其他线程必须等待当前线程释放锁之后才能访问。当线程完成对共享资源的操作后,它会释放锁,这样其他线程就有机会获取锁并访问资源。