With championship leader Nico Rosberg only able to manage fifth after first-corner contact with team mate Hamilton and a late-race puncture, the gap between the Mercedes men at the top of the table is now down to just nine points.
Max Verstappen was a fighting fourth for Red Bull, resisting intense pressure from Rosberg in the closing laps and forcing the Mercedes driver into a mistake and a spin on the final tour.
Ferrari's Kimi Raikkonen was sixth, while seventh went to Daniel Ricciardo in the second Red Bull, with Force India’s Nico Hulkenberg and Sergio Perez completing the top ten, split by the Toro Rosso of Carlos Sainz, who came from 20th on the grid to secure ninth place.
Hamilton might have messed up another start, but when it mattered on the Circuit Gilles-Villeneuve he “floated like a butterfly and stung like a bee” as he overcame Ferrari’s most consistent and convincing challenge since Malaysia 2015.
Hamilton dedicated the win to the late Muhammad Ali, out of respect for the inspiration the boxing legend provided during his early career.
It was Rosberg who appeared to have made the slightly better getaway as he moved alongside Hamilton, but then Vettel came boiling off the line to grab the lead ahead of both Silver Arrows as they left the grid.
The German leapt away and appeared to have a lock on the race, as a touch between Hamilton and Rosberg in Turn 1 left the second Mercedes driver down in ninth place.
But when he made his first pit stop, on the 11th lap, Ferrari put Vettel on to the supersoft Pirellis, committing him to a second stop later on as use of the soft tyre was mandatory here.
Hamilton led for the next 13 laps, dropping to second again as he switched to soft rubber. When Vettel then pitted for softs on the 37th lap, the battle was well and truly on.
Vettel rejoined only 7.8s behind the Mercedes, and victory seemed assured, but Hamilton was having none of it. Vettel got the gap down to 4.3s by the 55th lap, but then a mistake at the final corner cost him time, as did another five laps later. By then it was clear that Ferrari’s strategy of using Pirelli’s optimum tyre choice was not going to work. It had been based on 40 degrees C track temperature, but in the race that never went beyond 23 degrees.
Vettel nevertheless finished an honourable second, five seconds adrift, having shown sufficient speed with Ferrari’s revised turbocharger to suggest that the rest of the season will be a fantastic fight.
Behind them, Bottas made fine use of Williams’ single-stop strategy to take the final podium slot. Red Bull should have been there, but like Ferrari they chose to pit Verstappen and Ricciardo twice. At one stage when he was running third and Ricciardo fourth, the Dutchman was told not to hold up his team mate but it never came to that as the Australian never got close enough to challenge. Later, he dropped behind Raikkonen’s Ferrari after a slow second tyre stop.
In the end Verstappen finished a fine fourth, 6.5s behind Bottas, after a dramatic defence against Rosberg. After his early incident the German had a poor middle part of the race with undisclosed warnings of mechanical problems in his cockpit, and a slow right rear puncture. Verstappen was much harder to pass than Ricciardo had been, however, and resisted strongly on the 63rd and 64th laps. Rosberg counter-attacked on the penultimate lap, and actually squeaked ahead going into the final corner, only immediately to spin unprompted and hand back the place. He was very lucky to keep fifth, as Raikkonen and his constant shadow Ricciardo were within a second of passing him.
Hulkenberg took eighth for Force India ahead of Sainz, who drove a great race from 20th on the grid after a five-place grid drop for a gearbox change because of his qualifying accident, and Perez won the final point for Force India after a great scrap with McLaren’s Fernando Alonso and Toro Rosso’s Daniil Kvyat.
Esteban Gutierrez and Romain Grosjean brought their Haas cars home 13th and 14th respectively, ahead of Marcus Ericsson’s one-stopping Sauber and Kevin Magnussen’s Renault, as the Manors of Pascal Wehrlein and Rio Haryanto sandwiched Felipe Nasr’s Sauber. Nasr was delayed after spinning in Turn 3 on the opening lap following contact with Magnussen.
Jenson Button was the first of three retirements, pulling over on lap 11 after his McLaren’s Honda engine let go. Fellow Briton Jolyon Palmer was next up, bringing his Renault into the garage with a water leak on lap 19. And Felipe Massa made a terminal visit to the Williams pit on lap 38.
Rosberg continues to lead the title chase, but what was once a 43-point lead has been decimated to only nine over Hamilton, with Vettel third on 78 from Ricciardo on 72, Raikkonen on 69 and Verstappen on 50. In the constructors' stakes Mercedes extend their lead with 223 points over Ferrari’s 147, with Red Bull third on 130.
The paddock now moves rapidly to Azerbaijan and the inaugural race at the new Baku City Circuit, the 2016 Formula 1 Grand Prix of Europe, next Sunday.