MySQL頻繁更新熱點數(shù)據(jù)高并發(fā)場景下的具體解決方案
更新時間:2025年10月29日 08:53:39 作者:學亮編程手記
本文詳細介紹了MySQL高頻更新熱點數(shù)據(jù)在高并發(fā)場景下的解決方案,包括應(yīng)用層緩存、數(shù)據(jù)分片、隊列削峰、數(shù)據(jù)庫層面優(yōu)化(如CAS、調(diào)整事務(wù)隔離級別)、讀寫分離、請求合并、參數(shù)調(diào)優(yōu)、分布式計數(shù)器等方法,需要的朋友可以參考下
熱點數(shù)據(jù)問題確實是高并發(fā)場景下的典型瓶頸。以下是針對熱點數(shù)據(jù)問題的具體解決方案:
1. 應(yīng)用層緩存 + 批量更新
使用 Redis 計數(shù)器
// 應(yīng)用層累加,定期批量更新到數(shù)據(jù)庫
public class HotSpotCounter {
private Jedis jedis;
public void increment(String key) {
// 在Redis中累加
jedis.incr(key);
}
// 定時任務(wù),批量同步到數(shù)據(jù)庫
@Scheduled(fixedRate = 5000) // 每5秒同步一次
public void syncToDatabase() {
Set<String> keys = jedis.keys("counter:*");
for (String key : keys) {
Long value = Long.parseLong(jedis.get(key));
String entityId = key.substring(8); // 去掉"counter:"前綴
// 批量更新數(shù)據(jù)庫
updateDatabase(entityId, value);
// 清空Redis計數(shù)器
jedis.set(key, "0");
}
}
}
2. 數(shù)據(jù)分片(水平拆分)
將單行熱點數(shù)據(jù)拆分為多行
-- 原始熱點表
CREATE TABLE page_views (
page_id INT PRIMARY KEY,
view_count BIGINT
);
-- 拆分為多行
CREATE TABLE page_views_sharded (
page_id INT,
shard_id INT, -- 分片ID (0-15)
view_count BIGINT,
PRIMARY KEY (page_id, shard_id)
);
-- 更新時分散到不同分片
UPDATE page_views_sharded
SET view_count = view_count + 1
WHERE page_id = 1001 AND shard_id = RAND() * 16;
-- 查詢時匯總
SELECT SUM(view_count) FROM page_views_sharded WHERE page_id = 1001;
3. 隊列削峰填谷
使用消息隊列緩沖寫請求
@Component
public class ViewCountService {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void recordView(int pageId) {
// 發(fā)送到消息隊列,異步處理
kafkaTemplate.send("page-view-topic", String.valueOf(pageId));
}
}
// 消費者,批量處理
@KafkaListener(topics = "page-view-topic")
public void batchUpdateViews(List<String> pageIds) {
Map<Integer, Long> countMap = pageIds.stream()
.collect(Collectors.groupingBy(Integer::parseInt, Collectors.counting()));
// 批量更新數(shù)據(jù)庫
for (Map.Entry<Integer, Long> entry : countMap.entrySet()) {
updatePageView(entry.getKey(), entry.getValue());
}
}
4. 數(shù)據(jù)庫層面的優(yōu)化
使用 CAS (Compare-And-Set) 更新
-- 基于當前值的更新,減少鎖競爭 UPDATE products SET stock = stock - 1 WHERE id = 1001 AND stock > 0; -- 或者使用版本號 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1001 AND version = @current_version;
調(diào)整事務(wù)隔離級別(臨時方案)
-- 對于計數(shù)類操作,使用READ-COMMITTED + 短事務(wù) SET SESSION transaction_isolation = 'READ-COMMITTED'; BEGIN; UPDATE counters SET value = value + 1 WHERE name = 'page_views'; COMMIT;
5. 讀寫分離
寫主庫,讀從庫
-- 寫操作指向主庫 UPDATE hot_table SET count = count + 1 WHERE id = 1; -- 主庫 -- 讀操作指向從庫 SELECT count FROM hot_table WHERE id = 1; -- 從庫
6. 應(yīng)用層合并請求
請求合并窗口
public class RequestMerger {
private Map<Integer, AtomicLong> counterMap = new ConcurrentHashMap<>();
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public RequestMerger() {
// 每100ms批量處理一次
scheduler.scheduleAtFixedRate(this::flush, 100, 100, TimeUnit.MILLISECONDS);
}
public void increment(int id) {
counterMap.computeIfAbsent(id, k -> new AtomicLong(0)).incrementAndGet();
}
private void flush() {
Map<Integer, Long> snapshot = new HashMap<>();
counterMap.forEach((id, atomic) -> {
long value = atomic.getAndSet(0);
if (value > 0) {
snapshot.put(id, value);
}
});
// 批量更新數(shù)據(jù)庫
batchUpdate(snapshot);
}
}
7. 數(shù)據(jù)庫參數(shù)調(diào)優(yōu)
優(yōu)化 InnoDB 參數(shù)
-- 增加鎖相關(guān)內(nèi)存 SET GLOBAL innodb_buffer_pool_size = 8G; -- 根據(jù)內(nèi)存調(diào)整 SET GLOBAL innodb_log_file_size = 2G; -- 增大日志文件 SET GLOBAL innodb_lock_wait_timeout = 10; -- 減少鎖等待時間 -- 調(diào)整線程并發(fā)數(shù) SET GLOBAL innodb_thread_concurrency = 0; -- 0表示不限制
8. 架構(gòu)層面的解決方案
使用分布式計數(shù)器
// 使用 Redis Cluster 分散熱點
public class DistributedCounter {
public void increment(String key) {
// 使用CRC32分片到不同的Redis節(jié)點
int slot = CRC32.hash(key) % 16384;
String redisNode = getNodeBySlot(slot);
redisTemplate(redisNode).opsForValue().increment(key);
}
}
實際案例:電商庫存熱點
問題場景
-- 熱點商品庫存更新,秒殺時大量并發(fā) UPDATE products SET stock = stock - 1 WHERE id = 1001 AND stock > 0;
解決方案
-- 1. 庫存分片
CREATE TABLE product_stock_shard (
product_id INT,
shard_id TINYINT, -- 0-9 10個分片
stock INT,
PRIMARY KEY (product_id, shard_id)
);
-- 2. 更新時隨機選擇分片
UPDATE product_stock_shard
SET stock = stock - 1
WHERE product_id = 1001 AND shard_id = FLOOR(RAND() * 10) AND stock > 0;
-- 3. 檢查是否成功,如果失敗重試其他分片
監(jiān)控和診斷
監(jiān)控熱點行鎖
-- 查看行鎖等待 SELECT * FROM information_schema.INNODB_LOCKS WHERE lock_table = 'your_hot_table'; -- 查看鎖等待關(guān)系 SELECT * FROM information_schema.INNODB_LOCK_WAITS;
監(jiān)控數(shù)據(jù)庫狀態(tài)
-- 查看InnoDB狀態(tài) SHOW ENGINE INNODB STATUS; -- 查看當前運行的事務(wù) SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started DESC LIMIT 10;
選擇策略的建議
- 輕度熱點:應(yīng)用層緩存 + 批量更新
- 中度熱點:數(shù)據(jù)分片 + 隊列削峰
- 重度熱點:分布式計數(shù)器 + 讀寫分離
- 秒殺場景:預扣庫存 + 異步最終一致性
關(guān)鍵原則:將串行更新改為并行更新,將實時更新改為批量更新,將單點壓力分散到多個節(jié)點。
以上就是MySQL頻繁更新熱點數(shù)據(jù)高并發(fā)場景下的具體解決方案的詳細內(nèi)容,更多關(guān)于MySQL頻繁更新熱點數(shù)據(jù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Slave memory leak and trigger oom-killer
這篇文章主要介紹了Slave memory leak and trigger oom-killer,需要的朋友可以參考下2016-07-07
MySQL如何根據(jù)不同條件聯(lián)查不同表的數(shù)據(jù)if/case
這篇文章主要介紹了MySQL如何根據(jù)不同條件聯(lián)查不同表的數(shù)據(jù)if/case問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
在Centos7中利用Shell腳本實現(xiàn)MySQL數(shù)據(jù)備份
備份是容災的基礎(chǔ),是指為防止系統(tǒng)出現(xiàn)操作失誤或系統(tǒng)故障導致數(shù)據(jù)丟失,而將全部或部分數(shù)據(jù)集合從應(yīng)用主機的硬盤或陣列復制到其它的存儲介質(zhì)的過程,本文將給大家介紹了在Centos7中利用Shell腳本實現(xiàn)MySQL數(shù)據(jù)備份,文中有詳細的圖文介紹,需要的朋友可以參考下2023-12-12

