0%

MySQL 事务详解:ACID 特性、隔离级别与实践

事务是数据库操作的基本单位,确保一组 SQL 语句要么全部成功执行,要么全部失败回滚,是保证数据一致性的核心机制。MySQL 中仅 InnoDB 存储引擎支持事务,本文详细解析事务的特性、隔离级别及实际应用。

事务的基本概念

事务(Transaction):由一个或多个 SQL 语句组成的执行单元,具备 “原子性”—— 要么所有语句执行成功(提交),要么全部失败(回滚),不存在部分执行的情况。

  • 支持情况:MySQL 中只有 InnoDB 引擎支持事务(MyISAM、MEMORY 等引擎不支持)。
  • 典型场景:转账操作(扣减 A 账户金额 + 增加 B 账户金额,两步必须同时成功或失败)。

事务的 ACID 特性

事务的四大核心特性(ACID)是保证数据可靠性的基础:

特性 含义说明 示例
原子性(Atomicity) 事务是不可分割的最小单位,操作要么全执行,要么全不执行。 转账时,若扣减 A 账户后系统崩溃,B 账户不会增加金额(事务回滚,恢复 A 账户)。
一致性(Consistency) 事务执行前后,数据库从一个一致性状态切换到另一个一致性状态(数据规则不变)。 转账前 A+B 总金额为 1000,转账后总金额仍为 1000(不会多增或少减)。
隔离性(Isolation) 多个事务并发执行时,彼此不干扰,每个事务感觉不到其他事务的存在。 事务 1 查询余额时,事务 2 的转账操作不会影响事务 1 的查询结果(隔离级别决定影响程度)。
持久性(Durability) 事务提交后,对数据的修改永久生效,即使系统崩溃也不会丢失。 事务提交后,数据写入磁盘,重启数据库后修改仍存在。

事务的创建与控制

MySQL 默认开启 “自动提交”(autocommit=ON),即每条 SQL 语句自动作为一个事务提交。如需手动控制事务,需先关闭自动提交。

事务控制的核心语句

阅读全文 »

Java 编程细节:提升代码质量的关键注意事项

在 Java 开发中,一些看似微小的细节往往决定了代码的健壮性、可读性和性能。本文总结了日常编程中容易忽略的关键细节,涵盖方法重写、对象比较、异常处理等核心场景,帮助开发者写出更可靠的代码。

重写 equalshashCode 的注意事项

equalshashCodeObject 类的核心方法,用于对象比较和哈希表操作,重写时需严格遵循规范,否则可能导致集合框架(如 HashMapHashSet)行为异常。

重写 equals 时必须使用 @Override 注解

  • 错误示例:误将参数类型改为具体类(如User),导致实际是重载而非重写。

    1
    2
    3
    4
    // 错误:这是重载(参数类型为 User),而非重写 Object 的 equals
    public boolean equals(User other) {
    return this.id == other.id;
    }
  • 正确做法:参数类型必须为Object,并添加@Override注解,让编译器校验正确性。

    1
    2
    3
    4
    5
    6
    7
    @Override // 强制编译器检查是否正确重写
    public boolean equals(Object obj) {
    if (this == obj) return true; // 自身比较
    if (obj == null || getClass() != obj.getClass()) return false; // 类型不一致
    User other = (User) obj; // 强制转型
    return this.id == other.id; // 核心比较逻辑
    }

重写 equals 必须同时重写 hashCode

  • 规范要求Object 类文档明确规定:

    若两个对象通过 equals 比较相等,则它们的 hashCode 必须返回相同的值。
    若两个对象 hashCode 不同,则 equals 一定返回 false

  • 后果:若仅重写 equals 而忽略 hashCode,会导致对象在哈希集合(如 HashSet)中无法正确去重,或在 HashMap 中作为键时无法被正确查找。

  • 正确示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public boolean equals(Object obj) {
    // 省略 equals 实现...
    }

    @Override
    public int hashCode() {
    // 确保 equals 相等的对象,hashCode 也相等
    return Objects.hash(id, name); // 推荐使用 Objects.hash 组合字段
    }
  • 最佳实践

    • 参与 equals 比较的字段(如 idname),必须全部纳入 hashCode 计算。
    • 避免使用可变字段作为 hashCode 计算依据(对象修改后哈希值变化,导致集合操作异常)。
阅读全文 »

MySQL 函数大全:从基础操作到高级应用

MySQL 提供了丰富的内置函数,涵盖字符处理、数学计算、日期操作、聚合统计等场景,掌握这些函数能大幅提升查询效率和灵活性。本文按功能分类详解常用函数,包含语法、示例及使用注意事项。

字符函数:处理字符串的核心工具

字符函数用于字符串的拼接、截取、转换等操作,是日常查询中最常用的函数类别。

1. LENGTH(str):获取字符串字节数

  • 作用:返回字符串的字节长度(注意:不同字符集字节数不同,如 UTF8 中一个汉字占 3 字节)。

  • 示例:

    1
    2
    SELECT LENGTH('john');  -- 结果:4(纯英文,每个字符1字节)
    SELECT LENGTH('张三'); -- 结果:6(UTF8 编码,每个汉字3字节)

2. CONCAT(str1, str2, ...)CONCAT_WS(sep, str1, ...):拼接字符串

  • CONCAT:直接拼接多个字符串,若任一参数为 NULL,结果为 NULL

  • CONCAT_WS:以第一个参数为分隔符拼接字符串(WS 即 “With Separator”),忽略 NULL

  • 示例:

阅读全文 »

Hadoop问题集锦

在 Hadoop 相关的实际操作和应用过程中,常常会遇到各种问题,以下为你介绍两种常见问题及解决办法。

在使用flume将数据存储到hdfs时出现错误

错误信息如下:

1
2
[SinkRunner-PollingRunner-DefaultSinkProcessor] (org.apache.flume.sink.hdfs.HDFSEventSink.process:459)  - process failed
java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;)V

问题原因

这个问题是由于 Flume 和 Hadoop 所依赖的 Guava 库版本不一致导致的。Guava 是 Google 提供的一个 Java 工具类库,不同版本的 Guava 可能会有方法的增减或修改。当 Flume 和 Hadoop 使用的 Guava 版本存在差异时,就可能出现一方调用了另一方版本中不存在的方法,从而抛出NoSuchMethodError异常。

阅读全文 »

ConcurrentLinkedQueue源码深度解析:高性能无锁队列的实现原理

ConcurrentLinkedQueue 是 Java 并发包(JUC)中用于高并发场景的无锁队列,基于单向链表结构实现。它通过 CAS(Compare-And-Swap)操作替代传统的锁机制,在保证线程安全的同时显著提升了并发性能。本文将从源码角度深入剖析其设计思想、核心实现及性能优化策略。

核心数据结构与初始化

底层结构:单向链表节点

ConcurrentLinkedQueue 使用内部类 Node 表示链表节点,每个节点包含数据域 item 和指针域 next

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private static class Node<E> {  
volatile E item; // 节点存储的数据(volatile 保证可见性)
volatile Node<E> next; // 指向下一个节点的引用(volatile 保证可见性)

Node(E item) {
UNSAFE.putObject(this, itemOffset, item); // 使用 Unsafe 直接写入内存
}

// CAS 操作:更新 item 域
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}

// 延迟设置 next 域(不保证立即对其他线程可见,减少内存屏障开销)
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}

// CAS 操作:更新 next 域
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}

// Unsafe 机制初始化(通过反射获取内存偏移量)
private static final sun.misc.Unsafe UNSAFE;
// 偏移量
private static final long itemOffset;
// 下一个元素的偏移量
private static final long nextOffset;

static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}

关键点

  • volatile 修饰:确保多线程间的内存可见性,避免指令重排序;
  • CAS 操作:通过 Unsafe 类的原子操作保证线程安全,避免使用锁;
  • lazySetNext:延迟写操作,减少内存屏障,提升性能(适用于非关键路径)。

队列初始化

队列创建时,head 和 tail 指向同一个哨兵节点(item 为 null):

阅读全文 »